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

Separate chart functions into a class tree

Summary:
Depends on D20440. Ref T13279. Create a class to represent a chartable function: something we can get some data points out of.

Then, make the chart chart two functions.

For now, the only supported function is "fact(key)", which pulls data from the Facts ETL pipeline, identified by "key", and takes no other arguments.

In future changes, I plan to support things like "fact(tasks.open.project, PHID-PROJ-xyz)", "constant(1000)" (e.g. to draw a goal line), "sum(fact(...), fact(...))" (to combine data from several projects), and so on.

The UI may not expose this level of power for a while (or maybe ever) but until we get close enough to the UI that these features are a ton of extra work I'm going to try to keep things fairly flexible/modular.

Test Plan: {F6382286}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

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

+149 -62
+4
src/__phutil_library_map__.php
··· 2649 2649 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', 2650 2650 'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php', 2651 2651 'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', 2652 + 'PhabricatorChartFunction' => 'applications/fact/function/PhabricatorChartFunction.php', 2652 2653 'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php', 2653 2654 'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php', 2654 2655 'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php', ··· 3203 3204 'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php', 3204 3205 'PhabricatorFactChart' => 'applications/fact/storage/PhabricatorFactChart.php', 3205 3206 'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php', 3207 + 'PhabricatorFactChartFunction' => 'applications/fact/function/PhabricatorFactChartFunction.php', 3206 3208 'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php', 3207 3209 'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php', 3208 3210 'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php', ··· 8619 8621 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', 8620 8622 'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger', 8621 8623 'PhabricatorChangesetResponse' => 'AphrontProxyResponse', 8624 + 'PhabricatorChartFunction' => 'Phobject', 8622 8625 'PhabricatorChatLogApplication' => 'PhabricatorApplication', 8623 8626 'PhabricatorChatLogChannel' => array( 8624 8627 'PhabricatorChatLogDAO', ··· 9235 9238 'PhabricatorPolicyInterface', 9236 9239 ), 9237 9240 'PhabricatorFactChartController' => 'PhabricatorFactController', 9241 + 'PhabricatorFactChartFunction' => 'PhabricatorChartFunction', 9238 9242 'PhabricatorFactController' => 'PhabricatorController', 9239 9243 'PhabricatorFactCursor' => 'PhabricatorFactDAO', 9240 9244 'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
+19 -62
src/applications/fact/controller/PhabricatorFactChartController.php
··· 12 12 $is_chart_mode = ($mode === 'chart'); 13 13 $is_draw_mode = ($mode === 'draw'); 14 14 15 - $series = $request->getStr('y1'); 15 + $functions = array(); 16 16 17 - $facts = PhabricatorFact::getAllFacts(); 18 - $fact = idx($facts, $series); 17 + $functions[] = id(new PhabricatorFactChartFunction()) 18 + ->setArguments(array('tasks.count.create')); 19 19 20 - if (!$fact) { 21 - return new Aphront404Response(); 22 - } 23 - 24 - $key_id = id(new PhabricatorFactKeyDimension()) 25 - ->newDimensionID($fact->getKey()); 26 - if (!$key_id) { 27 - return new Aphront404Response(); 28 - } 20 + $functions[] = id(new PhabricatorFactChartFunction()) 21 + ->setArguments(array('tasks.open-count.create')); 29 22 30 23 if ($is_chart_mode) { 31 24 return $this->newChartResponse(); 32 25 } 33 26 34 - $table = $fact->newDatapoint(); 35 - $conn_r = $table->establishConnection('r'); 36 - $table_name = $table->getTableName(); 27 + $datasets = array(); 28 + foreach ($functions as $function) { 29 + $function->loadData(); 37 30 38 - $data = queryfx_all( 39 - $conn_r, 40 - 'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC', 41 - $table_name, 42 - $key_id); 31 + $points = $function->getDatapoints(2000); 43 32 44 - $points = array(); 45 - $sum = 0; 46 - foreach ($data as $key => $row) { 47 - $sum += (int)$row['value']; 48 - $points[(int)$row['epoch']] = $sum; 49 - } 33 + $x = array(); 34 + $y = array(); 50 35 51 - if (!$points) { 52 - throw new Exception('No data to show!'); 53 - } 54 - 55 - // Limit amount of data passed to browser. 56 - $count = count($points); 57 - $limit = 2000; 58 - if ($count > $limit) { 59 - $i = 0; 60 - $every = ceil($count / $limit); 61 - foreach ($points as $epoch => $sum) { 62 - $i++; 63 - if ($i % $every && $i != $count) { 64 - unset($points[$epoch]); 65 - } 36 + foreach ($points as $point) { 37 + $x[] = $point['x']; 38 + $y[] = $point['y']; 66 39 } 67 - } 68 40 69 - $datasets = array(); 70 - 71 - $datasets[] = array( 72 - 'x' => array_keys($points), 73 - 'y' => array_values($points), 74 - 'color' => '#ff0000', 75 - ); 76 - 77 - // Add a dummy "y = x" dataset to prove we can draw multiple datasets. 78 - $x_min = min(array_keys($points)); 79 - $x_max = max(array_keys($points)); 80 - $x_range = ($x_max - $x_min) / 4; 81 - $linear = array(); 82 - foreach ($points as $x => $y) { 83 - $linear[$x] = round(count($points) * (($x - $x_min) / $x_range)); 41 + $datasets[] = array( 42 + 'x' => $x, 43 + 'y' => $y, 44 + 'color' => '#ff00ff', 45 + ); 84 46 } 85 - $datasets[] = array( 86 - 'x' => array_keys($linear), 87 - 'y' => array_values($linear), 88 - 'color' => '#0000ff', 89 - ); 90 47 91 48 $y_min = 0; 92 49 $y_max = 0;
+24
src/applications/fact/function/PhabricatorChartFunction.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorChartFunction 4 + extends Phobject { 5 + 6 + final public function getFunctionKey() { 7 + return $this->getPhobjectClassConstant('FUNCTIONKEY', 32); 8 + } 9 + 10 + final public static function getAllFunctions() { 11 + return id(new PhutilClassMapQuery()) 12 + ->setAncestorClass(__CLASS__) 13 + ->setUniqueMethod('getFunctionKey') 14 + ->execute(); 15 + } 16 + 17 + final public function setArguments(array $arguments) { 18 + $this->newArguments($arguments); 19 + return $this; 20 + } 21 + 22 + abstract protected function newArguments(array $arguments); 23 + 24 + }
+102
src/applications/fact/function/PhabricatorFactChartFunction.php
··· 1 + <?php 2 + 3 + final class PhabricatorFactChartFunction 4 + extends PhabricatorChartFunction { 5 + 6 + const FUNCTIONKEY = 'fact'; 7 + 8 + private $factKey; 9 + private $fact; 10 + private $datapoints; 11 + 12 + protected function newArguments(array $arguments) { 13 + if (count($arguments) !== 1) { 14 + throw new Exception( 15 + pht( 16 + 'Chart function "fact(...)" expects one argument, got %s. '. 17 + 'Pass the key for a fact.', 18 + count($arguments))); 19 + } 20 + 21 + if (!is_string($arguments[0])) { 22 + throw new Exception( 23 + pht( 24 + 'First argument for "fact(...)" is invalid: expected string, '. 25 + 'got %s.', 26 + phutil_describe_type($arguments[0]))); 27 + } 28 + 29 + $facts = PhabricatorFact::getAllFacts(); 30 + $fact = idx($facts, $arguments[0]); 31 + 32 + if (!$fact) { 33 + throw new Exception( 34 + pht( 35 + 'Argument to "fact(...)" is invalid: "%s" is not a known fact '. 36 + 'key.', 37 + $arguments[0])); 38 + } 39 + 40 + $this->factKey = $arguments[0]; 41 + $this->fact = $fact; 42 + } 43 + 44 + public function loadData() { 45 + $fact = $this->fact; 46 + 47 + $key_id = id(new PhabricatorFactKeyDimension()) 48 + ->newDimensionID($fact->getKey()); 49 + if (!$key_id) { 50 + return; 51 + } 52 + 53 + $table = $fact->newDatapoint(); 54 + $conn = $table->establishConnection('r'); 55 + $table_name = $table->getTableName(); 56 + 57 + $data = queryfx_all( 58 + $conn, 59 + 'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC', 60 + $table_name, 61 + $key_id); 62 + if (!$data) { 63 + return; 64 + } 65 + 66 + $points = array(); 67 + 68 + $sum = 0; 69 + foreach ($data as $key => $row) { 70 + $sum += (int)$row['value']; 71 + $points[] = array( 72 + 'x' => (int)$row['epoch'], 73 + 'y' => $sum, 74 + ); 75 + } 76 + 77 + $this->datapoints = $points; 78 + } 79 + 80 + public function getDatapoints($limit) { 81 + $points = $this->datapoints; 82 + if (!$points) { 83 + return array(); 84 + } 85 + 86 + // If we have too many data points, throw away some of the data. 87 + $count = count($points); 88 + if ($count > $limit) { 89 + $ii = 0; 90 + $every = ceil($count / $limit); 91 + foreach ($points as $key => $point) { 92 + $ii++; 93 + if (($ii % $every) && ($ii != $count)) { 94 + unset($points[$key]); 95 + } 96 + } 97 + } 98 + 99 + return $points; 100 + } 101 + 102 + }