summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.markdown1
-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
-rw-r--r--assets/js/app.js3
-rw-r--r--assets/js/src/analytic.js52
-rw-r--r--composer.lock43
-rw-r--r--docs/analytics.markdown33
-rwxr-xr-xscripts/create-sample-burndown.php55
32 files changed, 351 insertions, 26 deletions
diff --git a/README.markdown b/README.markdown
index e98c9f93..de593e02 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 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
diff --git a/assets/js/app.js b/assets/js/app.js
index bb576df2..e99f5dfc 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -160,7 +160,8 @@ Kanboard.Calendar=function(){function a(a){var b=$("#calendar").data("save-url")
a.fullCalendar("addEventSource",b);a.fullCalendar("rerenderEvents")})}function b(a){var b=$("#calendar"),c=b.data("check-url"),d={start:b.fullCalendar("getView").start.format(),end:b.fullCalendar("getView").end.format()};jQuery.extend(d,a);for(var e in d)c+="&"+e+"="+d[e];$.getJSON(c,function(a){b.fullCalendar("removeEvents");b.fullCalendar("addEventSource",a);b.fullCalendar("rerenderEvents")})}function d(){var a=Kanboard.GetStorageItem(f);if(""!==a){var a=JSON.parse(a),c;for(c in a)$("select[name="+
c+"]").val(a[c])}b(a||{});$(".calendar-filter").change(e)}function e(){var a={};$(".calendar-filter").each(function(){a[$(this).attr("name")]=$(this).val()});Kanboard.SetStorageItem(f,JSON.stringify(a));b(a)}var f="";jQuery(document).ready(function(){Kanboard.Exists("calendar")?(f="calendar_filters_"+$("#calendar").data("project-id"),$("#calendar").fullCalendar({lang:$("body").data("js-lang"),editable:!0,eventLimit:!0,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},
viewRender:d,eventDrop:a}),d()):Kanboard.Exists("user-calendar")&&$("#user-calendar").fullCalendar({lang:$("body").data("js-lang"),editable:!0,eventLimit:!0,height:Kanboard.Exists("dashboard-calendar")?500:"auto",defaultView:"agendaWeek",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},viewRender:c,eventDrop:a})})}();
-Kanboard.Analytic=function(){jQuery(document).ready(function(){Kanboard.Exists("analytic-task-repartition")?Kanboard.Analytic.TaskRepartition.Init():Kanboard.Exists("analytic-user-repartition")?Kanboard.Analytic.UserRepartition.Init():Kanboard.Exists("analytic-cfd")&&Kanboard.Analytic.CFD.Init()});return{}}();
+Kanboard.Analytic=function(){jQuery(document).ready(function(){Kanboard.Exists("analytic-task-repartition")?Kanboard.Analytic.TaskRepartition.Init():Kanboard.Exists("analytic-user-repartition")?Kanboard.Analytic.UserRepartition.Init():Kanboard.Exists("analytic-cfd")?Kanboard.Analytic.CFD.Init():Kanboard.Exists("analytic-burndown")&&Kanboard.Analytic.Burndown.Init()});return{}}();
+Kanboard.Analytic.Burndown=function(){return{Init:function(){jQuery.getJSON($("#chart").attr("data-url"),function(a){var c=a.labels,b=a.metrics;a=[];for(var d=0;d<b.length;d++){var e={},f=parseInt(b[d].score);e[c.day]=b[d].day;e[c.score]=f;a.push(e)}b=dimple.newSvg("#chart","100%",380);a=new dimple.chart(b,a);a.addCategoryAxis("x",c.day).addOrderRule("Date");a.addMeasureAxis("y",c.score);a.addSeries(null,dimple.plot.line);a.draw()})}}}();
Kanboard.Analytic.CFD=function(){return{Init:function(){jQuery.getJSON($("#chart").attr("data-url"),function(a){var c=a.labels,b=a.columns,d=a.metrics;a=[];for(var e=0;e<d.length;e++){var f={};f[c.column]=d[e].column_title;f[c.day]=d[e].day;f[c.total]=d[e].total;a.push(f)}d=dimple.newSvg("#chart","100%",380);a=new dimple.chart(d,a);a.addCategoryAxis("x",c.day).addOrderRule("Date");a.addMeasureAxis("y",c.total);a.addSeries(c.column,dimple.plot.area).addOrderRule(b.reverse());a.addLegend(10,10,500,
30,"left");a.draw()})}}}();Kanboard.Analytic.TaskRepartition=function(){return{Init:function(){jQuery.getJSON($("#chart").attr("data-url"),function(a){var c=a.labels,b=a.metrics;a=[];for(var d=0;d<b.length;d++){var e={};e[c.nb_tasks]=b[d].nb_tasks;e[c.column_title]=b[d].column_title;a.push(e)}b=dimple.newSvg("#chart","100%",350);a=new dimple.chart(b,a);a.addMeasureAxis("p",c.nb_tasks);a.addSeries(c.column_title,dimple.plot.pie).innerRadius="50%";a.addLegend(0,0,100,"100%","left");a.draw()})}}}();
Kanboard.Analytic.UserRepartition=function(){return{Init:function(){jQuery.getJSON($("#chart").attr("data-url"),function(a){var c=a.labels,b=a.metrics;a=[];for(var d=0;d<b.length;d++){var e={};e[c.nb_tasks]=b[d].nb_tasks;e[c.user]=b[d].user;a.push(e)}b=dimple.newSvg("#chart","100%",350);a=new dimple.chart(b,a);a.addMeasureAxis("p",c.nb_tasks);a.addSeries(c.user,dimple.plot.pie).innerRadius="50%";a.addLegend(0,0,100,"100%","left");a.draw()})}}}();
diff --git a/assets/js/src/analytic.js b/assets/js/src/analytic.js
index 26050a49..0912bbcb 100644
--- a/assets/js/src/analytic.js
+++ b/assets/js/src/analytic.js
@@ -2,7 +2,7 @@
Kanboard.Analytic = (function() {
jQuery(document).ready(function() {
-
+
if (Kanboard.Exists("analytic-task-repartition")) {
Kanboard.Analytic.TaskRepartition.Init();
}
@@ -12,12 +12,62 @@ Kanboard.Analytic = (function() {
else if (Kanboard.Exists("analytic-cfd")) {
Kanboard.Analytic.CFD.Init();
}
+ else if (Kanboard.Exists("analytic-burndown")) {
+ Kanboard.Analytic.Burndown.Init();
+ }
});
return {};
})();
+Kanboard.Analytic.Burndown = (function() {
+
+ function fetchData()
+ {
+ jQuery.getJSON($("#chart").attr("data-url"), function(data) {
+ drawGraph(data.metrics, data.labels);
+ });
+ }
+
+ function drawGraph(metrics, labels)
+ {
+ var series = prepareSeries(metrics, labels);
+
+ var svg = dimple.newSvg("#chart", "100%", 380);
+ var chart = new dimple.chart(svg, series);
+
+ var x = chart.addCategoryAxis("x", labels['day']);
+ x.addOrderRule("Date");
+
+ chart.addMeasureAxis("y", labels['score']);
+ chart.addSeries(null, dimple.plot.line);
+
+ chart.draw();
+ }
+
+ function prepareSeries(metrics, labels)
+ {
+ var series = [];
+
+ for (var i = 0; i < metrics.length; i++) {
+
+ var row = {};
+ var score = parseInt(metrics[i]['score']);
+ row[labels['day']] = metrics[i]['day'];
+ row[labels['score']] = score;
+ series.push(row);
+ }
+
+ return series;
+ }
+
+ return {
+ Init: fetchData
+ };
+
+})();
+
Kanboard.Analytic.CFD = (function() {
function fetchData()
diff --git a/composer.lock b/composer.lock
index 360ed751..8058424a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "466ee3928d7d3b0bbee15de3b4c76676",
+ "hash": "01ebe465ed3a59d8350670ebd4ef8793",
"packages": [
{
"name": "christian-riesen/base32",
@@ -190,12 +190,12 @@
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
- "reference": "cd6a571d2de5c0b30d538d7cd6603dc16b25b844"
+ "reference": "35c8d2d3f70b713f66e1dc14c1d6481abe5db3ac"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fguillot/picoDb/zipball/cd6a571d2de5c0b30d538d7cd6603dc16b25b844",
- "reference": "cd6a571d2de5c0b30d538d7cd6603dc16b25b844",
+ "url": "https://api.github.com/repos/fguillot/picoDb/zipball/35c8d2d3f70b713f66e1dc14c1d6481abe5db3ac",
+ "reference": "35c8d2d3f70b713f66e1dc14c1d6481abe5db3ac",
"shasum": ""
},
"require": {
@@ -219,7 +219,7 @@
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb",
- "time": "2015-03-27 02:21:18"
+ "time": "2015-04-12 02:46:43"
},
{
"name": "fguillot/simple-validator",
@@ -227,12 +227,12 @@
"source": {
"type": "git",
"url": "https://github.com/fguillot/simpleValidator.git",
- "reference": "5ebdb6df4c5f3aa2539b633eb4ae94c9e8c4ada7"
+ "reference": "41655dc7b9224395f5bb3b5623f6e428fe6d64e8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fguillot/simpleValidator/zipball/5ebdb6df4c5f3aa2539b633eb4ae94c9e8c4ada7",
- "reference": "5ebdb6df4c5f3aa2539b633eb4ae94c9e8c4ada7",
+ "url": "https://api.github.com/repos/fguillot/simpleValidator/zipball/41655dc7b9224395f5bb3b5623f6e428fe6d64e8",
+ "reference": "41655dc7b9224395f5bb3b5623f6e428fe6d64e8",
"shasum": ""
},
"require": {
@@ -256,7 +256,7 @@
],
"description": "The most easy to use validator library for PHP :)",
"homepage": "https://github.com/fguillot/simpleValidator",
- "time": "2015-02-14 21:04:14"
+ "time": "2015-04-05 21:44:06"
},
{
"name": "fguillot/simpleLogger",
@@ -495,17 +495,17 @@
},
{
"name": "symfony/console",
- "version": "v2.6.5",
+ "version": "v2.6.6",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
- "reference": "53f86497ccd01677e22435cfb7262599450a90d1"
+ "reference": "5b91dc4ed5eb08553f57f6df04c4730a73992667"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/53f86497ccd01677e22435cfb7262599450a90d1",
- "reference": "53f86497ccd01677e22435cfb7262599450a90d1",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/5b91dc4ed5eb08553f57f6df04c4730a73992667",
+ "reference": "5b91dc4ed5eb08553f57f6df04c4730a73992667",
"shasum": ""
},
"require": {
@@ -549,11 +549,11 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
- "time": "2015-03-13 17:37:22"
+ "time": "2015-03-30 15:54:10"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.6.5",
+ "version": "v2.6.6",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
@@ -614,17 +614,17 @@
"packages-dev": [
{
"name": "symfony/stopwatch",
- "version": "v2.6.5",
+ "version": "v2.6.6",
"target-dir": "Symfony/Component/Stopwatch",
"source": {
"type": "git",
"url": "https://github.com/symfony/Stopwatch.git",
- "reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5"
+ "reference": "5f196e84b5640424a166d2ce9cca161ce1e9d912"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/ba4e774f71e2ce3e3f65cabac4031b9029972af5",
- "reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5",
+ "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/5f196e84b5640424a166d2ce9cca161ce1e9d912",
+ "reference": "5f196e84b5640424a166d2ce9cca161ce1e9d912",
"shasum": ""
},
"require": {
@@ -660,7 +660,7 @@
],
"description": "Symfony Stopwatch Component",
"homepage": "http://symfony.com",
- "time": "2015-02-24 11:52:21"
+ "time": "2015-03-22 16:55:57"
}
],
"aliases": [],
@@ -674,7 +674,8 @@
"prefer-stable": false,
"platform": {
"php": ">=5.3",
- "ext-mbstring": "*"
+ "ext-mbstring": "*",
+ "ext-gd": "*"
},
"platform-dev": []
}
diff --git a/docs/analytics.markdown b/docs/analytics.markdown
new file mode 100644
index 00000000..e088a221
--- /dev/null
+++ b/docs/analytics.markdown
@@ -0,0 +1,33 @@
+Analytics
+=========
+
+User repartition
+----------------
+
+![User repartition](http://kanboard.net/screenshots/documentation/user-repartition.png)
+
+This pie chart show the number of open tasks assigned per user.
+
+Task distribution
+-----------------
+
+![Task distribution](http://kanboard.net/screenshots/documentation/task-distribution.png)
+
+This pie chart gives an overview of the number of open tasks per column.
+
+Cumulative flow diagram
+-----------------------
+
+![Cumulative flow diagram](http://kanboard.net/screenshots/documentation/cfd.png)
+
+This chart show the number of tasks cumulatively for each column over the time.
+
+Burndown chart
+--------------
+
+![Burndown chart](http://kanboard.net/screenshots/documentation/burndown-chart.png)
+
+The [burn down chart](http://en.wikipedia.org/wiki/Burn_down_chart) is available for each project.
+This chart is a graphical representation of work left to do versus time.
+
+Kanboard use the complexity or story point to generate this diagram. \ No newline at end of file
diff --git a/scripts/create-sample-burndown.php b/scripts/create-sample-burndown.php
new file mode 100755
index 00000000..ae0b2627
--- /dev/null
+++ b/scripts/create-sample-burndown.php
@@ -0,0 +1,55 @@
+#!/usr/bin/env php
+<?php
+
+require __DIR__.'/../app/common.php';
+
+use Model\ProjectDailySummary;
+use Model\TaskCreation;
+use Model\TaskStatus;
+
+$pds = new ProjectDailySummary($container);
+$taskCreation = new TaskCreation($container);
+$taskStatus = new TaskStatus($container);
+
+for ($i = 1; $i <= 15; $i++) {
+
+ $task = array(
+ 'title' => 'Task #'.$i,
+ 'project_id' => 1,
+ 'column_id' => rand(1, 4),
+ 'score' => rand(1, 21)
+ );
+
+ $taskCreation->create($task);
+}
+
+$pds->updateTotals(1, date('Y-m-d', strtotime('-7 days')));
+
+$taskStatus->close(1);
+$pds->updateTotals(1, date('Y-m-d', strtotime('-6 days')));
+
+$taskStatus->close(2);
+$taskStatus->close(3);
+$pds->updateTotals(1, date('Y-m-d', strtotime('-5 days')));
+
+$taskStatus->close(4);
+$pds->updateTotals(1, date('Y-m-d', strtotime('-4 days')));
+
+$taskStatus->close(5);
+$pds->updateTotals(1, date('Y-m-d', strtotime('-3 days')));
+
+$taskStatus->close(6);
+$taskStatus->close(7);
+$taskStatus->close(8);
+$pds->updateTotals(1, date('Y-m-d', strtotime('-2 days')));
+
+$taskStatus->close(9);
+$taskStatus->close(10);
+$pds->updateTotals(1, date('Y-m-d', strtotime('-2 days')));
+
+$taskStatus->close(12);
+$taskStatus->close(13);
+$pds->updateTotals(1, date('Y-m-d', strtotime('-1 days')));
+
+$taskStatus->close(1);
+$pds->updateTotals(1, date('Y-m-d')); \ No newline at end of file