mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-27 16:00:59 +01:00
Simplify and improve the "burnup" chart
Summary: - We incorrectly count resolution changes and other noise as opens / closes. - Show one graph: open bugs over time (red line minus green line). This and its derivative are the values you actually care about. It is difficult to see the derivative with both lines, but easy with one line. Test Plan: Looked at burnup chart. Saw charty things. Verified resolution changes no longer make the line move. Reviewers: btrahan Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T923, T982 Differential Revision: https://secure.phabricator.com/D1945
This commit is contained in:
parent
3db02a584c
commit
49cc3d9f0d
4 changed files with 43 additions and 55 deletions
|
@ -441,7 +441,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'javelin-behavior-burn-chart' =>
|
'javelin-behavior-burn-chart' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/ed1bf018/rsrc/js/application/maniphest/behavior-burn-chart.js',
|
'uri' => '/res/053cffd4/rsrc/js/application/maniphest/behavior-burn-chart.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -108,7 +108,8 @@ final class ManiphestReportController extends ManiphestController {
|
||||||
|
|
||||||
$data = queryfx_all(
|
$data = queryfx_all(
|
||||||
$conn,
|
$conn,
|
||||||
'SELECT x.newValue, x.dateCreated FROM %T x %Q WHERE transactionType = %s
|
'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q
|
||||||
|
WHERE transactionType = %s
|
||||||
ORDER BY x.dateCreated ASC',
|
ORDER BY x.dateCreated ASC',
|
||||||
$table->getTableName(),
|
$table->getTableName(),
|
||||||
$joins,
|
$joins,
|
||||||
|
@ -117,8 +118,29 @@ final class ManiphestReportController extends ManiphestController {
|
||||||
$stats = array();
|
$stats = array();
|
||||||
$day_buckets = array();
|
$day_buckets = array();
|
||||||
|
|
||||||
foreach ($data as $row) {
|
$open_tasks = array();
|
||||||
$is_close = $row['newValue'];
|
|
||||||
|
foreach ($data as $key => $row) {
|
||||||
|
|
||||||
|
// NOTE: Hack to avoid json_decode().
|
||||||
|
$oldv = trim($row['oldValue'], '"');
|
||||||
|
$newv = trim($row['newValue'], '"');
|
||||||
|
|
||||||
|
$old_is_open = ($oldv === (string)ManiphestTaskStatus::STATUS_OPEN);
|
||||||
|
$new_is_open = ($newv === (string)ManiphestTaskStatus::STATUS_OPEN);
|
||||||
|
|
||||||
|
$is_open = ($new_is_open && !$old_is_open);
|
||||||
|
$is_close = ($old_is_open && !$new_is_open);
|
||||||
|
|
||||||
|
$data[$key]['_is_open'] = $is_open;
|
||||||
|
$data[$key]['_is_close'] = $is_close;
|
||||||
|
|
||||||
|
if (!$is_open && !$is_close) {
|
||||||
|
// This is either some kind of bogus event, or a resolution change
|
||||||
|
// (e.g., resolved -> invalid). Just skip it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$day_bucket = __phabricator_format_local_time(
|
$day_bucket = __phabricator_format_local_time(
|
||||||
$row['dateCreated'],
|
$row['dateCreated'],
|
||||||
$user,
|
$user,
|
||||||
|
@ -286,7 +308,7 @@ final class ManiphestReportController extends ManiphestController {
|
||||||
),
|
),
|
||||||
'');
|
'');
|
||||||
|
|
||||||
list($open_x, $close_x, $open_y, $close_y) = $this->buildSeries($data);
|
list($burn_x, $burn_y) = $this->buildSeries($data);
|
||||||
|
|
||||||
require_celerity_resource('raphael-core');
|
require_celerity_resource('raphael-core');
|
||||||
require_celerity_resource('raphael-g');
|
require_celerity_resource('raphael-g');
|
||||||
|
@ -295,12 +317,10 @@ final class ManiphestReportController extends ManiphestController {
|
||||||
Javelin::initBehavior('burn-chart', array(
|
Javelin::initBehavior('burn-chart', array(
|
||||||
'hardpoint' => $id,
|
'hardpoint' => $id,
|
||||||
'x' => array(
|
'x' => array(
|
||||||
$open_x,
|
$burn_x,
|
||||||
$close_x,
|
|
||||||
),
|
),
|
||||||
'y' => array(
|
'y' => array(
|
||||||
$open_y,
|
$burn_y,
|
||||||
$close_y,
|
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -308,40 +328,21 @@ final class ManiphestReportController extends ManiphestController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildSeries(array $data) {
|
private function buildSeries(array $data) {
|
||||||
$open_count = 0;
|
$out = array();
|
||||||
$close_count = 0;
|
|
||||||
|
|
||||||
$open_x = array();
|
|
||||||
$open_y = array();
|
|
||||||
$close_x = array();
|
|
||||||
$close_y = array();
|
|
||||||
|
|
||||||
$start = (int)idx(head($data), 'dateCreated', time());
|
|
||||||
|
|
||||||
$open_x[] = $start;
|
|
||||||
$open_y[] = $open_count;
|
|
||||||
$close_x[] = $start;
|
|
||||||
$close_y[] = $close_count;
|
|
||||||
|
|
||||||
|
$counter = 0;
|
||||||
foreach ($data as $row) {
|
foreach ($data as $row) {
|
||||||
$t = (int)$row['dateCreated'];
|
$t = (int)$row['dateCreated'];
|
||||||
if ($row['newValue']) {
|
if ($row['_is_close']) {
|
||||||
++$close_count;
|
--$counter;
|
||||||
$close_x[] = $t;
|
$out[$t] = $counter;
|
||||||
$close_y[] = $close_count;
|
} else if ($row['_is_open']) {
|
||||||
} else {
|
++$counter;
|
||||||
++$open_count;
|
$out[$t] = $counter;
|
||||||
$open_x[] = $t;
|
|
||||||
$open_y[] = $open_count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$close_x[] = time();
|
return array(array_keys($out), array_values($out));
|
||||||
$close_y[] = $close_count;
|
|
||||||
$open_x[] = time();
|
|
||||||
$open_y[] = $open_count;
|
|
||||||
|
|
||||||
return array($open_x, $close_x, $open_y, $close_y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatBurnRow($label, $info) {
|
private function formatBurnRow($label, $info) {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
phutil_require_module('phabricator', 'aphront/response/404');
|
phutil_require_module('phabricator', 'aphront/response/404');
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
|
phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
|
||||||
|
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
|
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/controller/base');
|
phutil_require_module('phabricator', 'applications/maniphest/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/query');
|
phutil_require_module('phabricator', 'applications/maniphest/query');
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
JX.behavior('burn-chart', function(config) {
|
JX.behavior('burn-chart', function(config) {
|
||||||
|
|
||||||
|
|
||||||
var h = JX.$(config.hardpoint);
|
var h = JX.$(config.hardpoint);
|
||||||
var p = JX.$V(h);
|
var p = JX.$V(h);
|
||||||
var d = JX.Vector.getDim(h);
|
var d = JX.Vector.getDim(h);
|
||||||
|
@ -25,7 +26,7 @@ JX.behavior('burn-chart', function(config) {
|
||||||
axis: "0 0 1 1",
|
axis: "0 0 1 1",
|
||||||
shade: true,
|
shade: true,
|
||||||
gutter: 1,
|
gutter: 1,
|
||||||
colors: ['#d00', '#090']
|
colors: ['#d06']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,13 +46,8 @@ JX.behavior('burn-chart', function(config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get rid of the green shading below closed tasks.
|
|
||||||
|
|
||||||
l.shades[1].attr({fill: '#fff', opacity: 1});
|
|
||||||
|
|
||||||
l.hoverColumn(function() {
|
l.hoverColumn(function() {
|
||||||
|
|
||||||
|
|
||||||
var open = 0;
|
var open = 0;
|
||||||
for (var ii = 0; ii < config.x[0].length; ii++) {
|
for (var ii = 0; ii < config.x[0].length; ii++) {
|
||||||
if (config.x[0][ii] > this.axis) {
|
if (config.x[0][ii] > this.axis) {
|
||||||
|
@ -60,23 +56,13 @@ JX.behavior('burn-chart', function(config) {
|
||||||
open = config.y[0][ii];
|
open = config.y[0][ii];
|
||||||
}
|
}
|
||||||
|
|
||||||
var closed = 0;
|
|
||||||
for (var ii = 0; ii < config.x[1].length; ii++) {
|
|
||||||
if (config.x[1][ii] > this.axis) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
closed = config.y[1][ii];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var date = new Date(parseInt(this.axis, 10) * 1000).toLocaleDateString();
|
var date = new Date(parseInt(this.axis, 10) * 1000).toLocaleDateString();
|
||||||
var total = open + " Total Tasks";
|
var total = open + " Open Tasks";
|
||||||
var pain = (open - closed) + " Open Tasks";
|
|
||||||
|
|
||||||
var tag = r.tag(
|
var tag = r.tag(
|
||||||
this.x,
|
this.x,
|
||||||
this.y[0],
|
this.y[0],
|
||||||
[date, total, pain].join("\n"),
|
[date, total].join("\n"),
|
||||||
180,
|
180,
|
||||||
24);
|
24);
|
||||||
tag
|
tag
|
||||||
|
|
Loading…
Reference in a new issue