Add lead and cycle time for projects

This commit is contained in:
Frederic Guillot
2015-07-06 21:34:57 -04:00
parent 663a1c20e6
commit 08259d4f20
26 changed files with 461 additions and 95 deletions

View File

@@ -7,13 +7,13 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
class ProjectDailySummaryExport extends Base class ProjectDailyColumnStatsExport extends Base
{ {
protected function configure() protected function configure()
{ {
$this $this
->setName('export:daily-project-summary') ->setName('export:daily-project-column-stats')
->setDescription('Daily project summary CSV export (number of tasks per column and per day)') ->setDescription('Daily project column stats CSV export (number of tasks per column and per day)')
->addArgument('project_id', InputArgument::REQUIRED, 'Project id') ->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)') ->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)'); ->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
@@ -21,7 +21,7 @@ class ProjectDailySummaryExport extends Base
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$data = $this->projectDailySummary->getAggregatedMetrics( $data = $this->projectDailyColumnStats->getAggregatedMetrics(
$input->getArgument('project_id'), $input->getArgument('project_id'),
$input->getArgument('start_date'), $input->getArgument('start_date'),
$input->getArgument('end_date') $input->getArgument('end_date')

View File

@@ -6,13 +6,13 @@ use Model\Project;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
class ProjectDailySummaryCalculation extends Base class ProjectDailyStatsCalculation extends Base
{ {
protected function configure() protected function configure()
{ {
$this $this
->setName('projects:daily-summary') ->setName('projects:daily-stats')
->setDescription('Calculate daily summary data for all projects'); ->setDescription('Calculate daily statistics for all projects');
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
@@ -21,7 +21,8 @@ class ProjectDailySummaryCalculation extends Base
foreach ($projects as $project) { foreach ($projects as $project) {
$output->writeln('Run calculation for '.$project['name']); $output->writeln('Run calculation for '.$project['name']);
$this->projectDailySummary->updateTotals($project['id'], date('Y-m-d')); $this->projectDailyColumnStats->updateTotals($project['id'], date('Y-m-d'));
$this->projectDailyStats->updateTotals($project['id'], date('Y-m-d'));
} }
} }
} }

View File

@@ -26,6 +26,40 @@ class Analytic extends Base
return $this->template->layout('analytic/layout', $params); return $this->template->layout('analytic/layout', $params);
} }
/**
* Show average Lead and Cycle time
*
* @access public
*/
public function leadAndCycleTime()
{
$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'));
if (! empty($values)) {
$from = $values['from'];
$to = $values['to'];
}
$this->response->html($this->layout('analytic/lead_cycle_time', array(
'values' => array(
'from' => $from,
'to' => $to,
),
'project' => $project,
'average' => $this->projectAnalytic->getAverageLeadAndCycleTime($project['id']),
'metrics' => $this->projectDailyStats->getRawMetrics($project['id'], $from, $to),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'title' => t('Lead and Cycle time for "%s"', $project['name']),
)));
}
/** /**
* Show average time spent by column * Show average time spent by column
* *
@@ -104,6 +138,8 @@ class Analytic extends Base
$project = $this->getProject(); $project = $this->getProject();
$values = $this->request->getValues(); $values = $this->request->getValues();
$this->projectDailyColumnStats->updateTotals($project['id'], date('Y-m-d'));
$from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week'))); $from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
$to = $this->request->getStringParam('to', date('Y-m-d')); $to = $this->request->getStringParam('to', date('Y-m-d'));
@@ -112,7 +148,7 @@ class Analytic extends Base
$to = $values['to']; $to = $values['to'];
} }
$display_graph = $this->projectDailySummary->countDays($project['id'], $from, $to) >= 2; $display_graph = $this->projectDailyColumnStats->countDays($project['id'], $from, $to) >= 2;
$this->response->html($this->layout($template, array( $this->response->html($this->layout($template, array(
'values' => array( 'values' => array(
@@ -120,7 +156,7 @@ class Analytic extends Base
'to' => $to, 'to' => $to,
), ),
'display_graph' => $display_graph, 'display_graph' => $display_graph,
'metrics' => $display_graph ? $this->projectDailySummary->getAggregatedMetrics($project['id'], $from, $to, $column) : array(), 'metrics' => $display_graph ? $this->projectDailyColumnStats->getAggregatedMetrics($project['id'], $from, $to, $column) : array(),
'project' => $project, 'project' => $project,
'date_format' => $this->config->get('application_date_format'), 'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(), 'date_formats' => $this->dateParser->getAvailableFormats(),

View File

@@ -70,7 +70,7 @@ class Export extends Base
*/ */
public function summary() public function summary()
{ {
$this->common('projectDailySummary', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export')); $this->common('ProjectDailyColumnStats', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export'));
} }
/** /**

View File

@@ -48,7 +48,8 @@ use Pimple\Container;
* @property \Model\ProjectActivity $projectActivity * @property \Model\ProjectActivity $projectActivity
* @property \Model\ProjectAnalytic $projectAnalytic * @property \Model\ProjectAnalytic $projectAnalytic
* @property \Model\ProjectDuplication $projectDuplication * @property \Model\ProjectDuplication $projectDuplication
* @property \Model\ProjectDailySummary $projectDailySummary * @property \Model\ProjectDailyColumnStats $projectDailyColumnStats
* @property \Model\ProjectDailyStats $projectDailyStats
* @property \Model\ProjectIntegration $projectIntegration * @property \Model\ProjectIntegration $projectIntegration
* @property \Model\ProjectPermission $projectPermission * @property \Model\ProjectPermission $projectPermission
* @property \Model\Subtask $subtask * @property \Model\Subtask $subtask

View File

@@ -88,6 +88,43 @@ class ProjectAnalytic extends Base
return array_values($metrics); return array_values($metrics);
} }
/**
* 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'] = (int) ($stats['total_lead_time'] / $stats['count']);
$stats['avg_cycle_time'] = (int) ($stats['total_cycle_time'] / $stats['count']);
return $stats;
}
/** /**
* Get the average time spent into each column * Get the average time spent into each column
* *

View File

@@ -3,22 +3,22 @@
namespace Model; namespace Model;
/** /**
* Project daily summary * Project Daily Column Stats
* *
* @package model * @package model
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class ProjectDailySummary extends Base class ProjectDailyColumnStats extends Base
{ {
/** /**
* SQL table name * SQL table name
* *
* @var string * @var string
*/ */
const TABLE = 'project_daily_summaries'; const TABLE = 'project_daily_column_stats';
/** /**
* Update daily totals for the project * Update daily totals for the project and foreach column
* *
* "total" is the number open of tasks in the column * "total" is the number open of tasks in the column
* "score" is the sum of tasks score in the column * "score" is the sum of tasks score in the column
@@ -38,7 +38,7 @@ class ProjectDailySummary extends Base
// This call will fail if the record already exists // This call will fail if the record already exists
// (cross database driver hack for INSERT..ON DUPLICATE KEY UPDATE) // (cross database driver hack for INSERT..ON DUPLICATE KEY UPDATE)
$db->table(ProjectDailySummary::TABLE)->insert(array( $db->table(ProjectDailyColumnStats::TABLE)->insert(array(
'day' => $date, 'day' => $date,
'project_id' => $project_id, 'project_id' => $project_id,
'column_id' => $column_id, 'column_id' => $column_id,
@@ -46,7 +46,7 @@ class ProjectDailySummary extends Base
'score' => 0, 'score' => 0,
)); ));
$db->table(ProjectDailySummary::TABLE) $db->table(ProjectDailyColumnStats::TABLE)
->eq('project_id', $project_id) ->eq('project_id', $project_id)
->eq('column_id', $column_id) ->eq('column_id', $column_id)
->eq('day', $date) ->eq('day', $date)
@@ -95,19 +95,19 @@ class ProjectDailySummary extends Base
*/ */
public function getRawMetrics($project_id, $from, $to) public function getRawMetrics($project_id, $from, $to)
{ {
return $this->db->table(ProjectDailySummary::TABLE) return $this->db->table(ProjectDailyColumnStats::TABLE)
->columns( ->columns(
ProjectDailySummary::TABLE.'.column_id', ProjectDailyColumnStats::TABLE.'.column_id',
ProjectDailySummary::TABLE.'.day', ProjectDailyColumnStats::TABLE.'.day',
ProjectDailySummary::TABLE.'.total', ProjectDailyColumnStats::TABLE.'.total',
ProjectDailySummary::TABLE.'.score', ProjectDailyColumnStats::TABLE.'.score',
Board::TABLE.'.title AS column_title' Board::TABLE.'.title AS column_title'
) )
->join(Board::TABLE, 'id', 'column_id') ->join(Board::TABLE, 'id', 'column_id')
->eq(ProjectDailySummary::TABLE.'.project_id', $project_id) ->eq(ProjectDailyColumnStats::TABLE.'.project_id', $project_id)
->gte('day', $from) ->gte('day', $from)
->lte('day', $to) ->lte('day', $to)
->asc(ProjectDailySummary::TABLE.'.day') ->asc(ProjectDailyColumnStats::TABLE.'.day')
->findAll(); ->findAll();
} }
@@ -122,17 +122,17 @@ class ProjectDailySummary extends Base
*/ */
public function getRawMetricsByDay($project_id, $from, $to) public function getRawMetricsByDay($project_id, $from, $to)
{ {
return $this->db->table(ProjectDailySummary::TABLE) return $this->db->table(ProjectDailyColumnStats::TABLE)
->columns( ->columns(
ProjectDailySummary::TABLE.'.day', ProjectDailyColumnStats::TABLE.'.day',
'SUM('.ProjectDailySummary::TABLE.'.total) AS total', 'SUM('.ProjectDailyColumnStats::TABLE.'.total) AS total',
'SUM('.ProjectDailySummary::TABLE.'.score) AS score' 'SUM('.ProjectDailyColumnStats::TABLE.'.score) AS score'
) )
->eq(ProjectDailySummary::TABLE.'.project_id', $project_id) ->eq(ProjectDailyColumnStats::TABLE.'.project_id', $project_id)
->gte('day', $from) ->gte('day', $from)
->lte('day', $to) ->lte('day', $to)
->asc(ProjectDailySummary::TABLE.'.day') ->asc(ProjectDailyColumnStats::TABLE.'.day')
->groupBy(ProjectDailySummary::TABLE.'.day') ->groupBy(ProjectDailyColumnStats::TABLE.'.day')
->findAll(); ->findAll();
} }
@@ -160,7 +160,7 @@ class ProjectDailySummary extends Base
$aggregates = array(); $aggregates = array();
// Fetch metrics for the project // Fetch metrics for the project
$records = $this->db->table(ProjectDailySummary::TABLE) $records = $this->db->table(ProjectDailyColumnStats::TABLE)
->eq('project_id', $project_id) ->eq('project_id', $project_id)
->gte('day', $from) ->gte('day', $from)
->lte('day', $to) ->lte('day', $to)

View File

@@ -0,0 +1,72 @@
<?php
namespace Model;
/**
* Project Daily Stats
*
* @package model
* @author Frederic Guillot
*/
class ProjectDailyStats extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_daily_stats';
/**
* Update daily totals for the project
*
* @access public
* @param integer $project_id Project id
* @param string $date Record date (YYYY-MM-DD)
* @return boolean
*/
public function updateTotals($project_id, $date)
{
$lead_cycle_time = $this->projectAnalytic->getAverageLeadAndCycleTime($project_id);
return $this->db->transaction(function($db) use ($project_id, $date, $lead_cycle_time) {
// This call will fail if the record already exists
// (cross database driver hack for INSERT..ON DUPLICATE KEY UPDATE)
$db->table(ProjectDailyStats::TABLE)->insert(array(
'day' => $date,
'project_id' => $project_id,
'avg_lead_time' => 0,
'avg_cycle_time' => 0,
));
$db->table(ProjectDailyStats::TABLE)
->eq('project_id', $project_id)
->eq('day', $date)
->update(array(
'avg_lead_time' => $lead_cycle_time['avg_lead_time'],
'avg_cycle_time' => $lead_cycle_time['avg_cycle_time'],
));
});
}
/**
* Get raw metrics for the project within a data range
*
* @access public
* @param integer $project_id Project id
* @param string $from Start date (ISO format YYYY-MM-DD)
* @param string $to End date
* @return array
*/
public function getRawMetrics($project_id, $from, $to)
{
return $this->db->table(self::TABLE)
->columns('day', 'avg_lead_time', 'avg_cycle_time')
->eq(self::TABLE.'.project_id', $project_id)
->gte('day', $from)
->lte('day', $to)
->asc(self::TABLE.'.day')
->findAll();
}
}

