diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-07-05 16:07:21 -0400 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-07-05 16:07:21 -0400 |
commit | bb8b4c0e36afc05ff5b0cb3ac6465351a696b001 (patch) | |
tree | e7499d04cfe32ed429413c2bbe0c81c33700d36a | |
parent | 67b9a56469b406b44cd1baad4445ddb6d707794f (diff) |
Add task analytics
-rw-r--r-- | app/Controller/Activity.php | 16 | ||||
-rw-r--r-- | app/Controller/Task.php | 11 | ||||
-rw-r--r-- | app/Core/Helper.php | 2 | ||||
-rw-r--r-- | app/Helper/Dt.php (renamed from app/Helper/Datetime.php) | 18 | ||||
-rw-r--r-- | app/Model/TaskAnalytic.php | 74 | ||||
-rw-r--r-- | app/Model/Transition.php | 16 | ||||
-rw-r--r-- | app/ServiceProvider/ClassProvider.php | 1 | ||||
-rw-r--r-- | app/Template/activity/task.php (renamed from app/Template/task/activity.php) | 0 | ||||
-rw-r--r-- | app/Template/board/task_private.php | 4 | ||||
-rw-r--r-- | app/Template/subtask/show.php | 2 | ||||
-rw-r--r-- | app/Template/task/analytics.php | 32 | ||||
-rw-r--r-- | app/Template/task/sidebar.php | 5 | ||||
-rw-r--r-- | app/Template/task/transitions.php | 2 | ||||
-rw-r--r-- | app/Template/timetable_day/index.php | 4 | ||||
-rw-r--r-- | app/Template/timetable_extra/index.php | 4 | ||||
-rw-r--r-- | app/Template/timetable_off/index.php | 4 | ||||
-rw-r--r-- | app/Template/timetable_week/index.php | 8 | ||||
-rw-r--r-- | tests/units/DatetimeHelperTest.php | 10 |
18 files changed, 186 insertions, 27 deletions
diff --git a/app/Controller/Activity.php b/app/Controller/Activity.php index 2276b3b8..234e4be4 100644 --- a/app/Controller/Activity.php +++ b/app/Controller/Activity.php @@ -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']), + ))); + } } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index dc83f7b1..b6e4845f 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -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), ))); } diff --git a/app/Core/Helper.php b/app/Core/Helper.php index e4f225b0..64eaed23 100644 --- a/app/Core/Helper.php +++ b/app/Core/Helper.php @@ -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 diff --git a/app/Helper/Datetime.php b/app/Helper/Dt.php index 74ea9bdd..be595605 100644 --- a/app/Helper/Datetime.php +++ b/app/Helper/Dt.php @@ -2,15 +2,31 @@ 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 * diff --git a/app/Model/TaskAnalytic.php b/app/Model/TaskAnalytic.php new file mode 100644 index 00000000..41579c7d --- /dev/null +++ b/app/Model/TaskAnalytic.php @@ -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; + } +} diff --git a/app/Model/Transition.php b/app/Model/Transition.php index cb759e4a..959b6aca 100644 --- a/app/Model/Transition.php +++ b/app/Model/Transition.php @@ -39,6 +39,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 * * @access public diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 1fa0d0ef..609f5824 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -42,6 +42,7 @@ class ClassProvider implements ServiceProviderInterface 'SubtaskTimeTracking', 'Swimlane', 'Task', + 'TaskAnalytic', 'TaskCreation', 'TaskDuplication', 'TaskExport', diff --git a/app/Template/task/activity.php b/app/Template/activity/task.php index cc4aad03..cc4aad03 100644 --- a/app/Template/task/activity.php +++ b/app/Template/activity/task.php diff --git a/app/Template/board/task_private.php b/app/Template/board/task_private.php index 87121f2c..3f4010ea 100644 --- a/app/Template/board/task_private.php +++ b/app/Template/board/task_private.php @@ -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> diff --git a/app/Template/subtask/show.php b/app/Template/subtask/show.php index b91e830f..cc82a74e 100644 --- a/app/Template/subtask/show.php +++ b/app/Template/subtask/show.php @@ -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'])) ?> diff --git a/app/Template/task/analytics.php b/app/Template/task/analytics.php new file mode 100644 index 00000000..dbee0e9c --- /dev/null +++ b/app/Template/task/analytics.php @@ -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>
\ No newline at end of file diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index bb137ac9..8b0f3c6e 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -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'])) ?> diff --git a/app/Template/task/transitions.php b/app/Template/task/transitions.php index 6455fd66..2ca2387f 100644 --- a/app/Template/task/transitions.php +++ b/app/Template/task/transitions.php @@ -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> diff --git a/app/Template/timetable_day/index.php b/app/Template/timetable_day/index.php index d2877816..386ceec2 100644 --- a/app/Template/timetable_day/index.php +++ b/app/Template/timetable_day/index.php @@ -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"/> diff --git a/app/Template/timetable_extra/index.php b/app/Template/timetable_extra/index.php index d3224ae6..e9982335 100644 --- a/app/Template/timetable_extra/index.php +++ b/app/Template/timetable_extra/index.php @@ -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) ?> diff --git a/app/Template/timetable_off/index.php b/app/Template/timetable_off/index.php index 75e02dbd..615c2b8d 100644 --- a/app/Template/timetable_off/index.php +++ b/app/Template/timetable_off/index.php @@ -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) ?> diff --git a/app/Template/timetable_week/index.php b/app/Template/timetable_week/index.php index 552e9302..d58c6cfb 100644 --- a/app/Template/timetable_week/index.php +++ b/app/Template/timetable_week/index.php @@ -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"/> diff --git a/tests/units/DatetimeHelperTest.php b/tests/units/DatetimeHelperTest.php index 216cf34c..21d452dd 100644 --- a/tests/units/DatetimeHelperTest.php +++ b/tests/units/DatetimeHelperTest.php @@ -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('<15m', $h->age(0, 30)); $this->assertEquals('<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)); |