@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Consolidate burndown logic into a "BurndownChartEngine"

Summary:
Ref T13279. For now, we need to render burndowns from both Maniphest (legacy) and Projects (new prototype).

Consolidate this logic into a "BurndownChartEngine". I plan to expand this to work a bit like a "SearchEngine", and serve as a UI layer on top of the raw chart features.

The old "ChartEngine" is now "ChartRenderingEngine".

Test Plan:
- Viewed burndowns ("burnups") in Maniphest.
- Viewed burndowns in Projects.
- Saw the same chart.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

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

+345 -291
+4
src/__phutil_library_map__.php
··· 2668 2668 'PhabricatorChartFunction' => 'applications/fact/chart/PhabricatorChartFunction.php', 2669 2669 'PhabricatorChartFunctionArgument' => 'applications/fact/chart/PhabricatorChartFunctionArgument.php', 2670 2670 'PhabricatorChartFunctionArgumentParser' => 'applications/fact/chart/PhabricatorChartFunctionArgumentParser.php', 2671 + 'PhabricatorChartRenderingEngine' => 'applications/fact/engine/PhabricatorChartRenderingEngine.php', 2671 2672 'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php', 2672 2673 'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php', 2673 2674 'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php', ··· 4142 4143 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 4143 4144 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 4144 4145 'PhabricatorProjectBuiltinsExample' => 'applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php', 4146 + 'PhabricatorProjectBurndownChartEngine' => 'applications/project/chart/PhabricatorProjectBurndownChartEngine.php', 4145 4147 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', 4146 4148 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', 4147 4149 'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php', ··· 8680 8682 'PhabricatorChartFunction' => 'Phobject', 8681 8683 'PhabricatorChartFunctionArgument' => 'Phobject', 8682 8684 'PhabricatorChartFunctionArgumentParser' => 'Phobject', 8685 + 'PhabricatorChartRenderingEngine' => 'Phobject', 8683 8686 'PhabricatorChatLogApplication' => 'PhabricatorApplication', 8684 8687 'PhabricatorChatLogChannel' => array( 8685 8688 'PhabricatorChatLogDAO', ··· 10383 10386 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 10384 10387 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', 10385 10388 'PhabricatorProjectBuiltinsExample' => 'PhabricatorUIExample', 10389 + 'PhabricatorProjectBurndownChartEngine' => 'PhabricatorChartEngine', 10386 10390 'PhabricatorProjectCardView' => 'AphrontTagView', 10387 10391 'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType', 10388 10392 'PhabricatorProjectColorsConfigType' => 'PhabricatorJSONConfigType',
+2 -2
src/applications/dashboard/paneltype/PhabricatorDashboardChartPanelType.php
··· 37 37 PhabricatorDashboardPanel $panel, 38 38 PhabricatorDashboardPanelRenderingEngine $engine) { 39 39 40 - $engine = id(new PhabricatorChartEngine()) 40 + $engine = id(new PhabricatorChartRenderingEngine()) 41 41 ->setViewer($viewer); 42 42 43 43 $chart = $engine->loadChart($panel->getProperty('chartKey')); ··· 55 55 PHUIHeaderView $header) { 56 56 57 57 $key = $panel->getProperty('chartKey'); 58 - $uri = PhabricatorChartEngine::getChartURI($key); 58 + $uri = PhabricatorChartRenderingEngine::getChartURI($key); 59 59 60 60 $icon = id(new PHUIIconView()) 61 61 ->setIcon('fa-area-chart');
+2 -2
src/applications/fact/controller/PhabricatorFactChartController.php
··· 10 10 return $this->newDemoChart(); 11 11 } 12 12 13 - $engine = id(new PhabricatorChartEngine()) 13 + $engine = id(new PhabricatorChartRenderingEngine()) 14 14 ->setViewer($viewer); 15 15 16 16 $chart = $engine->loadChart($chart_key); ··· 95 95 $chart = id(new PhabricatorFactChart()) 96 96 ->setDatasets($datasets); 97 97 98 - $engine = id(new PhabricatorChartEngine()) 98 + $engine = id(new PhabricatorChartRenderingEngine()) 99 99 ->setViewer($viewer) 100 100 ->setChart($chart); 101 101
+21 -205
src/applications/fact/engine/PhabricatorChartEngine.php
··· 1 1 <?php 2 2 3 - final class PhabricatorChartEngine 3 + abstract class PhabricatorChartEngine 4 4 extends Phobject { 5 5 6 6 private $viewer; 7 - private $chart; 8 - private $storedChart; 9 7 10 - public function setViewer(PhabricatorUser $viewer) { 8 + final public function setViewer(PhabricatorUser $viewer) { 11 9 $this->viewer = $viewer; 12 10 return $this; 13 11 } 14 12 15 - public function getViewer() { 13 + final public function getViewer() { 16 14 return $this->viewer; 17 15 } 18 16 19 - public function setChart(PhabricatorFactChart $chart) { 20 - $this->chart = $chart; 21 - return $this; 22 - } 23 - 24 - public function getChart() { 25 - return $this->chart; 26 - } 27 - 28 - public function loadChart($chart_key) { 29 - $chart = id(new PhabricatorFactChart())->loadOneWhere( 30 - 'chartKey = %s', 31 - $chart_key); 32 - 33 - if ($chart) { 34 - $this->setChart($chart); 35 - } 36 - 37 - return $chart; 17 + final public function getChartEngineKey() { 18 + return $this->getPhobjectClassConstant('CHARTENGINEKEY', 32); 38 19 } 39 20 40 - public static function getChartURI($chart_key) { 41 - return id(new PhabricatorFactChart()) 42 - ->setChartKey($chart_key) 43 - ->getURI(); 44 - } 45 - 46 - public function getStoredChart() { 47 - if (!$this->storedChart) { 48 - $chart = $this->getChart(); 49 - $chart_key = $chart->getChartKey(); 50 - if (!$chart_key) { 51 - $chart_key = $chart->newChartKey(); 52 - 53 - $stored_chart = id(new PhabricatorFactChart())->loadOneWhere( 54 - 'chartKey = %s', 55 - $chart_key); 56 - if ($stored_chart) { 57 - $chart = $stored_chart; 58 - } else { 59 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 60 - 61 - try { 62 - $chart->save(); 63 - } catch (AphrontDuplicateKeyQueryException $ex) { 64 - $chart = id(new PhabricatorFactChart())->loadOneWhere( 65 - 'chartKey = %s', 66 - $chart_key); 67 - if (!$chart) { 68 - throw new Exception( 69 - pht( 70 - 'Failed to load chart with key "%s" after key collision. '. 71 - 'This should not be possible.', 72 - $chart_key)); 73 - } 74 - } 75 - 76 - unset($unguarded); 77 - } 78 - $this->setChart($chart); 79 - } 80 - 81 - $this->storedChart = $chart; 82 - } 83 - 84 - return $this->storedChart; 85 - } 86 - 87 - public function newChartView() { 88 - $chart = $this->getStoredChart(); 89 - $chart_key = $chart->getChartKey(); 90 - 91 - $chart_node_id = celerity_generate_unique_node_id(); 92 - 93 - $chart_view = phutil_tag( 94 - 'div', 95 - array( 96 - 'id' => $chart_node_id, 97 - 'style' => 'background: #ffffff; '. 98 - 'height: 480px; ', 99 - ), 100 - ''); 101 - 102 - $data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key); 103 - 104 - Javelin::initBehavior( 105 - 'line-chart', 106 - array( 107 - 'chartNodeID' => $chart_node_id, 108 - 'dataURI' => (string)$data_uri, 109 - )); 110 - 111 - return $chart_view; 112 - } 113 - 114 - public function newChartData() { 115 - $chart = $this->getStoredChart(); 116 - $chart_key = $chart->getChartKey(); 117 - 118 - $datasets = $chart->getDatasets(); 119 - 120 - $functions = array(); 121 - foreach ($datasets as $dataset) { 122 - $functions[] = $dataset->getFunction(); 123 - } 21 + abstract protected function newChart(); 124 22 125 - $subfunctions = array(); 126 - foreach ($functions as $function) { 127 - foreach ($function->getSubfunctions() as $subfunction) { 128 - $subfunctions[] = $subfunction; 129 - } 130 - } 23 + final public function buildChart() { 24 + $viewer = $this->getViewer(); 131 25 132 - foreach ($subfunctions as $subfunction) { 133 - $subfunction->loadData(); 134 - } 26 + $chart = $this->newChart(); 135 27 136 - list($domain_min, $domain_max) = $this->getDomain($functions); 137 - 138 - $axis = id(new PhabricatorChartAxis()) 139 - ->setMinimumValue($domain_min) 140 - ->setMaximumValue($domain_max); 141 - 142 - $data_query = id(new PhabricatorChartDataQuery()) 143 - ->setMinimumValue($domain_min) 144 - ->setMaximumValue($domain_max) 145 - ->setLimit(2000); 28 + $rendering_engine = id(new PhabricatorChartRenderingEngine()) 29 + ->setViewer($viewer) 30 + ->setChart($chart); 146 31 147 - $datasets = array(); 148 - foreach ($functions as $function) { 149 - $points = $function->newDatapoints($data_query); 150 - 151 - $x = array(); 152 - $y = array(); 153 - 154 - foreach ($points as $point) { 155 - $x[] = $point['x']; 156 - $y[] = $point['y']; 157 - } 158 - 159 - $datasets[] = array( 160 - 'x' => $x, 161 - 'y' => $y, 162 - 'color' => '#ff00ff', 163 - ); 164 - } 165 - 166 - 167 - $y_min = 0; 168 - $y_max = 0; 169 - foreach ($datasets as $dataset) { 170 - if (!$dataset['y']) { 171 - continue; 172 - } 173 - 174 - $y_min = min($y_min, min($dataset['y'])); 175 - $y_max = max($y_max, max($dataset['y'])); 176 - } 177 - 178 - $chart_data = array( 179 - 'datasets' => $datasets, 180 - 'xMin' => $domain_min, 181 - 'xMax' => $domain_max, 182 - 'yMin' => $y_min, 183 - 'yMax' => $y_max, 184 - ); 185 - 186 - return $chart_data; 32 + return $rendering_engine->getStoredChart(); 187 33 } 188 34 189 - private function getDomain(array $functions) { 190 - $domain_min_list = null; 191 - $domain_max_list = null; 192 - 193 - foreach ($functions as $function) { 194 - $domain = $function->getDomain(); 195 - 196 - list($function_min, $function_max) = $domain; 197 - 198 - if ($function_min !== null) { 199 - $domain_min_list[] = $function_min; 200 - } 201 - 202 - if ($function_max !== null) { 203 - $domain_max_list[] = $function_max; 204 - } 205 - } 206 - 207 - $domain_min = null; 208 - $domain_max = null; 209 - 210 - if ($domain_min_list) { 211 - $domain_min = min($domain_min_list); 212 - } 213 - 214 - if ($domain_max_list) { 215 - $domain_max = max($domain_max_list); 216 - } 217 - 218 - // If we don't have any domain data from the actual functions, pick a 219 - // plausible domain automatically. 35 + final public function buildChartPanel() { 36 + $chart = $this->buildChart(); 220 37 221 - if ($domain_max === null) { 222 - $domain_max = PhabricatorTime::getNow(); 223 - } 38 + $panel_type = id(new PhabricatorDashboardChartPanelType()) 39 + ->getPanelTypeKey(); 224 40 225 - if ($domain_min === null) { 226 - $domain_min = $domain_max - phutil_units('365 days in seconds'); 227 - } 41 + $chart_panel = id(new PhabricatorDashboardPanel()) 42 + ->setPanelType($panel_type) 43 + ->setProperty('chartKey', $chart->getChartKey()); 228 44 229 - return array($domain_min, $domain_max); 45 + return $chart_panel; 230 46 } 231 47 232 48 }
+232
src/applications/fact/engine/PhabricatorChartRenderingEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorChartRenderingEngine 4 + extends Phobject { 5 + 6 + private $viewer; 7 + private $chart; 8 + private $storedChart; 9 + 10 + public function setViewer(PhabricatorUser $viewer) { 11 + $this->viewer = $viewer; 12 + return $this; 13 + } 14 + 15 + public function getViewer() { 16 + return $this->viewer; 17 + } 18 + 19 + public function setChart(PhabricatorFactChart $chart) { 20 + $this->chart = $chart; 21 + return $this; 22 + } 23 + 24 + public function getChart() { 25 + return $this->chart; 26 + } 27 + 28 + public function loadChart($chart_key) { 29 + $chart = id(new PhabricatorFactChart())->loadOneWhere( 30 + 'chartKey = %s', 31 + $chart_key); 32 + 33 + if ($chart) { 34 + $this->setChart($chart); 35 + } 36 + 37 + return $chart; 38 + } 39 + 40 + public static function getChartURI($chart_key) { 41 + return id(new PhabricatorFactChart()) 42 + ->setChartKey($chart_key) 43 + ->getURI(); 44 + } 45 + 46 + public function getStoredChart() { 47 + if (!$this->storedChart) { 48 + $chart = $this->getChart(); 49 + $chart_key = $chart->getChartKey(); 50 + if (!$chart_key) { 51 + $chart_key = $chart->newChartKey(); 52 + 53 + $stored_chart = id(new PhabricatorFactChart())->loadOneWhere( 54 + 'chartKey = %s', 55 + $chart_key); 56 + if ($stored_chart) { 57 + $chart = $stored_chart; 58 + } else { 59 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 60 + 61 + try { 62 + $chart->save(); 63 + } catch (AphrontDuplicateKeyQueryException $ex) { 64 + $chart = id(new PhabricatorFactChart())->loadOneWhere( 65 + 'chartKey = %s', 66 + $chart_key); 67 + if (!$chart) { 68 + throw new Exception( 69 + pht( 70 + 'Failed to load chart with key "%s" after key collision. '. 71 + 'This should not be possible.', 72 + $chart_key)); 73 + } 74 + } 75 + 76 + unset($unguarded); 77 + } 78 + $this->setChart($chart); 79 + } 80 + 81 + $this->storedChart = $chart; 82 + } 83 + 84 + return $this->storedChart; 85 + } 86 + 87 + public function newChartView() { 88 + $chart = $this->getStoredChart(); 89 + $chart_key = $chart->getChartKey(); 90 + 91 + $chart_node_id = celerity_generate_unique_node_id(); 92 + 93 + $chart_view = phutil_tag( 94 + 'div', 95 + array( 96 + 'id' => $chart_node_id, 97 + 'style' => 'background: #ffffff; '. 98 + 'height: 480px; ', 99 + ), 100 + ''); 101 + 102 + $data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key); 103 + 104 + Javelin::initBehavior( 105 + 'line-chart', 106 + array( 107 + 'chartNodeID' => $chart_node_id, 108 + 'dataURI' => (string)$data_uri, 109 + )); 110 + 111 + return $chart_view; 112 + } 113 + 114 + public function newChartData() { 115 + $chart = $this->getStoredChart(); 116 + $chart_key = $chart->getChartKey(); 117 + 118 + $datasets = $chart->getDatasets(); 119 + 120 + $functions = array(); 121 + foreach ($datasets as $dataset) { 122 + $functions[] = $dataset->getFunction(); 123 + } 124 + 125 + $subfunctions = array(); 126 + foreach ($functions as $function) { 127 + foreach ($function->getSubfunctions() as $subfunction) { 128 + $subfunctions[] = $subfunction; 129 + } 130 + } 131 + 132 + foreach ($subfunctions as $subfunction) { 133 + $subfunction->loadData(); 134 + } 135 + 136 + list($domain_min, $domain_max) = $this->getDomain($functions); 137 + 138 + $axis = id(new PhabricatorChartAxis()) 139 + ->setMinimumValue($domain_min) 140 + ->setMaximumValue($domain_max); 141 + 142 + $data_query = id(new PhabricatorChartDataQuery()) 143 + ->setMinimumValue($domain_min) 144 + ->setMaximumValue($domain_max) 145 + ->setLimit(2000); 146 + 147 + $datasets = array(); 148 + foreach ($functions as $function) { 149 + $points = $function->newDatapoints($data_query); 150 + 151 + $x = array(); 152 + $y = array(); 153 + 154 + foreach ($points as $point) { 155 + $x[] = $point['x']; 156 + $y[] = $point['y']; 157 + } 158 + 159 + $datasets[] = array( 160 + 'x' => $x, 161 + 'y' => $y, 162 + 'color' => '#ff00ff', 163 + ); 164 + } 165 + 166 + 167 + $y_min = 0; 168 + $y_max = 0; 169 + foreach ($datasets as $dataset) { 170 + if (!$dataset['y']) { 171 + continue; 172 + } 173 + 174 + $y_min = min($y_min, min($dataset['y'])); 175 + $y_max = max($y_max, max($dataset['y'])); 176 + } 177 + 178 + $chart_data = array( 179 + 'datasets' => $datasets, 180 + 'xMin' => $domain_min, 181 + 'xMax' => $domain_max, 182 + 'yMin' => $y_min, 183 + 'yMax' => $y_max, 184 + ); 185 + 186 + return $chart_data; 187 + } 188 + 189 + private function getDomain(array $functions) { 190 + $domain_min_list = null; 191 + $domain_max_list = null; 192 + 193 + foreach ($functions as $function) { 194 + $domain = $function->getDomain(); 195 + 196 + list($function_min, $function_max) = $domain; 197 + 198 + if ($function_min !== null) { 199 + $domain_min_list[] = $function_min; 200 + } 201 + 202 + if ($function_max !== null) { 203 + $domain_max_list[] = $function_max; 204 + } 205 + } 206 + 207 + $domain_min = null; 208 + $domain_max = null; 209 + 210 + if ($domain_min_list) { 211 + $domain_min = min($domain_min_list); 212 + } 213 + 214 + if ($domain_max_list) { 215 + $domain_max = max($domain_max_list); 216 + } 217 + 218 + // If we don't have any domain data from the actual functions, pick a 219 + // plausible domain automatically. 220 + 221 + if ($domain_max === null) { 222 + $domain_max = PhabricatorTime::getNow(); 223 + } 224 + 225 + if ($domain_min === null) { 226 + $domain_min = $domain_max - phutil_units('365 days in seconds'); 227 + } 228 + 229 + return array($domain_min, $domain_max); 230 + } 231 + 232 + }
+9 -42
src/applications/maniphest/controller/ManiphestReportController.php
··· 381 381 list($burn_x, $burn_y) = $this->buildSeries($data); 382 382 383 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 - ); 384 + $projects = id(new PhabricatorProjectQuery()) 385 + ->setViewer($viewer) 386 + ->withPHIDs(array($project_phid)) 387 + ->execute(); 399 388 } 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 - ); 389 + $projects = array(); 405 390 } 406 391 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); 417 - 418 - $engine = id(new PhabricatorChartEngine()) 392 + $panel = id(new PhabricatorProjectBurndownChartEngine()) 419 393 ->setViewer($viewer) 420 - ->setChart($chart); 421 - 422 - $chart = $engine->getStoredChart(); 423 - 424 - $panel_type = id(new PhabricatorDashboardChartPanelType()) 425 - ->getPanelTypeKey(); 394 + ->setProjects($projects) 395 + ->buildChartPanel(); 426 396 427 - $chart_panel = id(new PhabricatorDashboardPanel()) 428 - ->setPanelType($panel_type) 429 - ->setName(pht('Burnup Rate')) 430 - ->setProperty('chartKey', $chart->getChartKey()); 397 + $chart_panel = $panel->setName(pht('Burnup Rate')); 431 398 432 399 $chart_view = id(new PhabricatorDashboardPanelRenderingEngine()) 433 400 ->setViewer($viewer)
+71
src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectBurndownChartEngine 4 + extends PhabricatorChartEngine { 5 + 6 + const CHARTENGINEKEY = 'project.burndown'; 7 + 8 + private $projects; 9 + 10 + public function setProjects(array $projects) { 11 + assert_instances_of($projects, 'PhabricatorProject'); 12 + 13 + $this->projects = $projects; 14 + 15 + return $this; 16 + } 17 + 18 + public function getProjects() { 19 + return $this->projects; 20 + } 21 + 22 + protected function newChart() { 23 + if ($this->projects !== null) { 24 + $project_phids = mpull($this->projects, 'getPHID'); 25 + } else { 26 + $project_phids = null; 27 + } 28 + 29 + $argvs = array(); 30 + if ($project_phids) { 31 + foreach ($project_phids as $project_phid) { 32 + $argvs[] = array( 33 + 'sum', 34 + array( 35 + 'accumulate', 36 + array('fact', 'tasks.open-count.create.project', $project_phid), 37 + ), 38 + array( 39 + 'accumulate', 40 + array('fact', 'tasks.open-count.status.project', $project_phid), 41 + ), 42 + array( 43 + 'accumulate', 44 + array('fact', 'tasks.open-count.assign.project', $project_phid), 45 + ), 46 + ); 47 + } 48 + } else { 49 + $argvs[] = array( 50 + 'sum', 51 + array('accumulate', array('fact', 'tasks.open-count.create')), 52 + array('accumulate', array('fact', 'tasks.open-count.status')), 53 + ); 54 + } 55 + 56 + $datasets = array(); 57 + foreach ($argvs as $argv) { 58 + $function = id(new PhabricatorComposeChartFunction()) 59 + ->setArguments(array($argv)); 60 + 61 + $datasets[] = id(new PhabricatorChartDataset()) 62 + ->setFunction($function); 63 + } 64 + 65 + $chart = id(new PhabricatorFactChart()) 66 + ->setDatasets($datasets); 67 + 68 + return $chart; 69 + } 70 + 71 + }
+4 -40
src/applications/project/controller/PhabricatorProjectReportsController.php
··· 31 31 $crumbs->addTextCrumb(pht('Reports')); 32 32 $crumbs->setBorder(true); 33 33 34 - $project_phid = $project->getPHID(); 35 - 36 - $argv = array( 37 - 'sum', 38 - array( 39 - 'accumulate', 40 - array('fact', 'tasks.open-count.create.project', $project_phid), 41 - ), 42 - array( 43 - 'accumulate', 44 - array('fact', 'tasks.open-count.status.project', $project_phid), 45 - ), 46 - array( 47 - 'accumulate', 48 - array('fact', 'tasks.open-count.assign.project', $project_phid), 49 - ), 50 - ); 51 - 52 - $function = id(new PhabricatorComposeChartFunction()) 53 - ->setArguments(array($argv)); 54 - 55 - $datasets = array( 56 - id(new PhabricatorChartDataset()) 57 - ->setFunction($function), 58 - ); 59 - 60 - $chart = id(new PhabricatorFactChart()) 61 - ->setDatasets($datasets); 62 - 63 - $engine = id(new PhabricatorChartEngine()) 34 + $chart_panel = id(new PhabricatorProjectBurndownChartEngine()) 64 35 ->setViewer($viewer) 65 - ->setChart($chart); 66 - 67 - $chart = $engine->getStoredChart(); 36 + ->setProjects(array($project)) 37 + ->buildChartPanel(); 68 38 69 - $panel_type = id(new PhabricatorDashboardChartPanelType()) 70 - ->getPanelTypeKey(); 71 - 72 - $chart_panel = id(new PhabricatorDashboardPanel()) 73 - ->setPanelType($panel_type) 74 - ->setName(pht('%s: Burndown', $project->getName())) 75 - ->setProperty('chartKey', $chart->getChartKey()); 39 + $chart_panel->setName(pht('%s: Burndown', $project->getName())); 76 40 77 41 $chart_view = id(new PhabricatorDashboardPanelRenderingEngine()) 78 42 ->setViewer($viewer)