View File

@@ -6,7 +6,26 @@ use PDO;
use Core\Security; use Core\Security;
use Model\Link; use Model\Link;
const VERSION = 78; const VERSION = 79;
function version_79($pdo)
{
$pdo->exec("
CREATE TABLE project_daily_stats (
id INT NOT NULL AUTO_INCREMENT,
day CHAR(10) NOT NULL,
project_id INT NOT NULL,
avg_lead_time INT NOT NULL DEFAULT 0,
avg_cycle_time INT NOT NULL DEFAULT 0,
PRIMARY KEY(id),
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
) ENGINE=InnoDB CHARSET=utf8
");
$pdo->exec('CREATE UNIQUE INDEX project_daily_stats_idx ON project_daily_stats(day, project_id)');
$pdo->exec('RENAME TABLE project_daily_summaries TO project_daily_column_stats');
}
function version_78($pdo) function version_78($pdo)
{ {

View File

@@ -6,7 +6,25 @@ use PDO;
use Core\Security; use Core\Security;
use Model\Link; use Model\Link;
const VERSION = 58; const VERSION = 59;
function version_59($pdo)
{
$pdo->exec("
CREATE TABLE project_daily_stats (
id SERIAL PRIMARY KEY,
day CHAR(10) NOT NULL,
project_id INTEGER NOT NULL,
avg_lead_time INTEGER NOT NULL DEFAULT 0,
avg_cycle_time INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
)
");
$pdo->exec('CREATE UNIQUE INDEX project_daily_stats_idx ON project_daily_stats(day, project_id)');
$pdo->exec('ALTER TABLE project_daily_summaries RENAME TO project_daily_column_stats');
}
function version_58($pdo) function version_58($pdo)
{ {

View File

@@ -6,7 +6,25 @@ use Core\Security;
use PDO; use PDO;
use Model\Link; use Model\Link;
const VERSION = 74; const VERSION = 75;
function version_75($pdo)
{
$pdo->exec("
CREATE TABLE project_daily_stats (
id INTEGER PRIMARY KEY,
day TEXT NOT NULL,
project_id INTEGER NOT NULL,
avg_lead_time INTEGER NOT NULL DEFAULT 0,
avg_cycle_time INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
)
");
$pdo->exec('CREATE UNIQUE INDEX project_daily_stats_idx ON project_daily_stats(day, project_id)');
$pdo->exec('ALTER TABLE project_daily_summaries RENAME TO project_daily_column_stats');
}
function version_74($pdo) function version_74($pdo)
{ {

View File

@@ -33,7 +33,8 @@ class ClassProvider implements ServiceProviderInterface
'ProjectActivity', 'ProjectActivity',
'ProjectAnalytic', 'ProjectAnalytic',
'ProjectDuplication', 'ProjectDuplication',
'ProjectDailySummary', 'ProjectDailyColumnStats',
'ProjectDailyStats',
'ProjectIntegration', 'ProjectIntegration',
'ProjectPermission', 'ProjectPermission',
'Subtask', 'Subtask',

View File

@@ -22,7 +22,8 @@ class ProjectDailySummarySubscriber extends \Core\Base implements EventSubscribe
public function execute(TaskEvent $event) public function execute(TaskEvent $event)
{ {
if (isset($event['project_id'])) { if (isset($event['project_id'])) {
$this->projectDailySummary->updateTotals($event['project_id'], date('Y-m-d')); $this->projectDailyColumnStats->updateTotals($event['project_id'], date('Y-m-d'));
$this->projectDailyStats->updateTotals($event['project_id'], date('Y-m-d'));
} }
} }
} }

View File

@@ -0,0 +1,42 @@
<div class="page-header">
<h2><?= t('Average Lead and Cycle time') ?></h2>
</div>
<div class="listing">
<ul>
<li><?= t('Average lead time: ').'<strong>'.$this->dt->duration($average['avg_lead_time']) ?></strong></li>
<li><?= t('Average cycle time: ').'<strong>'.$this->dt->duration($average['avg_cycle_time']) ?></strong></li>
</ul>
</div>
<?php if (empty($metrics)): ?>
<p class="alert"><?= t('Not enough data to show the graph.') ?></p>
<?php else: ?>
<section id="analytic-lead-cycle-time">
<div id="chart" data-metrics='<?= json_encode($metrics) ?>' data-label-cycle="<?= t('Cycle Time') ?>" data-label-lead="<?= t('Lead Time') ?>"></div>
<form method="post" class="form-inline" action="<?= $this->url->href('analytic', 'leadAndCycleTime', array('project_id' => $project['id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<div class="form-inline-group">
<?= $this->form->label(t('Start Date'), 'from') ?>
<?= $this->form->text('from', $values, array(), array('required', 'placeholder="'.$this->text->in($date_format, $date_formats).'"'), 'form-date') ?>
</div>
<div class="form-inline-group">
<?= $this->form->label(t('End Date'), 'to') ?>
<?= $this->form->text('to', $values, array(), array('required', 'placeholder="'.$this->text->in($date_format, $date_formats).'"'), 'form-date') ?>
</div>
<div class="form-inline-group">
<input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/>
</div>
</form>
<p class="alert alert-info">
<?= t('This chart show the average lead and cycle time for the last %d tasks over the time.', 1000) ?>
</p>
</section>
<?php endif ?>

View File

@@ -16,5 +16,8 @@
<li> <li>
<?= $this->url->link(t('Average time into each column'), 'analytic', 'averageTimeByColumn', array('project_id' => $project['id'])) ?> <?= $this->url->link(t('Average time into each column'), 'analytic', 'averageTimeByColumn', array('project_id' => $project['id'])) ?>
</li> </li>
<li>
<?= $this->url->link(t('Lead and cycle time'), 'analytic', 'leadAndCycleTime', array('project_id' => $project['id'])) ?>
</li>
</ul> </ul>
</div> </div>

File diff suppressed because one or more lines are too long

View File

@@ -243,6 +243,51 @@
}); });
} }
// Draw lead and cycle time for the project
function drawLeadAndCycleTime()
{
var metrics = $("#chart").data("metrics");
var cycle = [$("#chart").data("label-cycle")];
var lead = [$("#chart").data("label-lead")];
var categories = [];
var types = {};
types[$("#chart").data("label-cycle")] = 'area';
types[$("#chart").data("label-lead")] = 'area-spline';
var colors = {};
colors[$("#chart").data("label-lead")] = '#afb42b';
colors[$("#chart").data("label-cycle")] = '#4e342e';
for (var i = 0; i < metrics.length; i++) {
cycle.push(parseInt(metrics[i].avg_cycle_time));
lead.push(parseInt(metrics[i].avg_lead_time));
categories.push(metrics[i].day);
}
c3.generate({
data: {
columns: [
lead,
cycle
],
types: types,
colors: colors
},
axis: {
x: {
type: 'category',
categories: categories
},
y: {
tick: {
format: formatDuration
}
}
}
});
}
function formatDuration(d) function formatDuration(d)
{ {
if (d >= 86400) { if (d >= 86400) {
@@ -281,6 +326,9 @@
else if (Kanboard.Exists("analytic-task-time-column")) { else if (Kanboard.Exists("analytic-task-time-column")) {
drawTaskTimeColumn(); drawTaskTimeColumn();
} }
else if (Kanboard.Exists("analytic-lead-cycle-time")) {
drawLeadAndCycleTime();
}
}); });
})(); })();

