1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 17:28:51 +02:00

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
This commit is contained in:
epriestley 2019-04-17 05:15:15 -07:00
parent 45b3c23148
commit 954831f533
4 changed files with 151 additions and 64 deletions

View file

@ -2649,6 +2649,7 @@ phutil_register_library_map(array(
'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php',
'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php',
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
'PhabricatorChartFunction' => 'applications/fact/function/PhabricatorChartFunction.php',
'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php',
'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php',
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
@ -3203,6 +3204,7 @@ phutil_register_library_map(array(
'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php',
'PhabricatorFactChart' => 'applications/fact/storage/PhabricatorFactChart.php',
'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php',
'PhabricatorFactChartFunction' => 'applications/fact/function/PhabricatorFactChartFunction.php',
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
@ -8619,6 +8621,7 @@ phutil_register_library_map(array(
'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase',
'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger',
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
'PhabricatorChartFunction' => 'Phobject',
'PhabricatorChatLogApplication' => 'PhabricatorApplication',
'PhabricatorChatLogChannel' => array(
'PhabricatorChatLogDAO',
@ -9235,6 +9238,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
),
'PhabricatorFactChartController' => 'PhabricatorFactController',
'PhabricatorFactChartFunction' => 'PhabricatorChartFunction',
'PhabricatorFactController' => 'PhabricatorController',
'PhabricatorFactCursor' => 'PhabricatorFactDAO',
'PhabricatorFactDAO' => 'PhabricatorLiskDAO',

View file

@ -12,81 +12,38 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
$is_chart_mode = ($mode === 'chart');
$is_draw_mode = ($mode === 'draw');
$series = $request->getStr('y1');
$functions = array();
$facts = PhabricatorFact::getAllFacts();
$fact = idx($facts, $series);
$functions[] = id(new PhabricatorFactChartFunction())
->setArguments(array('tasks.count.create'));
if (!$fact) {
return new Aphront404Response();
}
$key_id = id(new PhabricatorFactKeyDimension())
->newDimensionID($fact->getKey());
if (!$key_id) {
return new Aphront404Response();
}
$functions[] = id(new PhabricatorFactChartFunction())
->setArguments(array('tasks.open-count.create'));
if ($is_chart_mode) {
return $this->newChartResponse();
}
$table = $fact->newDatapoint();
$conn_r = $table->establishConnection('r');
$table_name = $table->getTableName();
$data = queryfx_all(
$conn_r,
'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC',
$table_name,
$key_id);
$points = array();
$sum = 0;
foreach ($data as $key => $row) {
$sum += (int)$row['value'];
$points[(int)$row['epoch']] = $sum;
}
if (!$points) {
throw new Exception('No data to show!');
}
// Limit amount of data passed to browser.
$count = count($points);
$limit = 2000;
if ($count > $limit) {
$i = 0;
$every = ceil($count / $limit);
foreach ($points as $epoch => $sum) {
$i++;
if ($i % $every && $i != $count) {
unset($points[$epoch]);
}
}
}
$datasets = array();
foreach ($functions as $function) {
$function->loadData();
$datasets[] = array(
'x' => array_keys($points),
'y' => array_values($points),
'color' => '#ff0000',
);
$points = $function->getDatapoints(2000);
// Add a dummy "y = x" dataset to prove we can draw multiple datasets.
$x_min = min(array_keys($points));
$x_max = max(array_keys($points));
$x_range = ($x_max - $x_min) / 4;
$linear = array();
foreach ($points as $x => $y) {
$linear[$x] = round(count($points) * (($x - $x_min) / $x_range));
$x = array();
$y = array();
foreach ($points as $point) {
$x[] = $point['x'];
$y[] = $point['y'];
}
$datasets[] = array(
'x' => $x,
'y' => $y,
'color' => '#ff00ff',
);
}
$datasets[] = array(
'x' => array_keys($linear),
'y' => array_values($linear),
'color' => '#0000ff',
);
$y_min = 0;
$y_max = 0;

View file

@ -0,0 +1,24 @@
<?php
abstract class PhabricatorChartFunction
extends Phobject {
final public function getFunctionKey() {
return $this->getPhobjectClassConstant('FUNCTIONKEY', 32);
}
final public static function getAllFunctions() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getFunctionKey')
->execute();
}
final public function setArguments(array $arguments) {
$this->newArguments($arguments);
return $this;
}
abstract protected function newArguments(array $arguments);
}

View file

@ -0,0 +1,102 @@
<?php
final class PhabricatorFactChartFunction
extends PhabricatorChartFunction {
const FUNCTIONKEY = 'fact';
private $factKey;
private $fact;
private $datapoints;
protected function newArguments(array $arguments) {
if (count($arguments) !== 1) {
throw new Exception(
pht(
'Chart function "fact(...)" expects one argument, got %s. '.
'Pass the key for a fact.',
count($arguments)));
}
if (!is_string($arguments[0])) {
throw new Exception(
pht(
'First argument for "fact(...)" is invalid: expected string, '.
'got %s.',
phutil_describe_type($arguments[0])));
}
$facts = PhabricatorFact::getAllFacts();
$fact = idx($facts, $arguments[0]);
if (!$fact) {
throw new Exception(
pht(
'Argument to "fact(...)" is invalid: "%s" is not a known fact '.
'key.',
$arguments[0]));
}
$this->factKey = $arguments[0];
$this->fact = $fact;
}
public function loadData() {
$fact = $this->fact;
$key_id = id(new PhabricatorFactKeyDimension())
->newDimensionID($fact->getKey());
if (!$key_id) {
return;
}
$table = $fact->newDatapoint();
$conn = $table->establishConnection('r');
$table_name = $table->getTableName();
$data = queryfx_all(
$conn,
'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC',
$table_name,
$key_id);
if (!$data) {
return;
}
$points = array();
$sum = 0;
foreach ($data as $key => $row) {
$sum += (int)$row['value'];
$points[] = array(
'x' => (int)$row['epoch'],
'y' => $sum,
);
}
$this->datapoints = $points;
}
public function getDatapoints($limit) {
$points = $this->datapoints;
if (!$points) {
return array();
}
// If we have too many data points, throw away some of the data.
$count = count($points);
if ($count > $limit) {
$ii = 0;
$every = ceil($count / $limit);
foreach ($points as $key => $point) {
$ii++;
if (($ii % $every) && ($ii != $count)) {
unset($points[$key]);
}
}
}
return $points;
}
}