summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-04-11 23:01:17 -0400
committerFrederic Guillot <fred@kanboard.net>2015-04-11 23:01:17 -0400
commit9ca2ba21272ddb1958e0b5b2f5842cde42508139 (patch)
treec6db297008d728a44040af5804386802b457219e /app
parent7df055aff1e1056d87bb720531d60cb079805f94 (diff)
Add burndown chart
Diffstat (limited to 'app')
-rw-r--r--app/Controller/Analytic.php42
-rw-r--r--app/Locale/da_DK/translations.php3
-rw-r--r--app/Locale/de_DE/translations.php3
-rw-r--r--app/Locale/es_ES/translations.php3
-rw-r--r--app/Locale/fi_FI/translations.php3
-rw-r--r--app/Locale/fr_FR/translations.php3
-rw-r--r--app/Locale/hu_HU/translations.php3
-rw-r--r--app/Locale/it_IT/translations.php3
-rw-r--r--app/Locale/ja_JP/translations.php3
-rw-r--r--app/Locale/nl_NL/translations.php3
-rw-r--r--app/Locale/pl_PL/translations.php3
-rw-r--r--app/Locale/pt_BR/translations.php3
-rw-r--r--app/Locale/ru_RU/translations.php3
-rw-r--r--app/Locale/sr_Latn_RS/translations.php3
-rw-r--r--app/Locale/sv_SE/translations.php3
-rw-r--r--app/Locale/th_TH/translations.php3
-rw-r--r--app/Locale/tr_TR/translations.php3
-rw-r--r--app/Locale/zh_CN/translations.php3
-rw-r--r--app/Model/ProjectAnalytic.php2
-rw-r--r--app/Model/ProjectDailySummary.php36
-rw-r--r--app/Schema/Mysql.php7
-rw-r--r--app/Schema/Postgres.php7
-rw-r--r--app/Schema/Sqlite.php7
-rw-r--r--app/Subscriber/ProjectDailySummarySubscriber.php1
-rw-r--r--app/Template/analytic/burndown.php34
-rw-r--r--app/Template/analytic/sidebar.php3
26 files changed, 187 insertions, 3 deletions
diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php
index 8b0684d4..e7578da9 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 2e543e48..e9725a00 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 55800071..cd0b4b7f 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 f0a2ef4c..efbf589e 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 48a38f46..e8df52d0 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 a99e19ae..63270415 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 f398629e..ca6e540f 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 385339f0..7c932d02 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 9cedf534..4b56298a 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 31a8006c..374bfe20 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 8dee492e..99de9460 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 f855edb0..e02489a6 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 d67de097..8f2ed825 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 26a3599a..8245d177 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 90de7470..7b933178 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 f48999cb..72edd63b 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 fb8bc080..fad993d1 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 10d4c604..8c454807 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 46f2242d..a663f921 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 0a06bbd4..9e7c836a 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 c2bfc97a..6ad6dc51 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 80f29219..b5cf72a6 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 d244cd89..fb1d7d29 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 6d737734..f865c036 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 00000000..5ebe1032
--- /dev/null
+++ b/app/Template/analytic/burndown.php
@@ -0,0 +1,34 @@
+<div class="page-header">
+ <h2><?= t('Burndown chart') ?></h2>
+</div>
+
+<?php if (! $display_graph): ?>
+ <p class="alert"><?= t('Not enough data to show the graph.') ?></p>
+<?php else: ?>
+ <section id="analytic-burndown">
+ <div id="chart" data-url="<?= $this->u('analytic', 'burndown', array('project_id' => $project['id'], 'from' => $values['from'], 'to' => $values['to'])) ?>"></div>
+ </section>
+<?php endif ?>
+
+<hr/>
+
+<form method="post" class="form-inline" action="<?= $this->u('analytic', 'burndown', array('project_id' => $project['id'])) ?>" autocomplete="off">
+
+ <?= $this->formCsrf() ?>
+
+ <div class="form-inline-group">
+ <?= $this->formLabel(t('Start Date'), 'from') ?>
+ <?= $this->formText('from', $values, array(), array('required', 'placeholder="'.$this->inList($date_format, $date_formats).'"'), 'form-date') ?>
+ </div>
+
+ <div class="form-inline-group">
+ <?= $this->formLabel(t('End Date'), 'to') ?>
+ <?= $this->formText('to', $values, array(), array('required', 'placeholder="'.$this->inList($date_format, $date_formats).'"'), 'form-date') ?>
+ </div>
+
+ <div class="form-inline-group">
+ <input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/>
+ </div>
+</form>
+
+<p class="alert alert-info"><?= t('This chart show the task complexity over the time (Work Remaining).') ?></p>
diff --git a/app/Template/analytic/sidebar.php b/app/Template/analytic/sidebar.php
index a7076db9..f3515281 100644
--- a/app/Template/analytic/sidebar.php
+++ b/app/Template/analytic/sidebar.php
@@ -10,5 +10,8 @@
<li>
<?= $this->a(t('Cumulative flow diagram'), 'analytic', 'cfd', array('project_id' => $project['id'])) ?>
</li>
+ <li>
+ <?= $this->a(t('Burndown chart'), 'analytic', 'burndown', array('project_id' => $project['id'])) ?>
+ </li>
</ul>
</div> \ No newline at end of file