@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.

Store charts earlier and build them out a little later

Summary:
Ref T13279. Currently, we store a fairly low-level description of functions and datasets in a chart. This will create problems with (for example) translating function labels.

If you view a chart someone links you, it should say "El Charto" if you speak Spanish, not "The Chart" if the original viewer speaks English.

To support this, store a slightly higher level version of the chart: the chart engine key, plus configuration parameters. This is very similar to how SearchEngine works.

For example, the burndown chart now stores a list of project PHIDs, instead of a list of `[accumulate [sum [fact task.open <project-phid>]]]` functions.

(This leaves some serialization code with no callsites, but we may eventually have a "CustomChartEngine" which stores raw functions, so I'm leaving it for now.)

As a result, function labels provided by the chart engine are now translatable.

(Note that the actual chart is meaningless since the underlying facts can't be stacked like they're being stacked, as some are negative in some areas of their accumulation.)

Test Plan: {F6439121}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

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

+133 -70
-3
src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
··· 148 148 $wire_labels = array(); 149 149 foreach ($functions as $function_key => $function) { 150 150 $label = $function->getFunctionLabel(); 151 - 152 - $label->setName(pht('Important Data %s', $function_key)); 153 - 154 151 $wire_labels[] = $label->toWireFormat(); 155 152 } 156 153
+57 -8
src/applications/fact/engine/PhabricatorChartEngine.php
··· 4 4 extends Phobject { 5 5 6 6 private $viewer; 7 + private $engineParameters = array(); 8 + 9 + const KEY_ENGINE = 'engineKey'; 10 + const KEY_PARAMETERS = 'engineParameters'; 7 11 8 12 final public function setViewer(PhabricatorUser $viewer) { 9 13 $this->viewer = $viewer; ··· 14 18 return $this->viewer; 15 19 } 16 20 21 + final protected function setEngineParameter($key, $value) { 22 + $this->engineParameters[$key] = $value; 23 + return $this; 24 + } 25 + 26 + final protected function getEngineParameter($key, $default = null) { 27 + return idx($this->engineParameters, $key, $default); 28 + } 29 + 30 + final protected function getEngineParameters() { 31 + return $this->engineParameters; 32 + } 33 + 34 + final public static function newFromChart(PhabricatorFactChart $chart) { 35 + $engine_key = $chart->getChartParameter(self::KEY_ENGINE); 36 + 37 + $engine_map = self::getAllChartEngines(); 38 + if (!isset($engine_map[$engine_key])) { 39 + throw new Exception( 40 + pht( 41 + 'Chart uses unknown engine key ("%s") and can not be rendered.', 42 + $engine_key)); 43 + } 44 + 45 + return clone id($engine_map[$engine_key]); 46 + } 47 + 48 + final public static function getAllChartEngines() { 49 + return id(new PhutilClassMapQuery()) 50 + ->setAncestorClass(__CLASS__) 51 + ->setUniqueMethod('getChartEngineKey') 52 + ->execute(); 53 + } 54 + 17 55 final public function getChartEngineKey() { 18 56 return $this->getPhobjectClassConstant('CHARTENGINEKEY', 32); 19 57 } 20 58 21 - abstract protected function newChart(); 59 + final public function buildChart(PhabricatorFactChart $chart) { 60 + $map = $chart->getChartParameter(self::KEY_PARAMETERS, array()); 61 + return $this->newChart($chart, $map); 62 + } 22 63 23 - final public function buildChart() { 64 + abstract protected function newChart(PhabricatorFactChart $chart, array $map); 65 + 66 + final public function buildChartPanel() { 24 67 $viewer = $this->getViewer(); 25 68 26 - $chart = $this->newChart(); 69 + $parameters = $this->getEngineParameters(); 70 + 71 + $chart = id(new PhabricatorFactChart()) 72 + ->setChartParameter(self::KEY_ENGINE, $this->getChartEngineKey()) 73 + ->setChartParameter(self::KEY_PARAMETERS, $this->getEngineParameters()); 27 74 28 75 $rendering_engine = id(new PhabricatorChartRenderingEngine()) 29 76 ->setViewer($viewer) 30 77 ->setChart($chart); 31 78 32 - return $rendering_engine->getStoredChart(); 33 - } 34 - 35 - final public function buildChartPanel() { 36 - $chart = $this->buildChart(); 79 + $chart = $rendering_engine->getStoredChart(); 37 80 38 81 $panel_type = id(new PhabricatorDashboardChartPanelType()) 39 82 ->getPanelTypeKey(); ··· 43 86 ->setProperty('chartKey', $chart->getChartKey()); 44 87 45 88 return $chart_panel; 89 + } 90 + 91 + final protected function newFunction($name /* , ... */) { 92 + $argv = func_get_args(); 93 + return id(new PhabricatorComposeChartFunction()) 94 + ->setArguments(array($argv)); 46 95 } 47 96 48 97 }
+4
src/applications/fact/engine/PhabricatorChartRenderingEngine.php
··· 113 113 $chart = $this->getStoredChart(); 114 114 $chart_key = $chart->getChartKey(); 115 115 116 + $chart_engine = PhabricatorChartEngine::newFromChart($chart) 117 + ->setViewer($this->getViewer()); 118 + $chart_engine->buildChart($chart); 119 + 116 120 $datasets = $chart->getDatasets(); 117 121 118 122 $functions = array();
+4 -25
src/applications/fact/storage/PhabricatorFactChart.php
··· 7 7 protected $chartKey; 8 8 protected $chartParameters = array(); 9 9 10 - private $datasets; 10 + private $datasets = self::ATTACHABLE; 11 11 12 12 protected function getConfiguration() { 13 13 return array( ··· 54 54 return parent::save(); 55 55 } 56 56 57 - public function setDatasets(array $datasets) { 57 + public function attachDatasets(array $datasets) { 58 58 assert_instances_of($datasets, 'PhabricatorChartDataset'); 59 - 60 - $dataset_list = array(); 61 - foreach ($datasets as $dataset) { 62 - $dataset_list[] = $dataset->toDictionary(); 63 - } 64 - 65 - $this->setChartParameter('datasets', $dataset_list); 66 - $this->datasets = null; 67 - 59 + $this->datasets = $datasets; 68 60 return $this; 69 61 } 70 62 71 63 public function getDatasets() { 72 - if ($this->datasets === null) { 73 - $this->datasets = $this->newDatasets(); 74 - } 75 - return $this->datasets; 76 - } 77 - 78 - private function newDatasets() { 79 - $datasets = $this->getChartParameter('datasets', array()); 80 - 81 - foreach ($datasets as $key => $dataset) { 82 - $datasets[$key] = PhabricatorChartDataset::newFromDictionary($dataset); 83 - } 84 - 85 - return $datasets; 64 + return $this->assertAttached($this->datasets); 86 65 } 87 66 88 67 public function getURI() {
+68 -34
src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
··· 5 5 6 6 const CHARTENGINEKEY = 'project.burndown'; 7 7 8 - private $projects; 9 - 10 8 public function setProjects(array $projects) { 11 9 assert_instances_of($projects, 'PhabricatorProject'); 12 - 13 - $this->projects = $projects; 14 - 15 - return $this; 10 + $project_phids = mpull($projects, 'getPHID'); 11 + return $this->setEngineParameter('projectPHIDs', $project_phids); 16 12 } 17 13 18 - public function getProjects() { 19 - return $this->projects; 20 - } 14 + protected function newChart(PhabricatorFactChart $chart, array $map) { 15 + $viewer = $this->getViewer(); 21 16 22 - protected function newChart() { 23 - if ($this->projects !== null) { 24 - $project_phids = mpull($this->projects, 'getPHID'); 17 + $map = $map + array( 18 + 'projectPHIDs' => array(), 19 + ); 20 + 21 + if ($map['projectPHIDs']) { 22 + $projects = id(new PhabricatorProjectQuery()) 23 + ->setViewer($viewer) 24 + ->withPHIDs($map['projectPHIDs']) 25 + ->execute(); 26 + $project_phids = mpull($projects, 'getPHID'); 25 27 } else { 26 - $project_phids = null; 28 + $project_phids = array(); 27 29 } 28 30 29 - $argvs = array(); 31 + $functions = array(); 30 32 if ($project_phids) { 31 33 foreach ($project_phids as $project_phid) { 32 - $argvs[] = array( 34 + $function = $this->newFunction( 33 35 'accumulate', 34 - array('fact', 'tasks.open-count.create.project', $project_phid), 35 - ); 36 - $argvs[] = array( 36 + array('fact', 'tasks.open-count.create.project', $project_phid)); 37 + 38 + $function->getFunctionLabel() 39 + ->setName(pht('Tasks Created')) 40 + ->setColor('rgba(0, 0, 200, 1)') 41 + ->setFillColor('rgba(0, 0, 200, 0.15)'); 42 + 43 + $functions[] = $function; 44 + 45 + 46 + $function = $this->newFunction( 37 47 'accumulate', 38 - array('fact', 'tasks.open-count.status.project', $project_phid), 39 - ); 40 - $argvs[] = array( 48 + array('fact', 'tasks.open-count.status.project', $project_phid)); 49 + 50 + $function->getFunctionLabel() 51 + ->setName(pht('Tasks Closed / Reopened')) 52 + ->setColor('rgba(200, 0, 200, 1)') 53 + ->setFillColor('rgba(200, 0, 200, 0.15)'); 54 + 55 + $functions[] = $function; 56 + 57 + 58 + $function = $this->newFunction( 41 59 'accumulate', 42 - array('fact', 'tasks.open-count.assign.project', $project_phid), 43 - ); 60 + array('fact', 'tasks.open-count.assign.project', $project_phid)); 61 + 62 + $function->getFunctionLabel() 63 + ->setName(pht('Tasks Rescoped')) 64 + ->setColor('rgba(0, 200, 200, 1)') 65 + ->setFillColor('rgba(0, 200, 200, 0.15)'); 66 + 67 + $functions[] = $function; 44 68 } 45 69 } else { 46 - $argvs[] = array('accumulate', array('fact', 'tasks.open-count.create')); 47 - $argvs[] = array('accumulate', array('fact', 'tasks.open-count.status')); 48 - } 70 + $function = $this->newFunction( 71 + 'accumulate', 72 + array('fact', 'tasks.open-count.create')); 73 + 74 + $function->getFunctionLabel() 75 + ->setName(pht('Tasks Created')) 76 + ->setColor('rgba(0, 200, 200, 1)') 77 + ->setFillColor('rgba(0, 200, 200, 0.15)'); 78 + 79 + $functions[] = $function; 80 + 81 + $function = $this->newFunction( 82 + 'accumulate', 83 + array('fact', 'tasks.open-count.status')); 84 + 85 + $function->getFunctionLabel() 86 + ->setName(pht('Tasks Closed / Reopened')) 87 + ->setColor('rgba(200, 0, 200, 1)') 88 + ->setFillColor('rgba(200, 0, 200, 0.15)'); 49 89 50 - $functions = array(); 51 - foreach ($argvs as $argv) { 52 - $functions[] = id(new PhabricatorComposeChartFunction()) 53 - ->setArguments(array($argv)); 90 + $functions[] = $function; 54 91 } 55 92 56 93 $datasets = array(); ··· 58 95 $datasets[] = id(new PhabricatorChartStackedAreaDataset()) 59 96 ->setFunctions($functions); 60 97 61 - $chart = id(new PhabricatorFactChart()) 62 - ->setDatasets($datasets); 63 - 64 - return $chart; 98 + $chart->attachDatasets($datasets); 65 99 } 66 100 67 101 }