Add task analytics

This commit is contained in:
Frederic Guillot 2015-07-05 16:07:21 -04:00
parent 67b9a56469
commit bb8b4c0e36
18 changed files with 186 additions and 27 deletions

View File

@ -26,4 +26,20 @@ class Activity extends Base
'title' => t('%s\'s activity', $project['name'])
)));
}
/**
* Display task activities
*
* @access public
*/
public function task()
{
$task = $this->getTask();
$this->response->html($this->taskLayout('activity/task', array(
'title' => $task['title'],
'task' => $task,
'events' => $this->projectActivity->getTask($task['id']),
)));
}
}

View File

@ -88,19 +88,20 @@ class Task extends Base
}
/**
* Display task activities
* Display task analytics
*
* @access public
*/
public function activites()
public function analytics()
{
$task = $this->getTask();
$this->response->html($this->taskLayout('task/activity', array(
$this->response->html($this->taskLayout('task/analytics', array(
'title' => $task['title'],
'task' => $task,
'ajax' => $this->request->isAjax(),
'events' => $this->projectActivity->getTask($task['id']),
'lead_time' => $this->taskAnalytic->getLeadTime($task),
'cycle_time' => $this->taskAnalytic->getCycleTime($task),
'column_averages' => $this->taskAnalytic->getAverageTimeByColumn($task),
)));
}

View File

@ -12,7 +12,7 @@ use Pimple\Container;
*
* @property \Helper\App $app
* @property \Helper\Asset $asset
* @property \Helper\Datetime $datetime
* @property \Helper\Dt $dt
* @property \Helper\File $file
* @property \Helper\Form $form
* @property \Helper\Subtask $subtask

View File

@ -2,14 +2,30 @@
namespace Helper;
use DateTime;
/**
* DateTime helpers
*
* @package helper
* @author Frederic Guillot
*/
class Datetime extends \Core\Base
class Dt extends \Core\Base
{
/**
* Get duration in seconds into human format
*
* @access public
* @param integer $seconds
* @return string
*/
public function duration($seconds)
{
$dtF = new DateTime("@0");
$dtT = new DateTime("@$seconds");
return $dtF->diff($dtT)->format('%a days, %h hours, %i minutes and %s seconds');
}
/**
* Get the age of an item in quasi human readable format.
* It's in this format: <1h , NNh, NNd

View File

@ -0,0 +1,74 @@
<?php
namespace Model;
/**
* Task Analytic
*
* @package model
* @author Frederic Guillot
*/
class TaskAnalytic extends Base
{
/**
* Get the time between date_creation and date_completed or now if empty
*
* @access public
* @param array $task
* @return integer
*/
public function getLeadTime(array $task)
{
return ($task['date_completed'] ?: time()) - $task['date_creation'];
}
/**
* Get the time between date_started and date_completed or now if empty
*
* @access public
* @param array $task
* @return integer
*/
public function getCycleTime(array $task)
{
if (empty($task['date_started'])) {
return 0;
}
return ($task['date_completed'] ?: time()) - $task['date_started'];
}
/**
* Get the average time spent in each column
*
* @access public
* @param array $task
* @return array
*/
public function getAverageTimeByColumn(array $task)
{
$result = array();
$columns = $this->board->getColumnsList($task['project_id']);
$averages = $this->transition->getAverageTimeSpentByTask($task['id']);
foreach ($columns as $column_id => $column_title) {
$time_spent = 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;
}
$result[] = array(
'id' => $column_id,
'title' => $column_title,
'time_spent' => $time_spent,
);
}
return $result;
}
}

View File

@ -38,6 +38,22 @@ class Transition extends Base
));
}
/**
* Get average time spent by task for each column
*
* @access public
* @param integer $task_id
* @return array
*/
public function getAverageTimeSpentByTask($task_id)
{
return $this->db
->hashtable(self::TABLE)
->groupBy('src_column_id')
->eq('task_id', $task_id)
->getAll('src_column_id', 'SUM(time_spent) AS time_spent');
}
/**
* Get all transitions by task
*

View File

@ -42,6 +42,7 @@ class ClassProvider implements ServiceProviderInterface
'SubtaskTimeTracking',
'Swimlane',
'Task',
'TaskAnalytic',
'TaskCreation',
'TaskDuplication',
'TaskExport',

View File

@ -43,8 +43,8 @@
<?php if ($task['is_active'] == 1): ?>
<div class="task-board-days">
<span title="<?= t('Task age in days')?>" class="task-days-age"><?= $this->datetime->age($task['date_creation']) ?></span>
<span title="<?= t('Days in this column')?>" class="task-days-incolumn"><?= $this->datetime->age($task['date_moved']) ?></span>
<span title="<?= t('Task age in days')?>" class="task-days-age"><?= $this->dt->age($task['date_creation']) ?></span>
<span title="<?= t('Days in this column')?>" class="task-days-incolumn"><?= $this->dt->age($task['date_moved']) ?></span>
</div>
<?php else: ?>
<div class="task-board-closed"><i class="fa fa-ban fa-fw"></i><?= t('Closed') ?></div>

View File

@ -48,7 +48,7 @@
<?php if ($subtask['is_timer_started']): ?>
<i class="fa fa-pause"></i>
<?= $this->url->link(t('Stop timer'), 'timer', 'subtask', array('timer' => 'stop', 'project_id' => $task['project_id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'])) ?>
(<?= $this->datetime->age($subtask['timer_start_date']) ?>)
(<?= $this->dt->age($subtask['timer_start_date']) ?>)
<?php else: ?>
<i class="fa fa-play-circle-o"></i>
<?= $this->url->link(t('Start timer'), 'timer', 'subtask', array('timer' => 'start', 'project_id' => $task['project_id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'])) ?>

View File

@ -0,0 +1,32 @@
<div class="page-header">
<h2><?= t('Analytics') ?></h2>
</div>
<div class="listing">
<ul>
<li><?= t('Lead time: ').'<strong>'.$this->dt->duration($lead_time) ?></strong></li>
<li><?= t('Cycle time: ').'<strong>'.$this->dt->duration($cycle_time) ?></strong></li>
</ul>
</div>
<h3><?= t('Average time spent for each column') ?></h3>
<table class="table-stripped">
<tr>
<th><?= t('Column') ?></th>
<th><?= t('Average time spent') ?></th>
</tr>
<?php foreach ($column_averages as $column): ?>
<tr>
<td><?= $this->e($column['title']) ?></td>
<td><?= $this->dt->duration($column['time_spent']) ?></td>
</tr>
<?php endforeach ?>
</table>
<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>
</ul>
</div>

View File

@ -5,11 +5,14 @@
<?= $this->url->link(t('Summary'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?= $this->url->link(t('Activity stream'), 'task', 'activites', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
<?= $this->url->link(t('Activity stream'), 'activity', 'task', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?= $this->url->link(t('Transitions'), 'task', 'transitions', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?= $this->url->link(t('Analytics'), 'task', 'analytics', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php if ($task['time_estimated'] > 0 || $task['time_spent'] > 0): ?>
<li>
<?= $this->url->link(t('Time tracking'), 'task', 'timesheet', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>

View File

@ -19,7 +19,7 @@
<td><?= $this->e($transition['src_column']) ?></td>
<td><?= $this->e($transition['dst_column']) ?></td>
<td><?= $this->url->link($this->e($transition['name'] ?: $transition['username']), 'user', 'show', array('user_id' => $transition['user_id'])) ?></td>
<td><?= n(round($transition['time_spent'] / 3600, 2)).' '.t('hours') ?></td>
<td><?= $this->dt->duration($transition['time_spent']) ?></td>
</tr>
<?php endforeach ?>
</table>

View File

@ -30,10 +30,10 @@
<?= $this->form->csrf() ?>
<?= $this->form->label(t('Start time'), 'start') ?>
<?= $this->form->select('start', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('start', $this->dt->getDayHours(), $values, $errors) ?>
<?= $this->form->label(t('End time'), 'end') ?>
<?= $this->form->select('end', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('end', $this->dt->getDayHours(), $values, $errors) ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>

View File

@ -42,10 +42,10 @@
<?= $this->form->checkbox('all_day', t('All day'), 1) ?>
<?= $this->form->label(t('Start time'), 'start') ?>
<?= $this->form->select('start', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('start', $this->dt->getDayHours(), $values, $errors) ?>
<?= $this->form->label(t('End time'), 'end') ?>
<?= $this->form->select('end', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('end', $this->dt->getDayHours(), $values, $errors) ?>
<?= $this->form->label(t('Comment'), 'comment') ?>
<?= $this->form->text('comment', $values, $errors) ?>

View File

@ -42,10 +42,10 @@
<?= $this->form->checkbox('all_day', t('All day'), 1) ?>
<?= $this->form->label(t('Start time'), 'start') ?>
<?= $this->form->select('start', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('start', $this->dt->getDayHours(), $values, $errors) ?>
<?= $this->form->label(t('End time'), 'end') ?>
<?= $this->form->select('end', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('end', $this->dt->getDayHours(), $values, $errors) ?>
<?= $this->form->label(t('Comment'), 'comment') ?>
<?= $this->form->text('comment', $values, $errors) ?>

View File

@ -13,7 +13,7 @@
</tr>
<?php foreach ($timetable as $slot): ?>
<tr>
<td><?= $this->datetime->getWeekDay($slot['day']) ?></td>
<td><?= $this->dt->getWeekDay($slot['day']) ?></td>
<td><?= $slot['start'] ?></td>
<td><?= $slot['end'] ?></td>
<td>
@ -32,13 +32,13 @@
<?= $this->form->csrf() ?>
<?= $this->form->label(t('Day'), 'day') ?>
<?= $this->form->select('day', $this->datetime->getWeekDays(), $values, $errors) ?>
<?= $this->form->select('day', $this->dt->getWeekDays(), $values, $errors) ?>
<?= $this->form->label(t('Start time'), 'start') ?>
<?= $this->form->select('start', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('start', $this->dt->getDayHours(), $values, $errors) ?>
<?= $this->form->label(t('End time'), 'end') ?>
<?= $this->form->select('end', $this->datetime->getDayHours(), $values, $errors) ?>
<?= $this->form->select('end', $this->dt->getDayHours(), $values, $errors) ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>

View File

@ -2,13 +2,13 @@
require_once __DIR__.'/Base.php';
use Helper\Datetime;
use Helper\Dt;
class DatetimeHelperTest extends Base
{
public function testAge()
{
$h = new Datetime($this->container);
$h = new Dt($this->container);
$this->assertEquals('&lt;15m', $h->age(0, 30));
$this->assertEquals('&lt;30m', $h->age(0, 1000));
@ -20,7 +20,7 @@ class DatetimeHelperTest extends Base
public function testGetDayHours()
{
$h = new Datetime($this->container);
$h = new Dt($this->container);
$slots = $h->getDayHours();
@ -36,7 +36,7 @@ class DatetimeHelperTest extends Base
public function testGetWeekDays()
{
$h = new Datetime($this->container);
$h = new Dt($this->container);
$slots = $h->getWeekDays();
@ -48,7 +48,7 @@ class DatetimeHelperTest extends Base
public function testGetWeekDay()
{
$h = new Datetime($this->container);
$h = new Dt($this->container);
$this->assertEquals('Monday', $h->getWeekDay(1));
$this->assertEquals('Sunday', $h->getWeekDay(7));