Remove ProjectAnalytic class

This commit is contained in:
Frederic Guillot 2016-01-16 17:01:56 -05:00
parent b77fecc7d9
commit 73ff5ec89b
10 changed files with 461 additions and 114 deletions

View File

@ -0,0 +1,112 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\Task;
/**
* Average Lead and Cycle Time
*
* @package analytic
* @author Frederic Guillot
*/
class AverageLeadCycleTimeAnalytic extends Base
{
/**
* Build report
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function build($project_id)
{
$stats = array(
'count' => 0,
'total_lead_time' => 0,
'total_cycle_time' => 0,
'avg_lead_time' => 0,
'avg_cycle_time' => 0,
);
foreach ($this->getTasks($project_id) as &$task) {
$stats['count']++;
$stats['total_lead_time'] += $this->calculateLeadTime($task);
$stats['total_cycle_time'] += $this->calculateCycleTime($task);
}
$stats['avg_lead_time'] = $this->calculateAverage($stats, 'total_lead_time');
$stats['avg_cycle_time'] = $this->calculateAverage($stats, 'total_cycle_time');
return $stats;
}
/**
* Calculate average
*
* @access private
* @param array &$stats
* @param string $field
* @return float
*/
private function calculateAverage(array &$stats, $field)
{
if ($stats['count'] > 0) {
return (int) ($stats[$field] / $stats['count']);
}
return 0;
}
/**
* Calculate lead time
*
* @access private
* @param array &$task
* @return integer
*/
private function calculateLeadTime(array &$task)
{
$end = $task['date_completed'] ?: time();
$start = $task['date_creation'];
return $end - $start;
}
/**
* Calculate cycle time
*
* @access private
* @param array &$task
* @return integer
*/
private function calculateCycleTime(array &$task)
{
if (empty($task['date_started'])) {
return 0;
}
$end = $task['date_completed'] ?: time();
$start = $task['date_started'];
return $end - $start;
}
/**
* Get the 1000 last created tasks
*
* @access private
* @return array
*/
private function getTasks($project_id)
{
return $this->db
->table(Task::TABLE)
->columns('date_completed', 'date_creation', 'date_started')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
}
}

View File

@ -0,0 +1,151 @@
<?php
namespace Kanboard\Analytic;
use Kanboard\Core\Base;
use Kanboard\Model\Task;
/**
* Average Time Spent by Column
*
* @package analytic
* @author Frederic Guillot
*/
class AverageTimeSpentColumnAnalytic extends Base
{
/**
* Build report
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function build($project_id)
{
$stats = $this->initialize($project_id);
$this->processTasks($stats, $project_id);
$this->calculateAverage($stats);
return $stats;
}
/**
* Initialize default values for each column
*
* @access private
* @param integer $project_id
* @return array
*/
private function initialize($project_id)
{
$stats = array();
$columns = $this->board->getColumnsList($project_id);
foreach ($columns as $column_id => $column_title) {
$stats[$column_id] = array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => $column_title,
);
}
return $stats;
}
/**
* Calculate time spent for each tasks for each columns
*
* @access private
* @param array $stats
* @param integer $project_id
*/
private function processTasks(array &$stats, $project_id)
{
foreach ($this->getTasks($project_id) as &$task) {
foreach ($this->getTaskTimeByColumns($task) as $column_id => $time_spent) {
if (isset($stats[$column_id])) {
$stats[$column_id]['count']++;
$stats[$column_id]['time_spent'] += $time_spent;
}
}
}
}
/**
* Calculate averages
*
* @access private
* @param array $stats
*/
private function calculateAverage(array &$stats)
{
foreach ($stats as &$column) {
$this->calculateColumnAverage($column);
}
}
/**
* Calculate column average
*
* @access private
* @param array $column
*/
private function calculateColumnAverage(array &$column)
{
if ($column['count'] > 0) {
$column['average'] = (int) ($column['time_spent'] / $column['count']);
}
}
/**
* Get time spent for each column for a given task
*
* @access private
* @param array $task
* @return array
*/
private function getTaskTimeByColumns(array &$task)
{
$columns = $this->transition->getTimeSpentByTask($task['id']);
if (! isset($columns[$task['column_id']])) {
$columns[$task['column_id']] = 0;
}
$columns[$task['column_id']] += $this->getTaskTimeSpentInCurrentColumn($task);
return $columns;
}
/**
* Calculate time spent of a task in the current column
*
* @access private
* @param array $task
*/
private function getTaskTimeSpentInCurrentColumn(array &$task)
{
$end = $task['date_completed'] ?: time();
return $end - $task['date_moved'];
}
/**
* Fetch the last 1000 tasks
*
* @access private
* @param integer $project_id
* @return array
*/
private function getTasks($project_id)
{
return $this->db
->table(Task::TABLE)
->columns('id', 'date_completed', 'date_moved', 'column_id')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
}
}

