Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php b/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
index 95392a029f..4a91aad635 100644
--- a/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
+++ b/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
@@ -1,170 +1,167 @@
<?php
final class PhabricatorChartStackedAreaDataset
extends PhabricatorChartDataset {
const DATASETKEY = 'stacked-area';
protected function newChartDisplayData(
PhabricatorChartDataQuery $data_query) {
$functions = $this->getFunctions();
$function_points = array();
foreach ($functions as $function_idx => $function) {
$function_points[$function_idx] = array();
$datapoints = $function->newDatapoints($data_query);
foreach ($datapoints as $point) {
$x = $point['x'];
$function_points[$function_idx][$x] = $point;
}
}
$raw_points = $function_points;
// We need to define every function we're drawing at every point where
// any of the functions we're drawing are defined. If we don't, we'll
// end up with weird gaps or overlaps between adjacent areas, and won't
// know how much we need to lift each point above the baseline when
// stacking the functions on top of one another.
$must_define = array();
foreach ($function_points as $function_idx => $points) {
foreach ($points as $x => $point) {
$must_define[$x] = $x;
}
}
ksort($must_define);
foreach ($functions as $function_idx => $function) {
$missing = array();
foreach ($must_define as $x) {
if (!isset($function_points[$function_idx][$x])) {
$missing[$x] = true;
}
}
if (!$missing) {
continue;
}
$points = $function_points[$function_idx];
$values = array_keys($points);
$cursor = -1;
$length = count($values);
foreach ($missing as $x => $ignored) {
// Move the cursor forward until we find the last point before "x"
// which is defined.
while ($cursor + 1 < $length && $values[$cursor + 1] < $x) {
$cursor++;
}
// If this new point is to the left of all defined points, we'll
// assume the value is 0. If the point is to the right of all defined
// points, we assume the value is the same as the last known value.
// If it's between two defined points, we average them.
if ($cursor < 0) {
$y = 0;
} else if ($cursor + 1 < $length) {
$xmin = $values[$cursor];
$xmax = $values[$cursor + 1];
$ymin = $points[$xmin]['y'];
$ymax = $points[$xmax]['y'];
// Fill in the missing point by creating a linear interpolation
// between the two adjacent points.
$distance = ($x - $xmin) / ($xmax - $xmin);
$y = $ymin + (($ymax - $ymin) * $distance);
} else {
$xmin = $values[$cursor];
$y = $function_points[$function_idx][$xmin]['y'];
}
$function_points[$function_idx][$x] = array(
'x' => $x,
'y' => $y,
);
}
ksort($function_points[$function_idx]);
}
$range_min = null;
$range_max = null;
$series = array();
$baseline = array();
foreach ($function_points as $function_idx => $points) {
$below = idx($function_points, $function_idx - 1);
$bounds = array();
foreach ($points as $x => $point) {
if (!isset($baseline[$x])) {
$baseline[$x] = 0;
}
$y0 = $baseline[$x];
$baseline[$x] += $point['y'];
$y1 = $baseline[$x];
$bounds[] = array(
'x' => $x,
'y0' => $y0,
'y1' => $y1,
);
if (isset($raw_points[$function_idx][$x])) {
$raw_points[$function_idx][$x]['y1'] = $y1;
}
if ($range_min === null) {
$range_min = $y0;
}
$range_min = min($range_min, $y0, $y1);
if ($range_max === null) {
$range_max = $y1;
}
$range_max = max($range_max, $y0, $y1);
}
$series[] = $bounds;
}
$events = array();
foreach ($raw_points as $function_idx => $points) {
$event_list = array();
foreach ($points as $point) {
$event_list[] = $point;
}
$events[] = $event_list;
}
$wire_labels = array();
foreach ($functions as $function_key => $function) {
$label = $function->getFunctionLabel();
-
- $label->setName(pht('Important Data %s', $function_key));
-
$wire_labels[] = $label->toWireFormat();
}
$result = array(
'type' => $this->getDatasetTypeKey(),
'data' => $series,
'events' => $events,
'labels' => $wire_labels,
);
return id(new PhabricatorChartDisplayData())
->setWireData($result)
->setRange(new PhabricatorChartInterval($range_min, $range_max));
}
}
diff --git a/src/applications/fact/engine/PhabricatorChartEngine.php b/src/applications/fact/engine/PhabricatorChartEngine.php
index d0ccca2034..f723633d6a 100644
--- a/src/applications/fact/engine/PhabricatorChartEngine.php
+++ b/src/applications/fact/engine/PhabricatorChartEngine.php
@@ -1,48 +1,97 @@
<?php
abstract class PhabricatorChartEngine
extends Phobject {
private $viewer;
+ private $engineParameters = array();
+
+ const KEY_ENGINE = 'engineKey';
+ const KEY_PARAMETERS = 'engineParameters';
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
+ final protected function setEngineParameter($key, $value) {
+ $this->engineParameters[$key] = $value;
+ return $this;
+ }
+
+ final protected function getEngineParameter($key, $default = null) {
+ return idx($this->engineParameters, $key, $default);
+ }
+
+ final protected function getEngineParameters() {
+ return $this->engineParameters;
+ }
+
+ final public static function newFromChart(PhabricatorFactChart $chart) {
+ $engine_key = $chart->getChartParameter(self::KEY_ENGINE);
+
+ $engine_map = self::getAllChartEngines();
+ if (!isset($engine_map[$engine_key])) {
+ throw new Exception(
+ pht(
+ 'Chart uses unknown engine key ("%s") and can not be rendered.',
+ $engine_key));
+ }
+
+ return clone id($engine_map[$engine_key]);
+ }
+
+ final public static function getAllChartEngines() {
+ return id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setUniqueMethod('getChartEngineKey')
+ ->execute();
+ }
+
final public function getChartEngineKey() {
return $this->getPhobjectClassConstant('CHARTENGINEKEY', 32);
}
- abstract protected function newChart();
+ final public function buildChart(PhabricatorFactChart $chart) {
+ $map = $chart->getChartParameter(self::KEY_PARAMETERS, array());
+ return $this->newChart($chart, $map);
+ }
+
+ abstract protected function newChart(PhabricatorFactChart $chart, array $map);
- final public function buildChart() {
+ final public function buildChartPanel() {
$viewer = $this->getViewer();
- $chart = $this->newChart();
+ $parameters = $this->getEngineParameters();
+
+ $chart = id(new PhabricatorFactChart())
+ ->setChartParameter(self::KEY_ENGINE, $this->getChartEngineKey())
+ ->setChartParameter(self::KEY_PARAMETERS, $this->getEngineParameters());
$rendering_engine = id(new PhabricatorChartRenderingEngine())
->setViewer($viewer)
->setChart($chart);
- return $rendering_engine->getStoredChart();
- }
-
- final public function buildChartPanel() {
- $chart = $this->buildChart();
+ $chart = $rendering_engine->getStoredChart();
$panel_type = id(new PhabricatorDashboardChartPanelType())
->getPanelTypeKey();
$chart_panel = id(new PhabricatorDashboardPanel())
->setPanelType($panel_type)
->setProperty('chartKey', $chart->getChartKey());
return $chart_panel;
}
+ final protected function newFunction($name /* , ... */) {
+ $argv = func_get_args();
+ return id(new PhabricatorComposeChartFunction())
+ ->setArguments(array($argv));
+ }
+
}
diff --git a/src/applications/fact/engine/PhabricatorChartRenderingEngine.php b/src/applications/fact/engine/PhabricatorChartRenderingEngine.php
index f241d45628..b328241ea6 100644
--- a/src/applications/fact/engine/PhabricatorChartRenderingEngine.php
+++ b/src/applications/fact/engine/PhabricatorChartRenderingEngine.php
@@ -1,209 +1,213 @@
<?php
final class PhabricatorChartRenderingEngine
extends Phobject {
private $viewer;
private $chart;
private $storedChart;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setChart(PhabricatorFactChart $chart) {
$this->chart = $chart;
return $this;
}
public function getChart() {
return $this->chart;
}
public function loadChart($chart_key) {
$chart = id(new PhabricatorFactChart())->loadOneWhere(
'chartKey = %s',
$chart_key);
if ($chart) {
$this->setChart($chart);
}
return $chart;
}
public static function getChartURI($chart_key) {
return id(new PhabricatorFactChart())
->setChartKey($chart_key)
->getURI();
}
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);
}
$this->storedChart = $chart;
}
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(
'id' => $chart_node_id,
'class' => 'chart-hardpoint',
));
$data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key);
Javelin::initBehavior(
'line-chart',
array(
'chartNodeID' => $chart_node_id,
'dataURI' => (string)$data_uri,
));
return $chart_view;
}
public function newChartData() {
$chart = $this->getStoredChart();
$chart_key = $chart->getChartKey();
+ $chart_engine = PhabricatorChartEngine::newFromChart($chart)
+ ->setViewer($this->getViewer());
+ $chart_engine->buildChart($chart);
+
$datasets = $chart->getDatasets();
$functions = array();
foreach ($datasets as $dataset) {
foreach ($dataset->getFunctions() as $function) {
$functions[] = $function;
}
}
$subfunctions = array();
foreach ($functions as $function) {
foreach ($function->getSubfunctions() as $subfunction) {
$subfunctions[] = $subfunction;
}
}
foreach ($subfunctions as $subfunction) {
$subfunction->loadData();
}
$domain = $this->getDomain($functions);
$axis = id(new PhabricatorChartAxis())
->setMinimumValue($domain->getMin())
->setMaximumValue($domain->getMax());
$data_query = id(new PhabricatorChartDataQuery())
->setMinimumValue($domain->getMin())
->setMaximumValue($domain->getMax())
->setLimit(2000);
$wire_datasets = array();
$ranges = array();
foreach ($datasets as $dataset) {
$display_data = $dataset->getChartDisplayData($data_query);
$ranges[] = $display_data->getRange();
$wire_datasets[] = $display_data->getWireData();
}
$range = $this->getRange($ranges);
$chart_data = array(
'datasets' => $wire_datasets,
'xMin' => $domain->getMin(),
'xMax' => $domain->getMax(),
'yMin' => $range->getMin(),
'yMax' => $range->getMax(),
);
return $chart_data;
}
private function getDomain(array $functions) {
$domains = array();
foreach ($functions as $function) {
$domains[] = $function->getDomain();
}
$domain = PhabricatorChartInterval::newFromIntervalList($domains);
// If we don't have any domain data from the actual functions, pick a
// plausible domain automatically.
if ($domain->getMax() === null) {
$domain->setMax(PhabricatorTime::getNow());
}
if ($domain->getMin() === null) {
$domain->setMin($domain->getMax() - phutil_units('365 days in seconds'));
}
return $domain;
}
private function getRange(array $ranges) {
$range = PhabricatorChartInterval::newFromIntervalList($ranges);
// Start the Y axis at 0 unless the chart has negative values.
$min = $range->getMin();
if ($min === null || $min >= 0) {
$range->setMin(0);
}
// If there's no maximum value, just pick a plausible default.
$max = $range->getMax();
if ($max === null) {
$range->setMax($range->getMin() + 100);
}
return $range;
}
}
diff --git a/src/applications/fact/storage/PhabricatorFactChart.php b/src/applications/fact/storage/PhabricatorFactChart.php
index 515b5f0a72..0fb04ccaa5 100644
--- a/src/applications/fact/storage/PhabricatorFactChart.php
+++ b/src/applications/fact/storage/PhabricatorFactChart.php
@@ -1,109 +1,88 @@
<?php
final class PhabricatorFactChart
extends PhabricatorFactDAO
implements PhabricatorPolicyInterface {
protected $chartKey;
protected $chartParameters = array();
- private $datasets;
+ private $datasets = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'chartParameters' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'chartKey' => 'bytes12',
),
self::CONFIG_KEY_SCHEMA => array(
'key_chart' => array(
'columns' => array('chartKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function setChartParameter($key, $value) {
$this->chartParameters[$key] = $value;
return $this;
}
public function getChartParameter($key, $default = null) {
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(
pht(
'Chart configurations are not mutable. You can not update or '.
'overwrite an existing chart configuration.'));
}
$this->chartKey = $this->newChartKey();
return parent::save();
}
- public function setDatasets(array $datasets) {
+ public function attachDatasets(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;
-
+ $this->datasets = $datasets;
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;
+ return $this->assertAttached($this->datasets);
}
public function getURI() {
return urisprintf('/fact/chart/%s/', $this->getChartKey());
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
}
diff --git a/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php b/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
index fd4a872bbd..35496330e8 100644
--- a/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
+++ b/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
@@ -1,67 +1,101 @@
<?php
final class PhabricatorProjectBurndownChartEngine
extends PhabricatorChartEngine {
const CHARTENGINEKEY = 'project.burndown';
- private $projects;
-
public function setProjects(array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
-
- $this->projects = $projects;
-
- return $this;
+ $project_phids = mpull($projects, 'getPHID');
+ return $this->setEngineParameter('projectPHIDs', $project_phids);
}
- public function getProjects() {
- return $this->projects;
- }
+ protected function newChart(PhabricatorFactChart $chart, array $map) {
+ $viewer = $this->getViewer();
+
+ $map = $map + array(
+ 'projectPHIDs' => array(),
+ );
- protected function newChart() {
- if ($this->projects !== null) {
- $project_phids = mpull($this->projects, 'getPHID');
+ if ($map['projectPHIDs']) {
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($map['projectPHIDs'])
+ ->execute();
+ $project_phids = mpull($projects, 'getPHID');
} else {
- $project_phids = null;
+ $project_phids = array();
}
- $argvs = array();
+ $functions = array();
if ($project_phids) {
foreach ($project_phids as $project_phid) {
- $argvs[] = array(
+ $function = $this->newFunction(
'accumulate',
- array('fact', 'tasks.open-count.create.project', $project_phid),
- );
- $argvs[] = array(
+ array('fact', 'tasks.open-count.create.project', $project_phid));
+
+ $function->getFunctionLabel()
+ ->setName(pht('Tasks Created'))
+ ->setColor('rgba(0, 0, 200, 1)')
+ ->setFillColor('rgba(0, 0, 200, 0.15)');
+
+ $functions[] = $function;
+
+
+ $function = $this->newFunction(
'accumulate',
- array('fact', 'tasks.open-count.status.project', $project_phid),
- );
- $argvs[] = array(
+ array('fact', 'tasks.open-count.status.project', $project_phid));
+
+ $function->getFunctionLabel()
+ ->setName(pht('Tasks Closed / Reopened'))
+ ->setColor('rgba(200, 0, 200, 1)')
+ ->setFillColor('rgba(200, 0, 200, 0.15)');
+
+ $functions[] = $function;
+
+
+ $function = $this->newFunction(
'accumulate',
- array('fact', 'tasks.open-count.assign.project', $project_phid),
- );
+ array('fact', 'tasks.open-count.assign.project', $project_phid));
+
+ $function->getFunctionLabel()
+ ->setName(pht('Tasks Rescoped'))
+ ->setColor('rgba(0, 200, 200, 1)')
+ ->setFillColor('rgba(0, 200, 200, 0.15)');
+
+ $functions[] = $function;
}
} else {
- $argvs[] = array('accumulate', array('fact', 'tasks.open-count.create'));
- $argvs[] = array('accumulate', array('fact', 'tasks.open-count.status'));
- }
+ $function = $this->newFunction(
+ 'accumulate',
+ array('fact', 'tasks.open-count.create'));
- $functions = array();
- foreach ($argvs as $argv) {
- $functions[] = id(new PhabricatorComposeChartFunction())
- ->setArguments(array($argv));
+ $function->getFunctionLabel()
+ ->setName(pht('Tasks Created'))
+ ->setColor('rgba(0, 200, 200, 1)')
+ ->setFillColor('rgba(0, 200, 200, 0.15)');
+
+ $functions[] = $function;
+
+ $function = $this->newFunction(
+ 'accumulate',
+ array('fact', 'tasks.open-count.status'));
+
+ $function->getFunctionLabel()
+ ->setName(pht('Tasks Closed / Reopened'))
+ ->setColor('rgba(200, 0, 200, 1)')
+ ->setFillColor('rgba(200, 0, 200, 0.15)');
+
+ $functions[] = $function;
}
$datasets = array();
$datasets[] = id(new PhabricatorChartStackedAreaDataset())
->setFunctions($functions);
- $chart = id(new PhabricatorFactChart())
- ->setDatasets($datasets);
-
- return $chart;
+ $chart->attachDatasets($datasets);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jul 2, 3:12 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
164648
Default Alt Text
(21 KB)

Event Timeline