@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Replace the chart in Maniphest Reports with a chart driven by Facts

Summary:
Depends on D20485. Ref T13279. This removes the ad-hoc charting in Maniphest and replaces it with a Facts-based chart.

(To do this, we build a dashboard panel inline and render it.)

Test Plan: {F6412720}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

Differential Revision: https://secure.phabricator.com/D20486

+130 -31
+5
src/applications/fact/chart/PhabricatorChartDataset.php
··· 9 9 return $this->function; 10 10 } 11 11 12 + public function setFunction(PhabricatorComposeChartFunction $function) { 13 + $this->function = $function; 14 + return $this; 15 + } 16 + 12 17 public static function newFromDictionary(array $map) { 13 18 PhutilTypeSpec::checkMap( 14 19 $map,
+5
src/applications/fact/chart/PhabricatorChartFunctionArgument.php
··· 30 30 'fact-key' => true, 31 31 'function' => true, 32 32 'number' => true, 33 + 'phid' => true, 33 34 ); 34 35 35 36 if (!isset($types[$type])) { ··· 51 52 52 53 public function newValue($value) { 53 54 switch ($this->getType()) { 55 + case 'phid': 56 + // TODO: This could be validated better, but probably should not be 57 + // a primitive type. 58 + return $value; 54 59 case 'fact-key': 55 60 if (!is_string($value)) { 56 61 throw new Exception(
+25 -12
src/applications/fact/chart/PhabricatorFactChartFunction.php
··· 35 35 $conn = $table->establishConnection('r'); 36 36 $table_name = $table->getTableName(); 37 37 38 + $where = array(); 39 + 40 + $where[] = qsprintf( 41 + $conn, 42 + 'keyID = %d', 43 + $key_id); 44 + 45 + $parser = $this->getArgumentParser(); 46 + 47 + $parts = $fact->buildWhereClauseParts($conn, $parser); 48 + foreach ($parts as $part) { 49 + $where[] = $part; 50 + } 51 + 38 52 $data = queryfx_all( 39 53 $conn, 40 - 'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC', 54 + 'SELECT value, epoch FROM %T WHERE %LA ORDER BY epoch ASC', 41 55 $table_name, 42 - $key_id); 43 - if (!$data) { 44 - return; 45 - } 56 + $where); 46 57 47 58 $map = array(); 48 - foreach ($data as $row) { 49 - $value = (int)$row['value']; 50 - $epoch = (int)$row['epoch']; 59 + if ($data) { 60 + foreach ($data as $row) { 61 + $value = (int)$row['value']; 62 + $epoch = (int)$row['epoch']; 51 63 52 - if (!isset($map[$epoch])) { 53 - $map[$epoch] = 0; 64 + if (!isset($map[$epoch])) { 65 + $map[$epoch] = 0; 66 + } 67 + 68 + $map[$epoch] += $value; 54 69 } 55 - 56 - $map[$epoch] += $value; 57 70 } 58 71 59 72 $this->map = $map;
+40 -1
src/applications/fact/fact/PhabricatorFact.php
··· 38 38 abstract protected function newTemplateDatapoint(); 39 39 40 40 final public function getFunctionArguments() { 41 - return array(); 41 + $key = $this->getKey(); 42 + 43 + $argv = array(); 44 + 45 + if (preg_match('/\.project\z/', $key)) { 46 + $argv[] = id(new PhabricatorChartFunctionArgument()) 47 + ->setName('phid') 48 + ->setType('phid'); 49 + } 50 + 51 + if (preg_match('/\.owner\z/', $key)) { 52 + $argv[] = id(new PhabricatorChartFunctionArgument()) 53 + ->setName('phid') 54 + ->setType('phid'); 55 + } 56 + 57 + return $argv; 58 + } 59 + 60 + final public function buildWhereClauseParts( 61 + AphrontDatabaseConnection $conn, 62 + PhabricatorChartFunctionArgumentParser $arguments) { 63 + $where = array(); 64 + 65 + $has_phid = $this->getFunctionArguments(); 66 + 67 + if ($has_phid) { 68 + $phid = $arguments->getArgumentValue('phid'); 69 + 70 + $dimension_id = id(new PhabricatorFactObjectDimension()) 71 + ->newDimensionID($phid); 72 + 73 + $where[] = qsprintf( 74 + $conn, 75 + 'dimensionID = %d', 76 + $dimension_id); 77 + } 78 + 79 + return $where; 42 80 } 81 + 43 82 44 83 }
+55 -18
src/applications/maniphest/controller/ManiphestReportController.php
··· 337 337 'the project recently, it is counted on the day it was '. 338 338 'opened, not the day it was categorized. If a task was part '. 339 339 'of this project in the past but no longer is, it is not '. 340 - 'counted at all.'); 340 + 'counted at all. This table may not agree exactly with the chart '. 341 + 'above.'); 341 342 $header = pht('Task Burn Rate for Project %s', $handle->renderLink()); 342 343 $caption = phutil_tag('p', array(), $inst); 343 344 } else { ··· 379 380 380 381 list($burn_x, $burn_y) = $this->buildSeries($data); 381 382 382 - require_celerity_resource('d3'); 383 - require_celerity_resource('phui-chart-css'); 383 + if ($project_phid) { 384 + $argv = array( 385 + 'sum', 386 + array( 387 + 'accumulate', 388 + array('fact', 'tasks.open-count.create.project', $project_phid), 389 + ), 390 + array( 391 + 'accumulate', 392 + array('fact', 'tasks.open-count.status.project', $project_phid), 393 + ), 394 + array( 395 + 'accumulate', 396 + array('fact', 'tasks.open-count.assign.project', $project_phid), 397 + ), 398 + ); 399 + } else { 400 + $argv = array( 401 + 'sum', 402 + array('accumulate', array('fact', 'tasks.open-count.create')), 403 + array('accumulate', array('fact', 'tasks.open-count.status')), 404 + ); 405 + } 406 + 407 + $function = id(new PhabricatorComposeChartFunction()) 408 + ->setArguments(array($argv)); 409 + 410 + $datasets = array( 411 + id(new PhabricatorChartDataset()) 412 + ->setFunction($function), 413 + ); 414 + 415 + $chart = id(new PhabricatorFactChart()) 416 + ->setDatasets($datasets); 384 417 385 - Javelin::initBehavior('line-chart-legacy', array( 386 - 'hardpoint' => $id, 387 - 'x' => array( 388 - $burn_x, 389 - ), 390 - 'y' => array( 391 - $burn_y, 392 - ), 393 - 'xformat' => 'epoch', 394 - 'yformat' => 'int', 395 - )); 418 + $engine = id(new PhabricatorChartEngine()) 419 + ->setViewer($viewer) 420 + ->setChart($chart); 396 421 397 - $box = id(new PHUIObjectBoxView()) 398 - ->setHeaderText(pht('Burnup Rate')) 399 - ->appendChild($chart); 422 + $chart = $engine->getStoredChart(); 423 + 424 + $panel_type = id(new PhabricatorDashboardChartPanelType()) 425 + ->getPanelTypeKey(); 400 426 401 - return array($filter, $box, $panel); 427 + $chart_panel = id(new PhabricatorDashboardPanel()) 428 + ->setPanelType($panel_type) 429 + ->setName(pht('Burnup Rate')) 430 + ->setProperty('chartKey', $chart->getChartKey()); 431 + 432 + $chart_view = id(new PhabricatorDashboardPanelRenderingEngine()) 433 + ->setViewer($viewer) 434 + ->setPanel($chart_panel) 435 + ->setParentPanelPHIDs(array()) 436 + ->renderPanel(); 437 + 438 + return array($filter, $chart_view, $panel); 402 439 } 403 440 404 441 private function renderReportFilters(array $tokens, $has_window) {