View File

@ -37,8 +37,6 @@ class Analytic extends Base
$project = $this->getProject();
$values = $this->request->getValues();
$this->projectDailyStats->updateTotals($project['id'], date('Y-m-d'));
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
$to = $this->request->getStringParam('to', date('Y-m-d'));
@ -53,7 +51,7 @@ class Analytic extends Base
'to' => $to,
),
'project' => $project,
'average' => $this->projectAnalytic->getAverageLeadAndCycleTime($project['id']),
'average' => $this->averageLeadCycleTimeAnalytic->build($project['id']),
'metrics' => $this->projectDailyStats->getRawMetrics($project['id'], $from, $to),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
@ -72,7 +70,7 @@ class Analytic extends Base
$this->response->html($this->layout('analytic/avg_time_columns', array(
'project' => $project,
'metrics' => $this->projectAnalytic->getAverageTimeSpentByColumn($project['id']),
'metrics' => $this->averageTimeSpentColumnAnalytic->build($project['id']),
'title' => t('Average time spent into each column for "%s"', $project['name']),
)));
}

View File

@ -13,6 +13,8 @@ use Pimple\Container;
* @property \Kanboard\Analytic\TaskDistributionAnalytic $taskDistributionAnalytic
* @property \Kanboard\Analytic\UserDistributionAnalytic $userDistributionAnalytic
* @property \Kanboard\Analytic\EstimatedTimeComparisonAnalytic $estimatedTimeComparisonAnalytic
* @property \Kanboard\Analytic\AverageLeadCycleTimeAnalytic $averageLeadCycleTimeAnalytic
* @property \Kanboard\Analytic\AverageTimeSpentColumnAnalytic $averageTimeSpentColumnAnalytic
* @property \Kanboard\Core\Action\ActionManager $actionManager
* @property \Kanboard\Core\Cache\MemoryCache $memoryCache
* @property \Kanboard\Core\Event\EventManager $eventManager

View File