View File

@@ -7,7 +7,7 @@
"eluceo/ical": "*", "eluceo/ical": "*",
"erusev/parsedown" : "1.5.3", "erusev/parsedown" : "1.5.3",
"fabiang/xmpp" : "0.6.1", "fabiang/xmpp" : "0.6.1",
"fguillot/json-rpc" : "dev-master", "fguillot/json-rpc" : "1.0.0",
"fguillot/picodb" : "1.0.0", "fguillot/picodb" : "1.0.0",
"fguillot/simpleLogger" : "0.0.2", "fguillot/simpleLogger" : "0.0.2",
"fguillot/simple-validator" : "0.0.3", "fguillot/simple-validator" : "0.0.3",

13
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "1ef3f084b6c8651977b1bbc84d86cb69", "hash": "0048471872ea99cd30c53c0398c7d9f2",
"packages": [ "packages": [
{ {
"name": "christian-riesen/base32", "name": "christian-riesen/base32",
@@ -260,16 +260,16 @@
}, },
{ {
"name": "fguillot/json-rpc", "name": "fguillot/json-rpc",
"version": "dev-master", "version": "v1.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fguillot/JsonRPC.git", "url": "https://github.com/fguillot/JsonRPC.git",
"reference": "ac3ddcf8f74777d72b8044d6d128f41aabe292f2" "reference": "5a11f1414780a200f09b78d20ab72b5cee4faa95"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/ac3ddcf8f74777d72b8044d6d128f41aabe292f2", "url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/5a11f1414780a200f09b78d20ab72b5cee4faa95",
"reference": "ac3ddcf8f74777d72b8044d6d128f41aabe292f2", "reference": "5a11f1414780a200f09b78d20ab72b5cee4faa95",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -292,7 +292,7 @@
], ],
"description": "Simple Json-RPC client/server library that just works", "description": "Simple Json-RPC client/server library that just works",
"homepage": "https://github.com/fguillot/JsonRPC", "homepage": "https://github.com/fguillot/JsonRPC",
"time": "2015-07-01 19:26:37" "time": "2015-07-01 19:50:31"
}, },
{ {
"name": "fguillot/picodb", "name": "fguillot/picodb",
@@ -819,7 +819,6 @@
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": { "stability-flags": {
"fguillot/json-rpc": 20,
"swiftmailer/swiftmailer": 0, "swiftmailer/swiftmailer": 0,
"symfony/console": 0 "symfony/console": 0
}, },

