diff --git a/src/applications/fact/chart/PhabricatorChartDataset.php b/src/applications/fact/chart/PhabricatorChartDataset.php --- a/src/applications/fact/chart/PhabricatorChartDataset.php +++ b/src/applications/fact/chart/PhabricatorChartDataset.php @@ -9,6 +9,11 @@ return $this->function; } + public function setFunction(PhabricatorComposeChartFunction $function) { + $this->function = $function; + return $this; + } + public static function newFromDictionary(array $map) { PhutilTypeSpec::checkMap( $map, diff --git a/src/applications/fact/chart/PhabricatorChartFunctionArgument.php b/src/applications/fact/chart/PhabricatorChartFunctionArgument.php --- a/src/applications/fact/chart/PhabricatorChartFunctionArgument.php +++ b/src/applications/fact/chart/PhabricatorChartFunctionArgument.php @@ -30,6 +30,7 @@ 'fact-key' => true, 'function' => true, 'number' => true, + 'phid' => true, ); if (!isset($types[$type])) { @@ -51,6 +52,10 @@ public function newValue($value) { switch ($this->getType()) { + case 'phid': + // TODO: This could be validated better, but probably should not be + // a primitive type. + return $value; case 'fact-key': if (!is_string($value)) { throw new Exception( diff --git a/src/applications/fact/chart/PhabricatorFactChartFunction.php b/src/applications/fact/chart/PhabricatorFactChartFunction.php --- a/src/applications/fact/chart/PhabricatorFactChartFunction.php +++ b/src/applications/fact/chart/PhabricatorFactChartFunction.php @@ -35,25 +35,38 @@ $conn = $table->establishConnection('r'); $table_name = $table->getTableName(); - $data = queryfx_all( + $where = array(); + + $where[] = qsprintf( $conn, - 'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC', - $table_name, + 'keyID = %d', $key_id); - if (!$data) { - return; + + $parser = $this->getArgumentParser(); + + $parts = $fact->buildWhereClauseParts($conn, $parser); + foreach ($parts as $part) { + $where[] = $part; } + $data = queryfx_all( + $conn, + 'SELECT value, epoch FROM %T WHERE %LA ORDER BY epoch ASC', + $table_name, + $where); + $map = array(); - foreach ($data as $row) { - $value = (int)$row['value']; - $epoch = (int)$row['epoch']; + if ($data) { + foreach ($data as $row) { + $value = (int)$row['value']; + $epoch = (int)$row['epoch']; - if (!isset($map[$epoch])) { - $map[$epoch] = 0; - } + if (!isset($map[$epoch])) { + $map[$epoch] = 0; + } - $map[$epoch] += $value; + $map[$epoch] += $value; + } } $this->map = $map; diff --git a/src/applications/fact/fact/PhabricatorFact.php b/src/applications/fact/fact/PhabricatorFact.php --- a/src/applications/fact/fact/PhabricatorFact.php +++ b/src/applications/fact/fact/PhabricatorFact.php @@ -38,7 +38,46 @@ abstract protected function newTemplateDatapoint(); final public function getFunctionArguments() { - return array(); + $key = $this->getKey(); + + $argv = array(); + + if (preg_match('/\.project\z/', $key)) { + $argv[] = id(new PhabricatorChartFunctionArgument()) + ->setName('phid') + ->setType('phid'); + } + + if (preg_match('/\.owner\z/', $key)) { + $argv[] = id(new PhabricatorChartFunctionArgument()) + ->setName('phid') + ->setType('phid'); + } + + return $argv; } + final public function buildWhereClauseParts( + AphrontDatabaseConnection $conn, + PhabricatorChartFunctionArgumentParser $arguments) { + $where = array(); + + $has_phid = $this->getFunctionArguments(); + + if ($has_phid) { + $phid = $arguments->getArgumentValue('phid'); + + $dimension_id = id(new PhabricatorFactObjectDimension()) + ->newDimensionID($phid); + + $where[] = qsprintf( + $conn, + 'dimensionID = %d', + $dimension_id); + } + + return $where; + } + + } diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -337,7 +337,8 @@ 'the project recently, it is counted on the day it was '. 'opened, not the day it was categorized. If a task was part '. 'of this project in the past but no longer is, it is not '. - 'counted at all.'); + 'counted at all. This table may not agree exactly with the chart '. + 'above.'); $header = pht('Task Burn Rate for Project %s', $handle->renderLink()); $caption = phutil_tag('p', array(), $inst); } else { @@ -379,26 +380,62 @@ list($burn_x, $burn_y) = $this->buildSeries($data); - require_celerity_resource('d3'); - require_celerity_resource('phui-chart-css'); + if ($project_phid) { + $argv = array( + 'sum', + array( + 'accumulate', + array('fact', 'tasks.open-count.create.project', $project_phid), + ), + array( + 'accumulate', + array('fact', 'tasks.open-count.status.project', $project_phid), + ), + array( + 'accumulate', + array('fact', 'tasks.open-count.assign.project', $project_phid), + ), + ); + } else { + $argv = array( + 'sum', + array('accumulate', array('fact', 'tasks.open-count.create')), + array('accumulate', array('fact', 'tasks.open-count.status')), + ); + } - Javelin::initBehavior('line-chart-legacy', array( - 'hardpoint' => $id, - 'x' => array( - $burn_x, - ), - 'y' => array( - $burn_y, - ), - 'xformat' => 'epoch', - 'yformat' => 'int', - )); + $function = id(new PhabricatorComposeChartFunction()) + ->setArguments(array($argv)); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Burnup Rate')) - ->appendChild($chart); + $datasets = array( + id(new PhabricatorChartDataset()) + ->setFunction($function), + ); + + $chart = id(new PhabricatorFactChart()) + ->setDatasets($datasets); + + $engine = id(new PhabricatorChartEngine()) + ->setViewer($viewer) + ->setChart($chart); + + $chart = $engine->getStoredChart(); + + $panel_type = id(new PhabricatorDashboardChartPanelType()) + ->getPanelTypeKey(); + + $chart_panel = id(new PhabricatorDashboardPanel()) + ->setPanelType($panel_type) + ->setName(pht('Burnup Rate')) + ->setProperty('chartKey', $chart->getChartKey()); + + $chart_view = id(new PhabricatorDashboardPanelRenderingEngine()) + ->setViewer($viewer) + ->setPanel($chart_panel) + ->setParentPanelPHIDs(array()) + ->renderPanel(); - return array($filter, $box, $panel); + return array($filter, $chart_view, $panel); } private function renderReportFilters(array $tokens, $has_window) {