@ -1074,8 +1074,8 @@ return array(
'You were mentioned in a comment on the task #%d' => 'Vous avez été mentionné dans un commentaire de la tâche n°%d',
'Mentioned' => 'Mentionné',
'Compare Estimated Time vs Actual Time' => 'Comparer le temps estimé et le temps actuel',
'Estimated hours: ' => 'Heures estimées :',
'Actual hours: ' => 'Heures actuelles :',
'Estimated hours: ' => 'Heures estimées : ',
'Actual hours: ' => 'Heures actuelles : ',
'Hours Spent' => 'Heures passées',
'Hours Estimated' => 'Heures estimées',
'Estimated Time' => 'Temps estimé',

View File

@ -1,106 +0,0 @@
<?php
namespace Kanboard\Model;
/**
* Project analytic model
*
* @package model
* @author Frederic Guillot
*/
class ProjectAnalytic extends Base
{
/**
* Get the average lead and cycle time
*
* @access public
* @param integer $project_id
* @return array
*/
public function getAverageLeadAndCycleTime($project_id)
{
$stats = array(
'count' => 0,
'total_lead_time' => 0,
'total_cycle_time' => 0,
'avg_lead_time' => 0,
'avg_cycle_time' => 0,
);
$tasks = $this->db
->table(Task::TABLE)
->columns('date_completed', 'date_creation', 'date_started')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
foreach ($tasks as &$task) {
$stats['count']++;
$stats['total_lead_time'] += ($task['date_completed'] ?: time()) - $task['date_creation'];
$stats['total_cycle_time'] += empty($task['date_started']) ? 0 : ($task['date_completed'] ?: time()) - $task['date_started'];
}
$stats['avg_lead_time'] = $stats['count'] > 0 ? (int) ($stats['total_lead_time'] / $stats['count']) : 0;
$stats['avg_cycle_time'] = $stats['count'] > 0 ? (int) ($stats['total_cycle_time'] / $stats['count']) : 0;
return $stats;
}
/**
* 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) {
if (isset($stats[$column_id])) {
$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'] = $stats[$column_id]['count'] > 0 ? (int) ($stats[$column_id]['time_spent'] / $stats[$column_id]['count']) : 0;
}
return $stats;
}
}

View File

@ -29,7 +29,7 @@ class ProjectDailyStats extends Base
{
$this->db->startTransaction();
$lead_cycle_time = $this->projectAnalytic->getAverageLeadAndCycleTime($project_id);
$lead_cycle_time = $this->averageLeadCycleTimeAnalytic->build($project_id);
$exists = $this->db->table(ProjectDailyStats::TABLE)
->eq('day', $date)

View File

@ -18,6 +18,8 @@ class ClassProvider implements ServiceProviderInterface
'TaskDistributionAnalytic',
'UserDistributionAnalytic',
'EstimatedTimeComparisonAnalytic',
'AverageLeadCycleTimeAnalytic',
'AverageTimeSpentColumnAnalytic',
),
'Model' => array(
'Action',
@ -39,7 +41,6 @@ class ClassProvider implements ServiceProviderInterface
'PasswordReset',
'Project',
'ProjectActivity',
'ProjectAnalytic',
'ProjectDuplication',
'ProjectDailyColumnStats',
'ProjectDailyStats',

View File

@ -0,0 +1,73 @@
<?php
require_once __DIR__.'/../Base.php';
use Kanboard\Model\TaskCreation;
use Kanboard\Model\Project;
use Kanboard\Model\Task;
use Kanboard\Analytic\AverageLeadCycleTimeAnalytic;
class AverageLeadCycleTimeAnalyticTest extends Base
{
public function testBuild()
{
$taskCreationModel = new TaskCreation($this->container);
$projectModel = new Project($this->container);
$averageLeadCycleTimeAnalytic = new AverageLeadCycleTimeAnalytic($this->container);
$now = time();
$this->assertEquals(1, $projectModel->create(array('name' => 'test1')));
$this->assertEquals(2, $projectModel->create(array('name' => 'test1')));
$this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
$this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
$this->assertEquals(3, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
$this->assertEquals(4, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
// LT=3600 CT=1800
$this->container['db']->table(Task::TABLE)->eq('id', 1)->update(array('date_completed' => $now + 3600, 'date_started' => $now + 1800));
// LT=1800 CT=900
$this->container['db']->table(Task::TABLE)->eq('id', 2)->update(array('date_completed' => $now + 1800, 'date_started' => $now + 900));
// LT=3600 CT=0
$this->container['db']->table(Task::TABLE)->eq('id', 3)->update(array('date_completed' => $now + 3600));
// LT=2*3600 CT=0
$this->container['db']->table(Task::TABLE)->eq('id', 4)->update(array('date_completed' => $now + 2 * 3600));
$stats = $averageLeadCycleTimeAnalytic->build(1);
$expected = array(
'count' => 4,
'total_lead_time' => 3600 + 1800 + 3600 + 2*3600,
'total_cycle_time' => 1800 + 900,
'avg_lead_time' => (3600 + 1800 + 3600 + 2*3600) / 4,
'avg_cycle_time' => (1800 + 900) / 4,
);
$this->assertEquals($expected, $stats);
}
public function testBuildWithNoTasks()
{
$taskCreationModel = new TaskCreation($this->container);
$projectModel = new Project($this->container);
$averageLeadCycleTimeAnalytic = new AverageLeadCycleTimeAnalytic($this->container);
$now = time();
$this->assertEquals(1, $projectModel->create(array('name' => 'test1')));
$this->assertEquals(2, $projectModel->create(array('name' => 'test1')));
$stats = $averageLeadCycleTimeAnalytic->build(1);
$expected = array(
'count' => 0,
'total_lead_time' => 0,
'total_cycle_time' => 0,
'avg_lead_time' => 0,
'avg_cycle_time' => 0,
);
$this->assertEquals($expected, $stats);
}
}

View File

@ -0,0 +1,116 @@
<?php
require_once __DIR__.'/../Base.php';
use Kanboard\Model\TaskCreation;
use Kanboard\Model\Project;
use Kanboard\Model\Transition;
use Kanboard\Model\Task;
use Kanboard\Model\TaskFinder;
use Kanboard\Analytic\AverageTimeSpentColumnAnalytic;
class AverageTimeSpentColumnAnalyticTest extends Base
{
public function testAverageWithNoTransitions()
{
$taskCreationModel = new TaskCreation($this->container);
$projectModel = new Project($this->container);
$averageLeadCycleTimeAnalytic = new AverageTimeSpentColumnAnalytic($this->container);
$now = time();
$this->assertEquals(1, $projectModel->create(array('name' => 'test1')));
$this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
$this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
$this->container['db']->table(Task::TABLE)->eq('id', 1)->update(array('date_completed' => $now + 3600));
$this->container['db']->table(Task::TABLE)->eq('id', 2)->update(array('date_completed' => $now + 1800));
$stats = $averageLeadCycleTimeAnalytic->build(1);
$expected = array(
1 => array(
'count' => 2,
'time_spent' => 3600+1800,
'average' => (int) ((3600+1800)/2),
'title' => 'Backlog',
),
2 => array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => 'Ready',
),
3 => array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => 'Work in progress',
),
4 => array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => 'Done',
)
);
$this->assertEquals($expected, $stats);
}
public function testAverageWithTransitions()
{
$transitionModel = new Transition($this->container);
$taskFinderModel = new TaskFinder($this->container);
$taskCreationModel = new TaskCreation($this->container);
$projectModel = new Project($this->container);
$averageLeadCycleTimeAnalytic = new AverageTimeSpentColumnAnalytic($this->container);
$now = time();
$this->assertEquals(1, $projectModel->create(array('name' => 'test1')));
$this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
$this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
$this->container['db']->table(Task::TABLE)->eq('id', 1)->update(array('date_completed' => $now + 3600));
$this->container['db']->table(Task::TABLE)->eq('id', 2)->update(array('date_completed' => $now + 1800));
foreach (array(1, 2) as $task_id) {
$task = $taskFinderModel->getById($task_id);
$task['task_id'] = $task['id'];
$task['date_moved'] = $now - 900;
$task['src_column_id'] = 3;
$task['dst_column_id'] = 1;
$this->assertTrue($transitionModel->save(1, $task));
}
$stats = $averageLeadCycleTimeAnalytic->build(1);
$expected = array(
1 => array(
'count' => 2,
'time_spent' => 3600+1800,
'average' => (int) ((3600+1800)/2),
'title' => 'Backlog',
),
2 => array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => 'Ready',
),
3 => array(
'count' => 2,
'time_spent' => 1800,
'average' => 900,
'title' => 'Work in progress',
),
4 => array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => 'Done',
)
);
$this->assertEquals($expected, $stats);
}
}