mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-02 09:58:24 +01: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:
parent
45b3c23148
commit
954831f533
4 changed files with 151 additions and 64 deletions
|
@ -2649,6 +2649,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php',
|
'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php',
|
||||||
'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php',
|
'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php',
|
||||||
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
|
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
|
||||||
|
'PhabricatorChartFunction' => 'applications/fact/function/PhabricatorChartFunction.php',
|
||||||
'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php',
|
'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php',
|
||||||
'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php',
|
'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php',
|
||||||
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
|
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
|
||||||
|
@ -3203,6 +3204,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php',
|
'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php',
|
||||||
'PhabricatorFactChart' => 'applications/fact/storage/PhabricatorFactChart.php',
|
'PhabricatorFactChart' => 'applications/fact/storage/PhabricatorFactChart.php',
|
||||||
'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php',
|
'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php',
|
||||||
|
'PhabricatorFactChartFunction' => 'applications/fact/function/PhabricatorFactChartFunction.php',
|
||||||
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
|
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
|
||||||
'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
|
'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
|
||||||
'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
|
'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
|
||||||
|
@ -8619,6 +8621,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase',
|
'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase',
|
||||||
'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger',
|
'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger',
|
||||||
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
|
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
|
||||||
|
'PhabricatorChartFunction' => 'Phobject',
|
||||||
'PhabricatorChatLogApplication' => 'PhabricatorApplication',
|
'PhabricatorChatLogApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorChatLogChannel' => array(
|
'PhabricatorChatLogChannel' => array(
|
||||||
'PhabricatorChatLogDAO',
|
'PhabricatorChatLogDAO',
|
||||||
|
@ -9235,6 +9238,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorFactChartController' => 'PhabricatorFactController',
|
'PhabricatorFactChartController' => 'PhabricatorFactController',
|
||||||
|
'PhabricatorFactChartFunction' => 'PhabricatorChartFunction',
|
||||||
'PhabricatorFactController' => 'PhabricatorController',
|
'PhabricatorFactController' => 'PhabricatorController',
|
||||||
'PhabricatorFactCursor' => 'PhabricatorFactDAO',
|
'PhabricatorFactCursor' => 'PhabricatorFactDAO',
|
||||||
'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
|
||||||
|
|
|
@ -12,81 +12,38 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||||
$is_chart_mode = ($mode === 'chart');
|
$is_chart_mode = ($mode === 'chart');
|
||||||
$is_draw_mode = ($mode === 'draw');
|
$is_draw_mode = ($mode === 'draw');
|
||||||
|
|
||||||
$series = $request->getStr('y1');
|
$functions = array();
|
||||||
|
|
||||||
$facts = PhabricatorFact::getAllFacts();
|
$functions[] = id(new PhabricatorFactChartFunction())
|
||||||
$fact = idx($facts, $series);
|
->setArguments(array('tasks.count.create'));
|
||||||
|
|
||||||
if (!$fact) {
|
$functions[] = id(new PhabricatorFactChartFunction())
|
||||||
return new Aphront404Response();
|
->setArguments(array('tasks.open-count.create'));
|
||||||
}
|
|
||||||
|
|
||||||
$key_id = id(new PhabricatorFactKeyDimension())
|
|
||||||
->newDimensionID($fact->getKey());
|
|
||||||
if (!$key_id) {
|
|
||||||
return new Aphront404Response();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($is_chart_mode) {
|
if ($is_chart_mode) {
|
||||||
return $this->newChartResponse();
|
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();
|
$datasets = array();
|
||||||
|
foreach ($functions as $function) {
|
||||||
|
$function->loadData();
|
||||||
|
|
||||||
$datasets[] = array(
|
$points = $function->getDatapoints(2000);
|
||||||
'x' => array_keys($points),
|
|
||||||
'y' => array_values($points),
|
|
||||||
'color' => '#ff0000',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a dummy "y = x" dataset to prove we can draw multiple datasets.
|
$x = array();
|
||||||
$x_min = min(array_keys($points));
|
$y = array();
|
||||||
$x_max = max(array_keys($points));
|
|
||||||
$x_range = ($x_max - $x_min) / 4;
|
foreach ($points as $point) {
|
||||||
$linear = array();
|
$x[] = $point['x'];
|
||||||
foreach ($points as $x => $y) {
|
$y[] = $point['y'];
|
||||||
$linear[$x] = round(count($points) * (($x - $x_min) / $x_range));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$datasets[] = array(
|
$datasets[] = array(
|
||||||
'x' => array_keys($linear),
|
'x' => $x,
|
||||||
'y' => array_values($linear),
|
'y' => $y,
|
||||||
'color' => '#0000ff',
|
'color' => '#ff00ff',
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$y_min = 0;
|
$y_min = 0;
|
||||||
$y_max = 0;
|
$y_max = 0;
|
||||||
|
|
24
src/applications/fact/function/PhabricatorChartFunction.php
Normal file
24
src/applications/fact/function/PhabricatorChartFunction.php
Normal 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);
|
||||||
|
|
||||||
|
}
|
102
src/applications/fact/function/PhabricatorFactChartFunction.php
Normal file
102
src/applications/fact/function/PhabricatorFactChartFunction.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue