diff --git a/README.markdown b/README.markdown index e98c9f93e..de593e020 100644 --- a/README.markdown +++ b/README.markdown @@ -75,6 +75,7 @@ Documentation - [Swimlanes](docs/swimlanes.markdown) - [Calendar](docs/calendar.markdown) - [Budget](docs/budget.markdown) +- [Analytics](docs/analytics.markdown) #### Working with tasks diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php index 8b0684d44..e7578da9c 100644 --- a/app/Controller/Analytic.php +++ b/app/Controller/Analytic.php @@ -125,4 +125,46 @@ class Analytic extends Base ))); } } + + /** + * Show burndown chart + * + * @access public + */ + public function burndown() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week'))); + $to = $this->request->getStringParam('to', date('Y-m-d')); + + if (! empty($values)) { + $from = $values['from']; + $to = $values['to']; + } + + if ($this->request->isAjax()) { + $this->response->json(array( + 'metrics' => $this->projectDailySummary->getRawMetricsByDay($project['id'], $from, $to), + 'labels' => array( + 'day' => t('Date'), + 'score' => t('Complexity'), + ) + )); + } + else { + $this->response->html($this->layout('analytic/burndown', array( + 'values' => array( + 'from' => $from, + 'to' => $to, + ), + 'display_graph' => $this->projectDailySummary->countDays($project['id'], $from, $to) >= 2, + 'project' => $project, + 'date_format' => $this->config->get('application_date_format'), + 'date_formats' => $this->dateParser->getAvailableFormats(), + 'title' => t('Burndown chart for "%s"', $project['name']), + ))); + } + } } diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 2e543e483..e9725a002 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 558000717..cd0b4b7f6 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index f0a2ef4cb..efbf589e2 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 48a38f46d..e8df52d05 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index a99e19ae7..63270415c 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -852,4 +852,7 @@ return array( 'uploaded by: %s' => 'Télécharger par : %s', 'uploaded on: %s' => 'Télécharger le : %s', 'size: %s' => 'Taille : %s', + 'Burndown chart for "%s"' => 'Graphique d\'avancement pour « %s »', + 'Burndown chart' => 'Graphique d\'avancement', + 'This chart show the task complexity over the time (Work Remaining).' => 'Ce graphique représente la complexité des tâches en fonction du temps (travail restant).', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index f398629ee..ca6e540f5 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 385339f07..7c932d02a 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 9cedf534f..4b56298a8 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 31a8006c0..374bfe20d 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 8dee492e2..99de94603 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index f855edb0b..e02489a6b 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index d67de097e..8f2ed8256 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 26a3599a5..8245d1771 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index 90de74705..7b933178a 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index f48999cb6..72edd63b6 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index fb8bc0807..fad993d1c 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 10d4c6049..8c4548070 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -850,4 +850,7 @@ return array( // 'uploaded by: %s' => '', // 'uploaded on: %s' => '', // 'size: %s' => '', + // 'Burndown chart for "%s"' => '', + // 'Burndown chart' => '', + // 'This chart show the task complexity over the time (Work Remaining).' => '', ); diff --git a/app/Model/ProjectAnalytic.php b/app/Model/ProjectAnalytic.php index 46f2242de..a663f921c 100644 --- a/app/Model/ProjectAnalytic.php +++ b/app/Model/ProjectAnalytic.php @@ -83,6 +83,8 @@ class ProjectAnalytic extends Base $metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2); } + ksort($metrics); + return array_values($metrics); } } diff --git a/app/Model/ProjectDailySummary.php b/app/Model/ProjectDailySummary.php index 0a06bbd42..9e7c836a1 100644 --- a/app/Model/ProjectDailySummary.php +++ b/app/Model/ProjectDailySummary.php @@ -20,6 +20,9 @@ class ProjectDailySummary extends Base /** * Update daily totals for the project * + * "total" is the number open of tasks in the column + * "score" is the sum of tasks score in the column + * * @access public * @param integer $project_id Project id * @param string $date Record date (YYYY-MM-DD) @@ -40,6 +43,7 @@ class ProjectDailySummary extends Base 'project_id' => $project_id, 'column_id' => $column_id, 'total' => 0, + 'score' => 0, )); $db->table(ProjectDailySummary::TABLE) @@ -47,6 +51,11 @@ class ProjectDailySummary extends Base ->eq('column_id', $column_id) ->eq('day', $date) ->update(array( + 'score' => $db->table(Task::TABLE) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->eq('is_active', Task::STATUS_OPEN) + ->sum('score'), 'total' => $db->table(Task::TABLE) ->eq('project_id', $project_id) ->eq('column_id', $column_id) @@ -92,12 +101,39 @@ class ProjectDailySummary extends Base ProjectDailySummary::TABLE.'.column_id', ProjectDailySummary::TABLE.'.day', ProjectDailySummary::TABLE.'.total', + ProjectDailySummary::TABLE.'.score', Board::TABLE.'.title AS column_title' ) ->join(Board::TABLE, 'id', 'column_id') ->eq(ProjectDailySummary::TABLE.'.project_id', $project_id) ->gte('day', $from) ->lte('day', $to) + ->asc(ProjectDailySummary::TABLE.'.day') + ->findAll(); + } + + /** + * Get raw metrics for the project within a data range grouped by day + * + * @access public + * @param integer $project_id Project id + * @param string $from Start date (ISO format YYYY-MM-DD) + * @param string $to End date + * @return array + */ + public function getRawMetricsByDay($project_id, $from, $to) + { + return $this->db->table(ProjectDailySummary::TABLE) + ->columns( + ProjectDailySummary::TABLE.'.day', + 'SUM('.ProjectDailySummary::TABLE.'.total) AS total', + 'SUM('.ProjectDailySummary::TABLE.'.score) AS score' + ) + ->eq(ProjectDailySummary::TABLE.'.project_id', $project_id) + ->gte('day', $from) + ->lte('day', $to) + ->asc(ProjectDailySummary::TABLE.'.day') + ->groupBy(ProjectDailySummary::TABLE.'.day') ->findAll(); } diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index c2bfc97a1..6ad6dc518 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,12 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 63; +const VERSION = 64; + +function version_64($pdo) +{ + $pdo->exec('ALTER TABLE project_daily_summaries ADD COLUMN score INT NOT NULL DEFAULT 0'); +} function version_63($pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 80f292190..b5cf72a60 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,12 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 44; +const VERSION = 45; + +function version_45($pdo) +{ + $pdo->exec('ALTER TABLE project_daily_summaries ADD COLUMN score INTEGER NOT NULL DEFAULT 0'); +} function version_44($pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index d244cd89d..fb1d7d29a 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,12 @@ use Core\Security; use PDO; use Model\Link; -const VERSION = 62; +const VERSION = 63; + +function version_63($pdo) +{ + $pdo->exec('ALTER TABLE project_daily_summaries ADD COLUMN score INTEGER NOT NULL DEFAULT 0'); +} function version_62($pdo) { diff --git a/app/Subscriber/ProjectDailySummarySubscriber.php b/app/Subscriber/ProjectDailySummarySubscriber.php index 6d7377343..f865c0368 100644 --- a/app/Subscriber/ProjectDailySummarySubscriber.php +++ b/app/Subscriber/ProjectDailySummarySubscriber.php @@ -12,6 +12,7 @@ class ProjectDailySummarySubscriber extends Base implements EventSubscriberInter { return array( Task::EVENT_CREATE => array('execute', 0), + Task::EVENT_UPDATE => array('execute', 0), Task::EVENT_CLOSE => array('execute', 0), Task::EVENT_OPEN => array('execute', 0), Task::EVENT_MOVE_COLUMN => array('execute', 0), diff --git a/app/Template/analytic/burndown.php b/app/Template/analytic/burndown.php new file mode 100644 index 000000000..5ebe1032b --- /dev/null +++ b/app/Template/analytic/burndown.php @@ -0,0 +1,34 @@ +
= t('Not enough data to show the graph.') ?>
+ += t('This chart show the task complexity over the time (Work Remaining).') ?>
diff --git a/app/Template/analytic/sidebar.php b/app/Template/analytic/sidebar.php index a7076db9d..f35152810 100644 --- a/app/Template/analytic/sidebar.php +++ b/app/Template/analytic/sidebar.php @@ -10,5 +10,8 @@