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

Simplify implementation of "pure" Chart functions

Summary:
Depends on D20445. Ref T13279. I'm not sure what the class tree of functions actually looks like, and I suspect it isn't really a tree, so I'm hesitant to start subclassing. Instead, try adding some `isSomethingSomething()` methods.

We have some different types of functions:

# Some functions can be evaluated anywhere, like "constant(3)", which always evaluates to 3.
# Some functions can't be evaluated anywhere, but have values everywhere in some domain. This is most interesting functions, like "number of open tasks". These functions also usually have a distinct set of interesting points, and are constant between those points (any count of anything, like "open points in project" or "tasks closed by alice", etc).
# Some functions can be evaluated almost nowhere and have only discrete values. This is most of the data we actually store, which is just "+1" when a task is opened and "-1" when a task is closed.

Soon, I'd like to be able to show ("all tasks" - "open tasks") and draw a chart of closed tasks. This is somewhat tricky because the two datasets are of the second class of function (straight lines connecting dots) but their "interesting" x values won't be the same (users don't open and close tasks every second, or at the same time).

The "subtract X Y" function will need to be able to know that `subtract "all tasks" 3` and `subtract "all tasks" "closed tasks"` evaluate slightly differently.

To make this worse, the data we actually //store// is of the third class of function (just the "derivative" of the line chart), then we accumulate it in the application after we pull it out of the database. So the code will need to know that `subtract "derivative of all tasks" "derivative of closed tasks"` is meaningless, or the UI needs to make that clear, or it needs to interpret it to mean "accumulate the derivative into a line first".

Anyway, I'll sort that out in future changes. For now, simplify the easy case of functions in class (1), where they're just actual functions.

Add "shift(function, number)" and "scale(function, number)". These are probably like "mul" and "add" but they can't take two functions -- the second value must always be a constant. Maybe these will go away in the future and become `add(function, constant(3))` or something?

Test Plan: {F6382885}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

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

+251 -53
+6
src/__phutil_library_map__.php
··· 2817 2817 'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php', 2818 2818 'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php', 2819 2819 'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php', 2820 + 'PhabricatorCosChartFunction' => 'applications/fact/chart/PhabricatorCosChartFunction.php', 2820 2821 'PhabricatorCountFact' => 'applications/fact/fact/PhabricatorCountFact.php', 2821 2822 'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php', 2822 2823 'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php', ··· 4475 4476 'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php', 4476 4477 'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php', 4477 4478 'PhabricatorSavedQueryQuery' => 'applications/search/query/PhabricatorSavedQueryQuery.php', 4479 + 'PhabricatorScaleChartFunction' => 'applications/fact/chart/PhabricatorScaleChartFunction.php', 4478 4480 'PhabricatorScheduleTaskTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php', 4479 4481 'PhabricatorScopedEnv' => 'infrastructure/env/PhabricatorScopedEnv.php', 4480 4482 'PhabricatorSearchAbstractDocument' => 'applications/search/index/PhabricatorSearchAbstractDocument.php', ··· 4564 4566 'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php', 4565 4567 'PhabricatorSetupIssueUIExample' => 'applications/uiexample/examples/PhabricatorSetupIssueUIExample.php', 4566 4568 'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php', 4569 + 'PhabricatorShiftChartFunction' => 'applications/fact/chart/PhabricatorShiftChartFunction.php', 4567 4570 'PhabricatorShortSite' => 'aphront/site/PhabricatorShortSite.php', 4568 4571 'PhabricatorShowFiletreeSetting' => 'applications/settings/setting/PhabricatorShowFiletreeSetting.php', 4569 4572 'PhabricatorSimpleEditType' => 'applications/transactions/edittype/PhabricatorSimpleEditType.php', ··· 8811 8814 'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType', 8812 8815 'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType', 8813 8816 'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType', 8817 + 'PhabricatorCosChartFunction' => 'PhabricatorChartFunction', 8814 8818 'PhabricatorCountFact' => 'PhabricatorFact', 8815 8819 'PhabricatorCountdown' => array( 8816 8820 'PhabricatorCountdownDAO', ··· 10775 10779 'PhabricatorPolicyInterface', 10776 10780 ), 10777 10781 'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 10782 + 'PhabricatorScaleChartFunction' => 'PhabricatorChartFunction', 10778 10783 'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction', 10779 10784 'PhabricatorScopedEnv' => 'Phobject', 10780 10785 'PhabricatorSearchAbstractDocument' => 'Phobject', ··· 10864 10869 'PhabricatorSetupIssue' => 'Phobject', 10865 10870 'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample', 10866 10871 'PhabricatorSetupIssueView' => 'AphrontView', 10872 + 'PhabricatorShiftChartFunction' => 'PhabricatorChartFunction', 10867 10873 'PhabricatorShortSite' => 'PhabricatorSite', 10868 10874 'PhabricatorShowFiletreeSetting' => 'PhabricatorSelectSetting', 10869 10875 'PhabricatorSimpleEditType' => 'PhabricatorEditType',
+77 -1
src/applications/fact/chart/PhabricatorChartFunction.php
··· 5 5 6 6 private $xAxis; 7 7 private $yAxis; 8 - private $limit; 9 8 10 9 private $argumentParser; 10 + private $sourceFunction; 11 11 12 12 final public function getFunctionKey() { 13 13 return $this->getPhobjectClassConstant('FUNCTIONKEY', 32); ··· 44 44 $parser->setHaveAllArguments(true); 45 45 $parser->parseArguments(); 46 46 47 + $source_argument = $parser->getSourceFunctionArgument(); 48 + if ($source_argument) { 49 + $source_function = $this->getArgument($source_argument->getName()); 50 + $this->setSourceFunction($source_function); 51 + } 52 + 47 53 return $this; 48 54 } 49 55 ··· 71 77 return; 72 78 } 73 79 80 + protected function setSourceFunction(PhabricatorChartFunction $source) { 81 + $this->sourceFunction = $source; 82 + return $this; 83 + } 84 + 85 + protected function getSourceFunction() { 86 + return $this->sourceFunction; 87 + } 88 + 74 89 final public function setXAxis(PhabricatorChartAxis $x_axis) { 75 90 $this->xAxis = $x_axis; 76 91 return $this; ··· 87 102 88 103 final public function getYAxis() { 89 104 return $this->yAxis; 105 + } 106 + 107 + protected function canEvaluateFunction() { 108 + return false; 109 + } 110 + 111 + protected function evaluateFunction($x) { 112 + throw new PhutilMethodNotImplementedException(); 113 + } 114 + 115 + public function hasDomain() { 116 + if ($this->canEvaluateFunction()) { 117 + return false; 118 + } 119 + 120 + throw new PhutilMethodNotImplementedException(); 121 + } 122 + 123 + public function getDatapoints(PhabricatorChartDataQuery $query) { 124 + if ($this->canEvaluateFunction()) { 125 + $points = $this->newSourceDatapoints($query); 126 + foreach ($points as $key => $point) { 127 + $y = $point['y']; 128 + $y = $this->evaluateFunction($y); 129 + $points[$key]['y'] = $y; 130 + } 131 + 132 + return $points; 133 + } 134 + 135 + return $this->newDatapoints($query); 136 + } 137 + 138 + protected function newDatapoints(PhabricatorChartDataQuery $query) { 139 + throw new PhutilMethodNotImplementedException(); 140 + } 141 + 142 + protected function newSourceDatapoints(PhabricatorChartDataQuery $query) { 143 + $source = $this->getSourceFunction(); 144 + if ($source) { 145 + return $source->getDatapoints($query); 146 + } 147 + 148 + return $this->newDefaultDatapoints($query); 149 + } 150 + 151 + protected function newDefaultDatapoints(PhabricatorChartDataQuery $query) { 152 + $x_min = $query->getMinimumValue(); 153 + $x_max = $query->getMaximumValue(); 154 + $limit = $query->getLimit(); 155 + 156 + $points = array(); 157 + $steps = $this->newLinearSteps($x_min, $x_max, $limit); 158 + foreach ($steps as $step) { 159 + $points[] = array( 160 + 'x' => $step, 161 + 'y' => $step, 162 + ); 163 + } 164 + 165 + return $points; 90 166 } 91 167 92 168 protected function newLinearSteps($src, $dst, $count) {
+10
src/applications/fact/chart/PhabricatorChartFunctionArgument.php
··· 5 5 6 6 private $name; 7 7 private $type; 8 + private $isSourceFunction; 8 9 9 10 public function setName($name) { 10 11 $this->name = $name; ··· 37 38 38 39 public function getType() { 39 40 return $this->type; 41 + } 42 + 43 + public function setIsSourceFunction($is_source_function) { 44 + $this->isSourceFunction = $is_source_function; 45 + return $this; 46 + } 47 + 48 + public function getIsSourceFunction() { 49 + return $this->isSourceFunction; 40 50 } 41 51 42 52 public function newValue($value) {
+39
src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
··· 151 151 implode(', ', $argument_list)); 152 152 } 153 153 154 + public function getSourceFunctionArgument() { 155 + $required_type = 'function'; 156 + 157 + $sources = array(); 158 + foreach ($this->argumentMap as $key => $argument) { 159 + if (!$argument->getIsSourceFunction()) { 160 + continue; 161 + } 162 + 163 + if ($argument->getType() !== $required_type) { 164 + throw new Exception( 165 + pht( 166 + 'Function "%s" defines an argument "%s" which is marked as a '. 167 + 'source function, but the type of this argument is not "%s".', 168 + $this->getFunctionArgumentSignature(), 169 + $argument->getName(), 170 + $required_type)); 171 + } 172 + 173 + $sources[$key] = $argument; 174 + } 175 + 176 + if (!$sources) { 177 + return null; 178 + } 179 + 180 + if (count($sources) > 1) { 181 + throw new Exception( 182 + pht( 183 + 'Function "%s" defines more than one argument as a source '. 184 + 'function (arguments: %s). Functions must have zero or one '. 185 + 'source function.', 186 + $this->getFunctionArgumentSignature(), 187 + implode(', ', array_keys($sources)))); 188 + } 189 + 190 + return head($sources); 191 + } 192 + 154 193 }
+4 -20
src/applications/fact/chart/PhabricatorConstantChartFunction.php
··· 5 5 6 6 const FUNCTIONKEY = 'constant'; 7 7 8 - private $value; 9 - 10 8 protected function newArguments() { 11 9 return array( 12 10 $this->newArgument() ··· 15 13 ); 16 14 } 17 15 18 - public function getDatapoints(PhabricatorChartDataQuery $query) { 19 - $x_min = $query->getMinimumValue(); 20 - $x_max = $query->getMaximumValue(); 21 - 22 - $value = $this->getArgument('n'); 23 - 24 - $points = array(); 25 - $steps = $this->newLinearSteps($x_min, $x_max, 2); 26 - foreach ($steps as $step) { 27 - $points[] = array( 28 - 'x' => $step, 29 - 'y' => $value, 30 - ); 31 - } 32 - 33 - return $points; 16 + protected function canEvaluateFunction() { 17 + return true; 34 18 } 35 19 36 - public function hasDomain() { 37 - return false; 20 + protected function evaluateFunction($x) { 21 + return $this->getArgument('n'); 38 22 } 39 23 40 24 }
+25
src/applications/fact/chart/PhabricatorCosChartFunction.php
··· 1 + <?php 2 + 3 + final class PhabricatorCosChartFunction 4 + extends PhabricatorChartFunction { 5 + 6 + const FUNCTIONKEY = 'cos'; 7 + 8 + protected function newArguments() { 9 + return array( 10 + $this->newArgument() 11 + ->setName('x') 12 + ->setType('function') 13 + ->setIsSourceFunction(true), 14 + ); 15 + } 16 + 17 + protected function canEvaluateFunction() { 18 + return true; 19 + } 20 + 21 + protected function evaluateFunction($x) { 22 + return cos(deg2rad($x)); 23 + } 24 + 25 + }
+28
src/applications/fact/chart/PhabricatorScaleChartFunction.php
··· 1 + <?php 2 + 3 + final class PhabricatorScaleChartFunction 4 + extends PhabricatorChartFunction { 5 + 6 + const FUNCTIONKEY = 'scale'; 7 + 8 + protected function newArguments() { 9 + return array( 10 + $this->newArgument() 11 + ->setName('x') 12 + ->setType('function') 13 + ->setIsSourceFunction(true), 14 + $this->newArgument() 15 + ->setName('scale') 16 + ->setType('number'), 17 + ); 18 + } 19 + 20 + protected function canEvaluateFunction() { 21 + return true; 22 + } 23 + 24 + protected function evaluateFunction($x) { 25 + return $x * $this->getArgument('scale'); 26 + } 27 + 28 + }
+28
src/applications/fact/chart/PhabricatorShiftChartFunction.php
··· 1 + <?php 2 + 3 + final class PhabricatorShiftChartFunction 4 + extends PhabricatorChartFunction { 5 + 6 + const FUNCTIONKEY = 'shift'; 7 + 8 + protected function newArguments() { 9 + return array( 10 + $this->newArgument() 11 + ->setName('x') 12 + ->setType('function') 13 + ->setIsSourceFunction(true), 14 + $this->newArgument() 15 + ->setName('shift') 16 + ->setType('number'), 17 + ); 18 + } 19 + 20 + protected function canEvaluateFunction() { 21 + return true; 22 + } 23 + 24 + protected function evaluateFunction($x) { 25 + return $x * $this->getArgument('shift'); 26 + } 27 + 28 + }
+6 -15
src/applications/fact/chart/PhabricatorSinChartFunction.php
··· 9 9 return array( 10 10 $this->newArgument() 11 11 ->setName('x') 12 - ->setType('function'), 12 + ->setType('function') 13 + ->setIsSourceFunction(true), 13 14 ); 14 15 } 15 16 16 - protected function assignArguments(array $arguments) { 17 - $this->argument = $arguments[0]; 17 + protected function canEvaluateFunction() { 18 + return true; 18 19 } 19 20 20 - public function getDatapoints(PhabricatorChartDataQuery $query) { 21 - $points = $this->getArgument('x')->getDatapoints($query); 22 - 23 - foreach ($points as $key => $point) { 24 - $points[$key]['y'] = sin(deg2rad($points[$key]['y'])); 25 - } 26 - 27 - return $points; 28 - } 29 - 30 - public function hasDomain() { 31 - return false; 21 + protected function evaluateFunction($x) { 22 + return sin(deg2rad($x)); 32 23 } 33 24 34 25 }
+4 -17
src/applications/fact/chart/PhabricatorXChartFunction.php
··· 9 9 return array(); 10 10 } 11 11 12 - public function getDatapoints(PhabricatorChartDataQuery $query) { 13 - $x_min = $query->getMinimumValue(); 14 - $x_max = $query->getMaximumValue(); 15 - $limit = $query->getLimit(); 16 - 17 - $points = array(); 18 - $steps = $this->newLinearSteps($x_min, $x_max, $limit); 19 - foreach ($steps as $step) { 20 - $points[] = array( 21 - 'x' => $step, 22 - 'y' => $step, 23 - ); 24 - } 25 - 26 - return $points; 12 + protected function canEvaluateFunction() { 13 + return true; 27 14 } 28 15 29 - public function hasDomain() { 30 - return false; 16 + protected function evaluateFunction($x) { 17 + return $x; 31 18 } 32 19 33 20 }
+24
src/applications/fact/controller/PhabricatorFactChartController.php
··· 29 29 $functions[] = id(new PhabricatorSinChartFunction()) 30 30 ->setArguments(array($x_function)); 31 31 32 + $cos_function = id(new PhabricatorCosChartFunction()) 33 + ->setArguments(array($x_function)); 34 + 35 + $functions[] = id(new PhabricatorShiftChartFunction()) 36 + ->setArguments( 37 + array( 38 + array( 39 + 'scale', 40 + array( 41 + 'cos', 42 + array( 43 + 'scale', 44 + array('x'), 45 + 0.001, 46 + ), 47 + ), 48 + 10, 49 + ), 50 + 200, 51 + )); 52 + 32 53 list($domain_min, $domain_max) = $this->getDomain($functions); 33 54 34 55 $axis = id(new PhabricatorChartAxis()) ··· 83 104 'yMax' => $y_max, 84 105 ); 85 106 107 + // TODO: Move this back up, it's just down here for now to make 108 + // debugging easier so the main page throws a more visible exception when 109 + // something goes wrong. 86 110 if ($is_chart_mode) { 87 111 return $this->newChartResponse(); 88 112 }