Add new analytic page: Average time spent into each column
This commit is contained in:
parent
bb8b4c0e36
commit
663a1c20e6
|
|
@ -3,7 +3,7 @@
|
|||
namespace Controller;
|
||||
|
||||
/**
|
||||
* Project Anaytic controller
|
||||
* Project Analytic controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
|
|
@ -26,6 +26,22 @@ class Analytic extends Base
|
|||
return $this->template->layout('analytic/layout', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show average time spent by column
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function averageTimeByColumn()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->layout('analytic/avg_time_columns', array(
|
||||
'project' => $project,
|
||||
'metrics' => $this->projectAnalytic->getAverageTimeSpentByColumn($project['id']),
|
||||
'title' => t('Average time spent into each column for "%s"', $project['name']),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tasks distribution graph
|
||||
*
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class Task extends Base
|
|||
'task' => $task,
|
||||
'lead_time' => $this->taskAnalytic->getLeadTime($task),
|
||||
'cycle_time' => $this->taskAnalytic->getCycleTime($task),
|
||||
'column_averages' => $this->taskAnalytic->getAverageTimeByColumn($task),
|
||||
'time_spent_columns' => $this->taskAnalytic->getTimeSpentByColumn($task),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ class Dt extends \Core\Base
|
|||
*/
|
||||
public function duration($seconds)
|
||||
{
|
||||
if ($seconds == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$dtF = new DateTime("@0");
|
||||
$dtT = new DateTime("@$seconds");
|
||||
return $dtF->diff($dtT)->format('%a days, %h hours, %i minutes and %s seconds');
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class ProjectAnalytic extends Base
|
|||
* Get users repartition
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $project_id
|
||||
* @return array
|
||||
*/
|
||||
public function getUserRepartition($project_id)
|
||||
|
|
@ -87,4 +87,59 @@ class ProjectAnalytic extends Base
|
|||
|
||||
return array_values($metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the average time spent into each column
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @return array
|
||||
*/
|
||||
public function getAverageTimeSpentByColumn($project_id)
|
||||
{
|
||||
$stats = array();
|
||||
$columns = $this->board->getColumnsList($project_id);
|
||||
|
||||
// Get the time spent of the last move for each tasks
|
||||
$tasks = $this->db
|
||||
->table(Task::TABLE)
|
||||
->columns('id', 'date_completed', 'date_moved', 'column_id')
|
||||
->eq('project_id', $project_id)
|
||||
->desc('id')
|
||||
->limit(1000)
|
||||
->findAll();
|
||||
|
||||
// Init values
|
||||
foreach ($columns as $column_id => $column_title) {
|
||||
$stats[$column_id] = array(
|
||||
'count' => 0,
|
||||
'time_spent' => 0,
|
||||
'average' => 0,
|
||||
'title' => $column_title,
|
||||
);
|
||||
}
|
||||
|
||||
// Get time spent foreach task/column and take into account the last move
|
||||
foreach ($tasks as &$task) {
|
||||
$sums = $this->transition->getTimeSpentByTask($task['id']);
|
||||
|
||||
if (! isset($sums[$task['column_id']])) {
|
||||
$sums[$task['column_id']] = 0;
|
||||
}
|
||||
|
||||
$sums[$task['column_id']] += ($task['date_completed'] ?: time()) - $task['date_moved'];
|
||||
|
||||
foreach ($sums as $column_id => $time_spent) {
|
||||
$stats[$column_id]['count']++;
|
||||
$stats[$column_id]['time_spent'] += $time_spent;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate average for each column
|
||||
foreach ($columns as $column_id => $column_title) {
|
||||
$stats[$column_id]['average'] = (int) ($stats[$column_id]['time_spent'] / $stats[$column_id]['count']);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,21 +45,18 @@ class TaskAnalytic extends Base
|
|||
* @param array $task
|
||||
* @return array
|
||||
*/
|
||||
public function getAverageTimeByColumn(array $task)
|
||||
public function getTimeSpentByColumn(array $task)
|
||||
{
|
||||
$result = array();
|
||||
$columns = $this->board->getColumnsList($task['project_id']);
|
||||
$averages = $this->transition->getAverageTimeSpentByTask($task['id']);
|
||||
$sums = $this->transition->getTimeSpentByTask($task['id']);
|
||||
|
||||
foreach ($columns as $column_id => $column_title) {
|
||||
|
||||
$time_spent = 0;
|
||||
$time_spent = isset($sums[$column_id]) ? $sums[$column_id] : 0;
|
||||
|
||||
if (empty($averages) && $task['column_id'] == $column_id) {
|
||||
$time_spent = time() - $task['date_creation'];
|
||||
}
|
||||
else {
|
||||
$time_spent = isset($averages[$column_id]) ? $averages[$column_id] : 0;
|
||||
if ($task['column_id'] == $column_id) {
|
||||
$time_spent += ($task['date_completed'] ?: time()) - $task['date_moved'];
|
||||
}
|
||||
|
||||
$result[] = array(
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ class Transition extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* Get average time spent by task for each column
|
||||
* Get time spent by task for each column
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id
|
||||
* @return array
|
||||
*/
|
||||
public function getAverageTimeSpentByTask($task_id)
|
||||
public function getTimeSpentByTask($task_id)
|
||||
{
|
||||
return $this->db
|
||||
->hashtable(self::TABLE)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Average time spent into each column') ?></h2>
|
||||
</div>
|
||||
|
||||
<?php if (empty($metrics)): ?>
|
||||
<p class="alert"><?= t('Not enough data to show the graph.') ?></p>
|
||||
<?php else: ?>
|
||||
<section id="analytic-avg-time-column">
|
||||
|
||||
<div id="chart" data-metrics='<?= json_encode($metrics) ?>' data-label="<?= t('Average time spent') ?>"></div>
|
||||
|
||||
<table class="table-stripped">
|
||||
<tr>
|
||||
<th><?= t('Column') ?></th>
|
||||
<th><?= t('Average time spent') ?></th>
|
||||
</tr>
|
||||
<?php foreach ($metrics as $column): ?>
|
||||
<tr>
|
||||
<td><?= $this->e($column['title']) ?></td>
|
||||
<td><?= $this->dt->duration($column['average']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
|
||||
<p class="alert alert-info">
|
||||
<?= t('This chart show the average time spent into each column for the last %d tasks.', 1000) ?>
|
||||
</p>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
|
|
@ -13,5 +13,8 @@
|
|||
<li>
|
||||
<?= $this->url->link(t('Burndown chart'), 'analytic', 'burndown', array('project_id' => $project['id'])) ?>
|
||||
</li>
|
||||
<li>
|
||||
<?= $this->url->link(t('Average time into each column'), 'analytic', 'averageTimeByColumn', array('project_id' => $project['id'])) ?>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -9,13 +9,14 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<h3><?= t('Average time spent for each column') ?></h3>
|
||||
<h3 id="analytic-task-time-column"><?= t('Time spent into each column') ?></h3>
|
||||
<div id="chart" data-metrics='<?= json_encode($time_spent_columns) ?>' data-label="<?= t('Time spent') ?>"></div>
|
||||
<table class="table-stripped">
|
||||
<tr>
|
||||
<th><?= t('Column') ?></th>
|
||||
<th><?= t('Average time spent') ?></th>
|
||||
<th><?= t('Time spent') ?></th>
|
||||
</tr>
|
||||
<?php foreach ($column_averages as $column): ?>
|
||||
<?php foreach ($time_spent_columns as $column): ?>
|
||||
<tr>
|
||||
<td><?= $this->e($column['title']) ?></td>
|
||||
<td><?= $this->dt->duration($column['time_spent']) ?></td>
|
||||
|
|
@ -25,8 +26,11 @@
|
|||
|
||||
<div class="alert alert-info">
|
||||
<ul>
|
||||
<li><?= t('The lead time is the time between the task creation and the completion.') ?></li>
|
||||
<li><?= t('The cycle time is the time between the start date and the completion.') ?></li>
|
||||
<li><?= t('If the task is not closed the current time is used.') ?></li>
|
||||
<li><?= t('The lead time is the duration between the task creation and the completion.') ?></li>
|
||||
<li><?= t('The cycle time is the duration between the start date and the completion.') ?></li>
|
||||
<li><?= t('If the task is not closed the current time is used instead of the completion date.') ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->asset->js('assets/js/vendor/d3.v3.min.js') ?>
|
||||
<?= $this->asset->js('assets/js/vendor/c3.min.js') ?>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -165,6 +165,99 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Draw chart for average time spent into each column
|
||||
function drawAvgTimeColumn()
|
||||
{
|
||||
var metrics = $("#chart").data("metrics");
|
||||
var plots = [$("#chart").data("label")];
|
||||
var categories = [];
|
||||
|
||||
for (var column_id in metrics) {
|
||||
plots.push(metrics[column_id].average);
|
||||
categories.push(metrics[column_id].title);
|
||||
}
|
||||
|
||||
c3.generate({
|
||||
data: {
|
||||
columns: [plots],
|
||||
type: 'bar'
|
||||
},
|
||||
bar: {
|
||||
width: {
|
||||
ratio: 0.5
|
||||
}
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
type: 'category',
|
||||
categories: categories
|
||||
},
|
||||
y: {
|
||||
tick: {
|
||||
format: formatDuration
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Draw chart for average time spent into each column
|
||||
function drawTaskTimeColumn()
|
||||
{
|
||||
var metrics = $("#chart").data("metrics");
|
||||
var plots = [$("#chart").data("label")];
|
||||
var categories = [];
|
||||
|
||||
for (var i = 0; i < metrics.length; i++) {
|
||||
plots.push(metrics[i].time_spent);
|
||||
categories.push(metrics[i].title);
|
||||
}
|
||||
|
||||
c3.generate({
|
||||
data: {
|
||||
columns: [plots],
|
||||
type: 'bar'
|
||||
},
|
||||
bar: {
|
||||
width: {
|
||||
ratio: 0.5
|
||||
}
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
type: 'category',
|
||||
categories: categories
|
||||
},
|
||||
y: {
|
||||
tick: {
|
||||
format: formatDuration
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formatDuration(d)
|
||||
{
|
||||
if (d >= 86400) {
|
||||
return Math.round(d/86400) + "d";
|
||||
}
|
||||
else if (d >= 3600) {
|
||||
return Math.round(d/3600) + "h";
|
||||
}
|
||||
else if (d >= 60) {
|
||||
return Math.round(d/60) + "m";
|
||||
}
|
||||
|
||||
return d + "s";
|
||||
}
|
||||
|
||||
jQuery(document).ready(function() {
|
||||
|
||||
if (Kanboard.Exists("analytic-task-repartition")) {
|
||||
|
|
@ -182,6 +275,12 @@
|
|||
else if (Kanboard.Exists("budget-chart")) {
|
||||
drawBudget();
|
||||
}
|
||||
else if (Kanboard.Exists("analytic-avg-time-column")) {
|
||||
drawAvgTimeColumn();
|
||||
}
|
||||
else if (Kanboard.Exists("analytic-task-time-column")) {
|
||||
drawTaskTimeColumn();
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in New Issue