View File

@@ -37,6 +37,6 @@ Kanboard use the complexity or story point to generate this diagram.
Don't forget to run the daily job for stats calculation Don't forget to run the daily job for stats calculation
------------------------------------------------------- -------------------------------------------------------
To generate accurate analytics data, you should run the daily cronjob **project daily summaries** just before midnight. To generate accurate analytics data, you should run the daily cronjob **project daily statistics** just before midnight.
[Read the documentation about Kanboard CLI](http://kanboard.net/documentation/cli) [Read the documentation about Kanboard CLI](cli.markdown)

View File

@@ -13,36 +13,35 @@ Usage
- Run the command `./kanboard` - Run the command `./kanboard`
```bash ```bash
$ ./kanboard
Kanboard version master Kanboard version master
Usage: Usage:
command [options] [arguments] command [options] [arguments]
Options: Options:
--help (-h) Display this help message -h, --help Display this help message
--quiet (-q) Do not output any message -q, --quiet Do not output any message
--verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug -V, --version Display this application version
--version (-V) Display this application version --ansi Force ANSI output
--ansi Force ANSI output --no-ansi Disable ANSI output
--no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question
--no-interaction (-n) Do not ask any interactive question -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands: Available commands:
help Displays help for a command help Displays help for a command
list Lists commands list Lists commands
export export
export:daily-project-summary Daily project summary CSV export (number of tasks per column and per day) export:daily-project-column-stats Daily project column stats CSV export (number of tasks per column and per day)
export:subtasks Subtasks CSV export export:subtasks Subtasks CSV export
export:tasks Tasks CSV export export:tasks Tasks CSV export
export:transitions Task transitions CSV export export:transitions Task transitions CSV export
locale locale
locale:compare Compare application translations with the fr_FR locale locale:compare Compare application translations with the fr_FR locale
locale:sync Synchronize all translations based on the fr_FR locale locale:sync Synchronize all translations based on the fr_FR locale
notification notification
notification:overdue-tasks Send notifications for overdue tasks notification:overdue-tasks Send notifications for overdue tasks
projects projects
projects:daily-summary Calculate daily summary data for all projects projects:daily-stats Calculate daily statistics for all projects
``` ```
Available commands Available commands
@@ -97,13 +96,13 @@ Example:
The exported data will be printed on the standard output: The exported data will be printed on the standard output:
```bash ```bash
./kanboard export:daily-project-summary <project_id> <start_date> <end_date> ./kanboard export:daily-project-column-stats <project_id> <start_date> <end_date>
``` ```
Example: Example:
```bash ```bash
./kanboard export:daily-project-summary 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv ./kanboard export:daily-project-column-stats 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv
``` ```
### Send notifications for overdue tasks ### Send notifications for overdue tasks
@@ -133,12 +132,12 @@ Cronjob example:
0 8 * * * cd /path/to/kanboard && ./kanboard notification:overdue-tasks >/dev/null 2>&1 0 8 * * * cd /path/to/kanboard && ./kanboard notification:overdue-tasks >/dev/null 2>&1
``` ```
### Run daily project summaries calculation ### Run daily project stats calculation
You can add a background task that calculate the daily project summaries everyday: You can add a background task to calculate the project statistics everyday:
```bash ```bash
$ ./kanboard projects:daily-summary $ ./kanboard projects:daily-stats
Run calculation for Project #0 Run calculation for Project #0
Run calculation for Project #1 Run calculation for Project #1
Run calculation for Project #10 Run calculation for Project #10

View File

@@ -12,8 +12,8 @@ $application = new Application('Kanboard', APP_VERSION);
$application->add(new Console\TaskOverdueNotification($container)); $application->add(new Console\TaskOverdueNotification($container));
$application->add(new Console\SubtaskExport($container)); $application->add(new Console\SubtaskExport($container));
$application->add(new Console\TaskExport($container)); $application->add(new Console\TaskExport($container));
$application->add(new Console\ProjectDailySummaryCalculation($container)); $application->add(new Console\ProjectDailyStatsCalculation($container));
$application->add(new Console\ProjectDailySummaryExport($container)); $application->add(new Console\ProjectDailyColumnStatsExport($container));
$application->add(new Console\TransitionExport($container)); $application->add(new Console\TransitionExport($container));
$application->add(new Console\LocaleSync($container)); $application->add(new Console\LocaleSync($container));
$application->add(new Console\LocaleComparator($container)); $application->add(new Console\LocaleComparator($container));

View File

@@ -3,11 +3,11 @@
require __DIR__.'/../app/common.php'; require __DIR__.'/../app/common.php';
use Model\ProjectDailySummary; use Model\ProjectDailyColumnStats;
use Model\TaskCreation; use Model\TaskCreation;
use Model\TaskStatus; use Model\TaskStatus;
$pds = new ProjectDailySummary($container); $pds = new ProjectDailyColumnStats($container);
$taskCreation = new TaskCreation($container); $taskCreation = new TaskCreation($container);
$taskStatus = new TaskStatus($container); $taskStatus = new TaskStatus($container);

View File

@@ -3,11 +3,11 @@
require __DIR__.'/../app/common.php'; require __DIR__.'/../app/common.php';
use Model\ProjectDailySummary; use Model\ProjectDailyColumnStats;
use Model\TaskCreation; use Model\TaskCreation;
use Model\TaskPosition; use Model\TaskPosition;
$pds = new ProjectDailySummary($container); $pds = new ProjectDailyColumnStats($container);
$taskCreation = new TaskCreation($container); $taskCreation = new TaskCreation($container);
$taskPosition = new TaskPosition($container); $taskPosition = new TaskPosition($container);

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env php
<?php
require __DIR__.'/../app/common.php';
use Model\Project;
use Model\ProjectDailyStats;
$p = new Project($container);
$pds = new ProjectDailyStats($container);
$p->create(array('name' => 'Test Lead/Cycle time'));
$container['db']->table('tasks')->insert(array(
'title' => 'Lead time = 4d | Cycle time = 3d',
'date_creation' => strtotime('-7 days'),
'date_started' => strtotime('-6 days'),
'date_completed' => strtotime('-3 days'),
'is_active' => 0,
'project_id' => 1,
'column_id' => 1,
));
$container['db']->table('tasks')->insert(array(
'title' => 'Lead time = 1d | Cycle time = 1d',
'date_creation' => strtotime('-7 days'),
'date_started' => strtotime('-7 days'),
'date_completed' => strtotime('-6 days'),
'is_active' => 0,
'project_id' => 1,
'column_id' => 1,
));
$pds->updateTotals(1, date('Y-m-d', strtotime('-6 days')));
$container['db']->table('tasks')->insert(array(
'title' => 'Lead time = 7d | Cycle time = 5d',
'date_creation' => strtotime('-7 days'),
'date_started' => strtotime('-5 days'),
'date_completed' => strtotime('today'),
'is_active' => 0,
'project_id' => 1,
'column_id' => 1,
));
$pds->updateTotals(1, date('Y-m-d', strtotime('-5 days')));
$container['db']->table('tasks')->insert(array(
'title' => 'Lead time = 1d | Cycle time = 0',
'date_creation' => strtotime('-3 days'),
'date_started' => 0,
'date_completed' => 0,
'is_active' => 0,
'project_id' => 1,
'column_id' => 1,
));
$pds->updateTotals(1, date('Y-m-d', strtotime('-4 days')));
$container['db']->table('tasks')->insert(array(
'title' => 'Lead time = 1d | Cycle time = 1d',
'date_creation' => strtotime('-3 days'),
'date_started' => strtotime('-3 days'),
'date_completed' => 0,
'is_active' => 0,
'project_id' => 1,
'column_id' => 1,
));
$pds->updateTotals(1, date('Y-m-d', strtotime('-3 days')));

View File

@@ -3,17 +3,17 @@
require_once __DIR__.'/Base.php'; require_once __DIR__.'/Base.php';
use Model\Project; use Model\Project;
use Model\ProjectDailySummary; use Model\ProjectDailyColumnStats;
use Model\Task; use Model\Task;
use Model\TaskCreation; use Model\TaskCreation;
use Model\TaskStatus; use Model\TaskStatus;
class ProjectDailySummaryTest extends Base class ProjectDailyColumnStatsTest extends Base
{ {
public function testUpdateTotals() public function testUpdateTotals()
{ {
$p = new Project($this->container); $p = new Project($this->container);
$pds = new ProjectDailySummary($this->container); $pds = new ProjectDailyColumnStats($this->container);
$tc = new TaskCreation($this->container); $tc = new TaskCreation($this->container);
$ts = new TaskStatus($this->container); $ts = new TaskStatus($this->container);