Path: blob/master/src/applications/fact/engine/PhabricatorChartRenderingEngine.php
12256 views
<?php12final class PhabricatorChartRenderingEngine3extends Phobject {45private $viewer;6private $chart;7private $storedChart;89public function setViewer(PhabricatorUser $viewer) {10$this->viewer = $viewer;11return $this;12}1314public function getViewer() {15return $this->viewer;16}1718public function setChart(PhabricatorFactChart $chart) {19$this->chart = $chart;20return $this;21}2223public function getChart() {24return $this->chart;25}2627public function loadChart($chart_key) {28$chart = id(new PhabricatorFactChart())->loadOneWhere(29'chartKey = %s',30$chart_key);3132if ($chart) {33$this->setChart($chart);34}3536return $chart;37}3839public static function getChartURI($chart_key) {40return id(new PhabricatorFactChart())41->setChartKey($chart_key)42->getURI();43}4445public function getStoredChart() {46if (!$this->storedChart) {47$chart = $this->getChart();48$chart_key = $chart->getChartKey();49if (!$chart_key) {50$chart_key = $chart->newChartKey();5152$stored_chart = id(new PhabricatorFactChart())->loadOneWhere(53'chartKey = %s',54$chart_key);55if ($stored_chart) {56$chart = $stored_chart;57} else {58$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();5960try {61$chart->save();62} catch (AphrontDuplicateKeyQueryException $ex) {63$chart = id(new PhabricatorFactChart())->loadOneWhere(64'chartKey = %s',65$chart_key);66if (!$chart) {67throw new Exception(68pht(69'Failed to load chart with key "%s" after key collision. '.70'This should not be possible.',71$chart_key));72}73}7475unset($unguarded);76}77$this->setChart($chart);78}7980$this->storedChart = $chart;81}8283return $this->storedChart;84}8586public function newChartView() {87$chart = $this->getStoredChart();88$chart_key = $chart->getChartKey();8990$chart_node_id = celerity_generate_unique_node_id();9192$chart_view = phutil_tag(93'div',94array(95'id' => $chart_node_id,96'class' => 'chart-hardpoint',97));9899$data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key);100101Javelin::initBehavior(102'line-chart',103array(104'chartNodeID' => $chart_node_id,105'dataURI' => (string)$data_uri,106));107108return $chart_view;109}110111public function newTabularView() {112$viewer = $this->getViewer();113$tabular_data = $this->newTabularData();114115$ref_keys = array();116foreach ($tabular_data['datasets'] as $tabular_dataset) {117foreach ($tabular_dataset as $function) {118foreach ($function['data'] as $point) {119foreach ($point['refs'] as $ref) {120$ref_keys[$ref] = $ref;121}122}123}124}125126$chart = $this->getStoredChart();127128$ref_map = array();129foreach ($chart->getDatasets() as $dataset) {130foreach ($dataset->getFunctions() as $function) {131// If we aren't looking for anything else, bail out.132if (!$ref_keys) {133break 2;134}135136$function_refs = $function->loadRefs($ref_keys);137138$ref_map += $function_refs;139140// Remove the ref keys that we found data for from the list of keys141// we are looking for. If any function gives us data for a given ref,142// that's satisfactory.143foreach ($function_refs as $ref_key => $ref_data) {144unset($ref_keys[$ref_key]);145}146}147}148149$phids = array();150foreach ($ref_map as $ref => $ref_data) {151if (isset($ref_data['objectPHID'])) {152$phids[] = $ref_data['objectPHID'];153}154}155156$handles = $viewer->loadHandles($phids);157158$tabular_view = array();159foreach ($tabular_data['datasets'] as $tabular_data) {160foreach ($tabular_data as $function) {161$rows = array();162foreach ($function['data'] as $point) {163$ref_views = array();164165$xv = date('Y-m-d h:i:s', $point['x']);166$yv = $point['y'];167168$point_refs = array();169foreach ($point['refs'] as $ref) {170if (!isset($ref_map[$ref])) {171continue;172}173$point_refs[$ref] = $ref_map[$ref];174}175176if (!$point_refs) {177$rows[] = array(178$xv,179$yv,180null,181null,182null,183);184} else {185foreach ($point_refs as $ref => $ref_data) {186$ref_value = $ref_data['value'];187$ref_link = $handles[$ref_data['objectPHID']]188->renderLink();189190$view_uri = urisprintf(191'/fact/object/%s/',192$ref_data['objectPHID']);193194$ref_button = id(new PHUIButtonView())195->setIcon('fa-table')196->setTag('a')197->setColor('grey')198->setHref($view_uri)199->setText(pht('View Data'));200201$rows[] = array(202$xv,203$yv,204$ref_value,205$ref_link,206$ref_button,207);208209$xv = null;210$yv = null;211}212}213}214215$table = id(new AphrontTableView($rows))216->setHeaders(217array(218pht('X'),219pht('Y'),220pht('Raw'),221pht('Refs'),222null,223))224->setColumnClasses(225array(226'n',227'n',228'n',229'wide',230null,231));232233$tabular_view[] = id(new PHUIObjectBoxView())234->setHeaderText(pht('Function'))235->setTable($table);236}237}238239return $tabular_view;240}241242public function newChartData() {243return $this->newWireData(false);244}245246public function newTabularData() {247return $this->newWireData(true);248}249250private function newWireData($is_tabular) {251$chart = $this->getStoredChart();252$chart_key = $chart->getChartKey();253254$chart_engine = PhabricatorChartEngine::newFromChart($chart)255->setViewer($this->getViewer());256$chart_engine->buildChart($chart);257258$datasets = $chart->getDatasets();259260$functions = array();261foreach ($datasets as $dataset) {262foreach ($dataset->getFunctions() as $function) {263$functions[] = $function;264}265}266267$subfunctions = array();268foreach ($functions as $function) {269foreach ($function->getSubfunctions() as $subfunction) {270$subfunctions[] = $subfunction;271}272}273274foreach ($subfunctions as $subfunction) {275$subfunction->loadData();276}277278$domain = $this->getDomain($functions);279280$axis = id(new PhabricatorChartAxis())281->setMinimumValue($domain->getMin())282->setMaximumValue($domain->getMax());283284$data_query = id(new PhabricatorChartDataQuery())285->setMinimumValue($domain->getMin())286->setMaximumValue($domain->getMax())287->setLimit(2000);288289$wire_datasets = array();290$ranges = array();291foreach ($datasets as $dataset) {292if ($is_tabular) {293$display_data = $dataset->getTabularDisplayData($data_query);294} else {295$display_data = $dataset->getChartDisplayData($data_query);296}297298$ranges[] = $display_data->getRange();299$wire_datasets[] = $display_data->getWireData();300}301302$range = $this->getRange($ranges);303304$chart_data = array(305'datasets' => $wire_datasets,306'xMin' => $domain->getMin(),307'xMax' => $domain->getMax(),308'yMin' => $range->getMin(),309'yMax' => $range->getMax(),310);311312return $chart_data;313}314315private function getDomain(array $functions) {316$domains = array();317foreach ($functions as $function) {318$domains[] = $function->getDomain();319}320321$domain = PhabricatorChartInterval::newFromIntervalList($domains);322323// If we don't have any domain data from the actual functions, pick a324// plausible domain automatically.325326if ($domain->getMax() === null) {327$domain->setMax(PhabricatorTime::getNow());328}329330if ($domain->getMin() === null) {331$domain->setMin($domain->getMax() - phutil_units('365 days in seconds'));332}333334return $domain;335}336337private function getRange(array $ranges) {338$range = PhabricatorChartInterval::newFromIntervalList($ranges);339340// Start the Y axis at 0 unless the chart has negative values.341$min = $range->getMin();342if ($min === null || $min >= 0) {343$range->setMin(0);344}345346// If there's no maximum value, just pick a plausible default.347$max = $range->getMax();348if ($max === null) {349$range->setMax($range->getMin() + 100);350}351352return $range;353}354355}356357358