diff --git a/app/Controller/Activity.php b/app/Controller/Activity.php
index 2276b3b88..234e4be46 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 dc83f7b16..b6e4845f2 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 e4f225b01..64eaed238 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
similarity index 83%
rename from app/Helper/Datetime.php
rename to app/Helper/Dt.php
index 74ea9bdd7..be595605e 100644
--- a/app/Helper/Datetime.php
+++ b/app/Helper/Dt.php
@@ -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
diff --git a/app/Model/TaskAnalytic.php b/app/Model/TaskAnalytic.php
new file mode 100644
index 000000000..41579c7df
--- /dev/null
+++ b/app/Model/TaskAnalytic.php
@@ -0,0 +1,74 @@
+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 cb759e4a4..959b6aca2 100644
--- a/app/Model/Transition.php
+++ b/app/Model/Transition.php
@@ -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
*
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index 1fa0d0ef1..609f58244 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
similarity index 100%
rename from app/Template/task/activity.php
rename to app/Template/activity/task.php
diff --git a/app/Template/board/task_private.php b/app/Template/board/task_private.php
index 87121f2c9..3f4010ea4 100644
--- a/app/Template/board/task_private.php
+++ b/app/Template/board/task_private.php
@@ -43,8 +43,8 @@
- = $this->datetime->age($task['date_creation']) ?>
- = $this->datetime->age($task['date_moved']) ?>
+ = $this->dt->age($task['date_creation']) ?>
+ = $this->dt->age($task['date_moved']) ?>
= t('Closed') ?>
diff --git a/app/Template/subtask/show.php b/app/Template/subtask/show.php
index b91e830f7..cc82a74eb 100644
--- a/app/Template/subtask/show.php
+++ b/app/Template/subtask/show.php
@@ -48,7 +48,7 @@
= $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']) ?>)
= $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 000000000..dbee0e9cc
--- /dev/null
+++ b/app/Template/task/analytics.php
@@ -0,0 +1,32 @@
+
+
+
+
+ = t('Lead time: ').''.$this->dt->duration($lead_time) ?>
+ = t('Cycle time: ').''.$this->dt->duration($cycle_time) ?>
+
+
+
+= t('Average time spent for each column') ?>
+
+
+ = t('Column') ?>
+ = t('Average time spent') ?>
+
+
+
+ = $this->e($column['title']) ?>
+ = $this->dt->duration($column['time_spent']) ?>
+
+
+
+
+
+
+ = t('The lead time is the time between the task creation and the completion.') ?>
+ = t('The cycle time is the time between the start date and the completion.') ?>
+ = t('If the task is not closed the current time is used.') ?>
+
+
\ No newline at end of file
diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php
index bb137ac91..8b0f3c6e7 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'])) ?>
- = $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'])) ?>
= $this->url->link(t('Transitions'), 'task', 'transitions', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
+
+ = $this->url->link(t('Analytics'), 'task', 'analytics', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
+
0 || $task['time_spent'] > 0): ?>
= $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 6455fd667..2ca2387f3 100644
--- a/app/Template/task/transitions.php
+++ b/app/Template/task/transitions.php
@@ -19,7 +19,7 @@
= $this->e($transition['src_column']) ?>
= $this->e($transition['dst_column']) ?>
= $this->url->link($this->e($transition['name'] ?: $transition['username']), 'user', 'show', array('user_id' => $transition['user_id'])) ?>
- = n(round($transition['time_spent'] / 3600, 2)).' '.t('hours') ?>
+ = $this->dt->duration($transition['time_spent']) ?>
diff --git a/app/Template/timetable_day/index.php b/app/Template/timetable_day/index.php
index d2877816c..386ceec22 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) ?>
diff --git a/app/Template/timetable_extra/index.php b/app/Template/timetable_extra/index.php
index d3224ae68..e9982335f 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 75e02dbd7..615c2b8d1 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 552e93023..d58c6cfbf 100644
--- a/app/Template/timetable_week/index.php
+++ b/app/Template/timetable_week/index.php
@@ -13,7 +13,7 @@
- = $this->datetime->getWeekDay($slot['day']) ?>
+ = $this->dt->getWeekDay($slot['day']) ?>
= $slot['start'] ?>
= $slot['end'] ?>
@@ -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) ?>
diff --git a/tests/units/DatetimeHelperTest.php b/tests/units/DatetimeHelperTest.php
index 216cf34c1..21d452dd2 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));