Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15420148
D20483.id48865.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
18 KB
Referenced Files
None
Subscribers
None
D20483.id48865.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2653,6 +2653,8 @@
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
'PhabricatorChartAxis' => 'applications/fact/chart/PhabricatorChartAxis.php',
'PhabricatorChartDataQuery' => 'applications/fact/chart/PhabricatorChartDataQuery.php',
+ 'PhabricatorChartDataset' => 'applications/fact/chart/PhabricatorChartDataset.php',
+ 'PhabricatorChartEngine' => 'applications/fact/engine/PhabricatorChartEngine.php',
'PhabricatorChartFunction' => 'applications/fact/chart/PhabricatorChartFunction.php',
'PhabricatorChartFunctionArgument' => 'applications/fact/chart/PhabricatorChartFunctionArgument.php',
'PhabricatorChartFunctionArgumentParser' => 'applications/fact/chart/PhabricatorChartFunctionArgumentParser.php',
@@ -8640,6 +8642,8 @@
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
'PhabricatorChartAxis' => 'Phobject',
'PhabricatorChartDataQuery' => 'Phobject',
+ 'PhabricatorChartDataset' => 'Phobject',
+ 'PhabricatorChartEngine' => 'Phobject',
'PhabricatorChartFunction' => 'Phobject',
'PhabricatorChartFunctionArgument' => 'Phobject',
'PhabricatorChartFunctionArgumentParser' => 'Phobject',
diff --git a/src/applications/fact/application/PhabricatorFactApplication.php b/src/applications/fact/application/PhabricatorFactApplication.php
--- a/src/applications/fact/application/PhabricatorFactApplication.php
+++ b/src/applications/fact/application/PhabricatorFactApplication.php
@@ -30,7 +30,9 @@
return array(
'/fact/' => array(
'' => 'PhabricatorFactHomeController',
- '(?<mode>chart|draw)/' => 'PhabricatorFactChartController',
+ 'chart/' => 'PhabricatorFactChartController',
+ 'chart/(?P<chartKey>[^/]+)/(?:(?P<mode>draw)/)?' =>
+ 'PhabricatorFactChartController',
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
),
);
diff --git a/src/applications/fact/chart/PhabricatorChartDataset.php b/src/applications/fact/chart/PhabricatorChartDataset.php
new file mode 100644
--- /dev/null
+++ b/src/applications/fact/chart/PhabricatorChartDataset.php
@@ -0,0 +1,37 @@
+<?php
+
+final class PhabricatorChartDataset
+ extends Phobject {
+
+ private $function;
+
+ public function getFunction() {
+ return $this->function;
+ }
+
+ public static function newFromDictionary(array $map) {
+ PhutilTypeSpec::checkMap(
+ $map,
+ array(
+ 'function' => 'list<wild>',
+ ));
+
+ $dataset = new self();
+
+ $dataset->function = id(new PhabricatorComposeChartFunction())
+ ->setArguments(array($map['function']));
+
+ return $dataset;
+ }
+
+ public function toDictionary() {
+ // Since we wrap the raw value in a "compose(...)", when deserializing,
+ // we need to unwrap it when serializing.
+ $function_raw = head($this->getFunction()->toDictionary());
+
+ return array(
+ 'function' => $function_raw,
+ );
+ }
+
+}
diff --git a/src/applications/fact/chart/PhabricatorChartFunction.php b/src/applications/fact/chart/PhabricatorChartFunction.php
--- a/src/applications/fact/chart/PhabricatorChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorChartFunction.php
@@ -43,6 +43,10 @@
return $this;
}
+ public function toDictionary() {
+ return $this->getArgumentParser()->getRawArguments();
+ }
+
public function getSubfunctions() {
$result = array();
$result[] = $this;
diff --git a/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
--- a/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
+++ b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
@@ -103,6 +103,10 @@
return array_values($this->argumentMap);
}
+ public function getRawArguments() {
+ return $this->rawArguments;
+ }
+
public function parseArguments() {
$have_count = count($this->rawArguments);
$want_count = count($this->argumentMap);
diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php
--- a/src/applications/fact/controller/PhabricatorFactChartController.php
+++ b/src/applications/fact/controller/PhabricatorFactChartController.php
@@ -5,13 +5,60 @@
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
+ $chart_key = $request->getURIData('chartKey');
+ if ($chart_key === null) {
+ return $this->newDemoChart();
+ }
+
+ $chart = id(new PhabricatorFactChart())->loadOneWhere(
+ 'chartKey = %s',
+ $chart_key);
+ if (!$chart) {
+ return new Aphront404Response();
+ }
+
+ $engine = id(new PhabricatorChartEngine())
+ ->setViewer($viewer)
+ ->setChart($chart);
+
// When drawing a chart, we send down a placeholder piece of HTML first,
// then fetch the data via async request. Determine if we're drawing
// the structure or actually pulling the data.
$mode = $request->getURIData('mode');
- $is_chart_mode = ($mode === 'chart');
$is_draw_mode = ($mode === 'draw');
+ // TODO: For now, always pull the data. We'll throw it away if we're just
+ // drawing the frame, but this makes errors easier to debug.
+ $chart_data = $engine->newChartData();
+
+ if ($is_draw_mode) {
+ return id(new AphrontAjaxResponse())->setContent($chart_data);
+ }
+
+ $chart_view = $engine->newChartView();
+ return $this->newChartResponse($chart_view);
+ }
+
+ private function newChartResponse($chart_view) {
+ $box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Chart'))
+ ->appendChild($chart_view);
+
+ $crumbs = $this->buildApplicationCrumbs()
+ ->addTextCrumb(pht('Chart'))
+ ->setBorder(true);
+
+ $title = pht('Chart');
+
+ return $this->newPage()
+ ->setTitle($title)
+ ->setCrumbs($crumbs)
+ ->appendChild($box);
+ }
+
+ private function newDemoChart() {
+ $viewer = $this->getViewer();
+
$argvs = array();
$argvs[] = array('fact', 'tasks.count.create');
@@ -40,165 +87,24 @@
array('shift', 800),
);
- $functions = array();
- foreach ($argvs as $argv) {
- $functions[] = id(new PhabricatorComposeChartFunction())
- ->setArguments(array($argv));
- }
-
- $subfunctions = array();
- foreach ($functions as $function) {
- foreach ($function->getSubfunctions() as $subfunction) {
- $subfunctions[] = $subfunction;
- }
- }
-
- foreach ($subfunctions as $subfunction) {
- $subfunction->loadData();
- }
-
- list($domain_min, $domain_max) = $this->getDomain($functions);
-
- $axis = id(new PhabricatorChartAxis())
- ->setMinimumValue($domain_min)
- ->setMaximumValue($domain_max);
-
- $data_query = id(new PhabricatorChartDataQuery())
- ->setMinimumValue($domain_min)
- ->setMaximumValue($domain_max)
- ->setLimit(2000);
-
$datasets = array();
- foreach ($functions as $function) {
- $points = $function->newDatapoints($data_query);
-
- $x = array();
- $y = array();
-
- foreach ($points as $point) {
- $x[] = $point['x'];
- $y[] = $point['y'];
- }
-
- $datasets[] = array(
- 'x' => $x,
- 'y' => $y,
- 'color' => '#ff00ff',
- );
- }
-
-
- $y_min = 0;
- $y_max = 0;
- foreach ($datasets as $dataset) {
- if (!$dataset['y']) {
- continue;
- }
-
- $y_min = min($y_min, min($dataset['y']));
- $y_max = max($y_max, max($dataset['y']));
- }
-
- $chart_data = array(
- 'datasets' => $datasets,
- 'xMin' => $domain_min,
- 'xMax' => $domain_max,
- 'yMin' => $y_min,
- 'yMax' => $y_max,
- );
-
- // TODO: Move this back up, it's just down here for now to make
- // debugging easier so the main page throws a more visible exception when
- // something goes wrong.
- if ($is_chart_mode) {
- return $this->newChartResponse();
- }
-
- return id(new AphrontAjaxResponse())->setContent($chart_data);
- }
-
- private function newChartResponse() {
- $request = $this->getRequest();
- $chart_node_id = celerity_generate_unique_node_id();
-
- $chart_view = phutil_tag(
- 'div',
- array(
- 'id' => $chart_node_id,
- 'style' => 'background: #ffffff; '.
- 'height: 480px; ',
- ),
- '');
-
- $data_uri = $request->getRequestURI();
- $data_uri->setPath('/fact/draw/');
-
- Javelin::initBehavior(
- 'line-chart',
- array(
- 'chartNodeID' => $chart_node_id,
- 'dataURI' => (string)$data_uri,
- ));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Chart'))
- ->appendChild($chart_view);
-
- $crumbs = $this->buildApplicationCrumbs()
- ->addTextCrumb(pht('Chart'))
- ->setBorder(true);
-
- $title = pht('Chart');
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($box);
-
- }
-
- private function getDomain(array $functions) {
- $domain_min_list = null;
- $domain_max_list = null;
-
- foreach ($functions as $function) {
- $domain = $function->getDomain();
-
- list($function_min, $function_max) = $domain;
-
- if ($function_min !== null) {
- $domain_min_list[] = $function_min;
- }
-
- if ($function_max !== null) {
- $domain_max_list[] = $function_max;
- }
- }
-
- $domain_min = null;
- $domain_max = null;
-
- if ($domain_min_list) {
- $domain_min = min($domain_min_list);
- }
-
- if ($domain_max_list) {
- $domain_max = max($domain_max_list);
+ foreach ($argvs as $argv) {
+ $datasets[] = PhabricatorChartDataset::newFromDictionary(
+ array(
+ 'function' => $argv,
+ ));
}
- // If we don't have any domain data from the actual functions, pick a
- // plausible domain automatically.
+ $chart = id(new PhabricatorFactChart())
+ ->setDatasets($datasets);
- if ($domain_max === null) {
- $domain_max = PhabricatorTime::getNow();
- }
+ $engine = id(new PhabricatorChartEngine())
+ ->setViewer($viewer)
+ ->setChart($chart);
- if ($domain_min === null) {
- $domain_min = $domain_max - phutil_units('365 days in seconds');
- }
+ $chart = $engine->getStoredChart();
- return array($domain_min, $domain_max);
+ return id(new AphrontRedirectResponse())->setURI($chart->getURI());
}
-
}
diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/engine/PhabricatorChartEngine.php
copy from src/applications/fact/controller/PhabricatorFactChartController.php
copy to src/applications/fact/engine/PhabricatorChartEngine.php
--- a/src/applications/fact/controller/PhabricatorFactChartController.php
+++ b/src/applications/fact/engine/PhabricatorChartEngine.php
@@ -1,49 +1,107 @@
<?php
-final class PhabricatorFactChartController extends PhabricatorFactController {
+final class PhabricatorChartEngine
+ extends Phobject {
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
+ private $viewer;
+ private $chart;
+ private $storedChart;
- // When drawing a chart, we send down a placeholder piece of HTML first,
- // then fetch the data via async request. Determine if we're drawing
- // the structure or actually pulling the data.
- $mode = $request->getURIData('mode');
- $is_chart_mode = ($mode === 'chart');
- $is_draw_mode = ($mode === 'draw');
+ public function setViewer(PhabricatorUser $viewer) {
+ $this->viewer = $viewer;
+ return $this;
+ }
+
+ public function getViewer() {
+ return $this->viewer;
+ }
- $argvs = array();
+ public function setChart(PhabricatorFactChart $chart) {
+ $this->chart = $chart;
+ return $this;
+ }
- $argvs[] = array('fact', 'tasks.count.create');
+ public function getChart() {
+ return $this->chart;
+ }
- $argvs[] = array('constant', 360);
+ public function getStoredChart() {
+ if (!$this->storedChart) {
+ $chart = $this->getChart();
+ $chart_key = $chart->getChartKey();
+ if (!$chart_key) {
+ $chart_key = $chart->newChartKey();
+
+ $stored_chart = id(new PhabricatorFactChart())->loadOneWhere(
+ 'chartKey = %s',
+ $chart_key);
+ if ($stored_chart) {
+ $chart = $stored_chart;
+ } else {
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+
+ try {
+ $chart->save();
+ } catch (AphrontDuplicateKeyQueryException $ex) {
+ $chart = id(new PhabricatorFactChart())->loadOneWhere(
+ 'chartKey = %s',
+ $chart_key);
+ if (!$chart) {
+ throw new Exception(
+ pht(
+ 'Failed to load chart with key "%s" after key collision. '.
+ 'This should not be possible.',
+ $chart_key));
+ }
+ }
+
+ unset($unguarded);
+ }
+ $this->setChart($chart);
+ }
- $argvs[] = array('fact', 'tasks.open-count.create');
+ $this->storedChart = $chart;
+ }
- $argvs[] = array(
- 'sum',
+ return $this->storedChart;
+ }
+
+ public function newChartView() {
+ $chart = $this->getStoredChart();
+ $chart_key = $chart->getChartKey();
+
+ $chart_node_id = celerity_generate_unique_node_id();
+
+ $chart_view = phutil_tag(
+ 'div',
array(
- 'accumulate',
- array('fact', 'tasks.count.create'),
+ 'id' => $chart_node_id,
+ 'style' => 'background: #ffffff; '.
+ 'height: 480px; ',
),
+ '');
+
+ $data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key);
+
+ Javelin::initBehavior(
+ 'line-chart',
array(
- 'accumulate',
- array('fact', 'tasks.open-count.create'),
- ),
- );
+ 'chartNodeID' => $chart_node_id,
+ 'dataURI' => (string)$data_uri,
+ ));
- $argvs[] = array(
- 'compose',
- array('scale', 0.001),
- array('cos'),
- array('scale', 100),
- array('shift', 800),
- );
+ return $chart_view;
+ }
+
+ public function newChartData() {
+ $chart = $this->getStoredChart();
+ $chart_key = $chart->getChartKey();
+
+ $datasets = $chart->getDatasets();
$functions = array();
- foreach ($argvs as $argv) {
- $functions[] = id(new PhabricatorComposeChartFunction())
- ->setArguments(array($argv));
+ foreach ($datasets as $dataset) {
+ $functions[] = $dataset->getFunction();
}
$subfunctions = array();
@@ -107,54 +165,7 @@
'yMax' => $y_max,
);
- // TODO: Move this back up, it's just down here for now to make
- // debugging easier so the main page throws a more visible exception when
- // something goes wrong.
- if ($is_chart_mode) {
- return $this->newChartResponse();
- }
-
- return id(new AphrontAjaxResponse())->setContent($chart_data);
- }
-
- private function newChartResponse() {
- $request = $this->getRequest();
- $chart_node_id = celerity_generate_unique_node_id();
-
- $chart_view = phutil_tag(
- 'div',
- array(
- 'id' => $chart_node_id,
- 'style' => 'background: #ffffff; '.
- 'height: 480px; ',
- ),
- '');
-
- $data_uri = $request->getRequestURI();
- $data_uri->setPath('/fact/draw/');
-
- Javelin::initBehavior(
- 'line-chart',
- array(
- 'chartNodeID' => $chart_node_id,
- 'dataURI' => (string)$data_uri,
- ));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Chart'))
- ->appendChild($chart_view);
-
- $crumbs = $this->buildApplicationCrumbs()
- ->addTextCrumb(pht('Chart'))
- ->setBorder(true);
-
- $title = pht('Chart');
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($box);
-
+ return $chart_data;
}
private function getDomain(array $functions) {
@@ -200,5 +211,4 @@
return array($domain_min, $domain_max);
}
-
}
diff --git a/src/applications/fact/storage/PhabricatorFactChart.php b/src/applications/fact/storage/PhabricatorFactChart.php
--- a/src/applications/fact/storage/PhabricatorFactChart.php
+++ b/src/applications/fact/storage/PhabricatorFactChart.php
@@ -7,6 +7,8 @@
protected $chartKey;
protected $chartParameters = array();
+ private $datasets;
+
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
@@ -33,6 +35,12 @@
return idx($this->chartParameters, $key, $default);
}
+ public function newChartKey() {
+ $digest = serialize($this->chartParameters);
+ $digest = PhabricatorHash::digestForIndex($digest);
+ return $digest;
+ }
+
public function save() {
if ($this->getID()) {
throw new Exception(
@@ -41,14 +49,46 @@
'overwrite an existing chart configuration.'));
}
- $digest = serialize($this->chartParameters);
- $digest = PhabricatorHash::digestForIndex($digest);
-
- $this->chartKey = $digest;
+ $this->chartKey = $this->newChartKey();
return parent::save();
}
+ public function setDatasets(array $datasets) {
+ assert_instances_of($datasets, 'PhabricatorChartDataset');
+
+ $dataset_list = array();
+ foreach ($datasets as $dataset) {
+ $dataset_list[] = $dataset->toDictionary();
+ }
+
+ $this->setChartParameter('datasets', $dataset_list);
+ $this->datasets = null;
+
+ return $this;
+ }
+
+ public function getDatasets() {
+ if ($this->datasets === null) {
+ $this->datasets = $this->newDatasets();
+ }
+ return $this->datasets;
+ }
+
+ private function newDatasets() {
+ $datasets = $this->getChartParameter('datasets', array());
+
+ foreach ($datasets as $key => $dataset) {
+ $datasets[$key] = PhabricatorChartDataset::newFromDictionary($dataset);
+ }
+
+ return $datasets;
+ }
+
+ public function getURI() {
+ return urisprintf('/fact/chart/%s/', $this->getChartKey());
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 22, 11:45 AM (3 d, 11 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7713081
Default Alt Text
D20483.id48865.diff (18 KB)
Attached To
Mode
D20483: Render charts from storage instead of just one ad-hoc hard-coded chart
Attached
Detach File
Event Timeline
Log In to Comment