@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 372 lines 9.4 kB view raw
1<?php 2 3final class PhabricatorChartRenderingEngine 4 extends Phobject { 5 6 private $viewer; 7 private $chart; 8 private $storedChart; 9 10 public function setViewer(PhabricatorUser $viewer) { 11 $this->viewer = $viewer; 12 return $this; 13 } 14 15 public function getViewer() { 16 return $this->viewer; 17 } 18 19 public function setChart(PhabricatorFactChart $chart) { 20 $this->chart = $chart; 21 return $this; 22 } 23 24 public function getChart() { 25 return $this->chart; 26 } 27 28 /** 29 * Load the chart by its key 30 * @param string $chart_key 12-character string identifier of chart to load 31 * @return PhabricatorFactChart 32 */ 33 public function loadChart($chart_key) { 34 $chart = id(new PhabricatorFactChart())->loadOneWhere( 35 'chartKey = %s', 36 $chart_key); 37 38 if ($chart) { 39 $this->setChart($chart); 40 } 41 42 return $chart; 43 } 44 45 /** 46 * Get the relative URI of the chart 47 * @param string $chart_key 12-character string identifier of chart to load 48 * @return string Relative URI of the chart, e.g. "fact/chart/a1b2c3d4e5f6/" 49 */ 50 public static function getChartURI($chart_key) { 51 return id(new PhabricatorFactChart()) 52 ->setChartKey($chart_key) 53 ->getURI(); 54 } 55 56 /** 57 * @return PhabricatorFactChart 58 */ 59 public function getStoredChart() { 60 if (!$this->storedChart) { 61 $chart = $this->getChart(); 62 $chart_key = $chart->getChartKey(); 63 if (!$chart_key) { 64 $chart_key = $chart->newChartKey(); 65 66 $stored_chart = id(new PhabricatorFactChart())->loadOneWhere( 67 'chartKey = %s', 68 $chart_key); 69 if ($stored_chart) { 70 $chart = $stored_chart; 71 } else { 72 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 73 74 try { 75 $chart->save(); 76 } catch (AphrontDuplicateKeyQueryException $ex) { 77 $chart = id(new PhabricatorFactChart())->loadOneWhere( 78 'chartKey = %s', 79 $chart_key); 80 if (!$chart) { 81 throw new Exception( 82 pht( 83 'Failed to load chart with key "%s" after key collision. '. 84 'This should not be possible.', 85 $chart_key)); 86 } 87 } 88 89 unset($unguarded); 90 } 91 $this->setChart($chart); 92 } 93 94 $this->storedChart = $chart; 95 } 96 97 return $this->storedChart; 98 } 99 100 /** 101 * @return PhutilSafeHTML 102 */ 103 public function newChartView() { 104 $chart = $this->getStoredChart(); 105 $chart_key = $chart->getChartKey(); 106 107 $chart_node_id = celerity_generate_unique_node_id(); 108 109 $chart_view = phutil_tag( 110 'div', 111 array( 112 'id' => $chart_node_id, 113 'class' => 'chart-hardpoint', 114 )); 115 116 $data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key); 117 118 Javelin::initBehavior( 119 'line-chart', 120 array( 121 'chartNodeID' => $chart_node_id, 122 'dataURI' => (string)$data_uri, 123 )); 124 125 return $chart_view; 126 } 127 128 public function newTabularView() { 129 $viewer = $this->getViewer(); 130 $tabular_data = $this->newTabularData(); 131 132 $ref_keys = array(); 133 foreach ($tabular_data['datasets'] as $tabular_dataset) { 134 foreach ($tabular_dataset as $function) { 135 foreach ($function['data'] as $point) { 136 foreach ($point['refs'] as $ref) { 137 $ref_keys[$ref] = $ref; 138 } 139 } 140 } 141 } 142 143 $chart = $this->getStoredChart(); 144 145 $ref_map = array(); 146 foreach ($chart->getDatasets() as $dataset) { 147 foreach ($dataset->getFunctions() as $function) { 148 // If we aren't looking for anything else, bail out. 149 if (!$ref_keys) { 150 break 2; 151 } 152 153 $function_refs = $function->loadRefs($ref_keys); 154 155 $ref_map += $function_refs; 156 157 // Remove the ref keys that we found data for from the list of keys 158 // we are looking for. If any function gives us data for a given ref, 159 // that's satisfactory. 160 foreach ($function_refs as $ref_key => $ref_data) { 161 unset($ref_keys[$ref_key]); 162 } 163 } 164 } 165 166 $phids = array(); 167 foreach ($ref_map as $ref => $ref_data) { 168 if (isset($ref_data['objectPHID'])) { 169 $phids[] = $ref_data['objectPHID']; 170 } 171 } 172 173 $handles = $viewer->loadHandles($phids); 174 175 $tabular_view = array(); 176 foreach ($tabular_data['datasets'] as $tabular_data) { 177 foreach ($tabular_data as $function) { 178 $rows = array(); 179 foreach ($function['data'] as $point) { 180 $ref_views = array(); 181 182 $xv = date('Y-m-d h:i:s', $point['x']); 183 $yv = $point['y']; 184 185 $point_refs = array(); 186 foreach ($point['refs'] as $ref) { 187 if (!isset($ref_map[$ref])) { 188 continue; 189 } 190 $point_refs[$ref] = $ref_map[$ref]; 191 } 192 193 if (!$point_refs) { 194 $rows[] = array( 195 $xv, 196 $yv, 197 null, 198 null, 199 null, 200 ); 201 } else { 202 foreach ($point_refs as $ref => $ref_data) { 203 $ref_value = $ref_data['value']; 204 $ref_link = $handles[$ref_data['objectPHID']] 205 ->renderLink(); 206 207 $view_uri = urisprintf( 208 '/fact/object/%s/', 209 $ref_data['objectPHID']); 210 211 $ref_button = id(new PHUIButtonView()) 212 ->setIcon('fa-table') 213 ->setTag('a') 214 ->setColor('grey') 215 ->setHref($view_uri) 216 ->setText(pht('View Data')); 217 218 $rows[] = array( 219 $xv, 220 $yv, 221 $ref_value, 222 $ref_link, 223 $ref_button, 224 ); 225 226 $xv = null; 227 $yv = null; 228 } 229 } 230 } 231 232 $table = id(new AphrontTableView($rows)) 233 ->setHeaders( 234 array( 235 pht('X'), 236 pht('Y'), 237 pht('Raw'), 238 pht('Refs'), 239 null, 240 )) 241 ->setColumnClasses( 242 array( 243 'n', 244 'n', 245 'n', 246 'wide', 247 null, 248 )); 249 250 $tabular_view[] = id(new PHUIObjectBoxView()) 251 ->setHeaderText(pht('Function')) 252 ->setTable($table); 253 } 254 } 255 256 return $tabular_view; 257 } 258 259 public function newChartData() { 260 return $this->newWireData(false); 261 } 262 263 public function newTabularData() { 264 return $this->newWireData(true); 265 } 266 267 private function newWireData($is_tabular) { 268 $chart = $this->getStoredChart(); 269 $chart_key = $chart->getChartKey(); 270 271 $chart_engine = PhabricatorChartEngine::newFromChart($chart) 272 ->setViewer($this->getViewer()); 273 $chart_engine->buildChart($chart); 274 275 $datasets = $chart->getDatasets(); 276 277 $functions = array(); 278 foreach ($datasets as $dataset) { 279 foreach ($dataset->getFunctions() as $function) { 280 $functions[] = $function; 281 } 282 } 283 284 $subfunctions = array(); 285 foreach ($functions as $function) { 286 foreach ($function->getSubfunctions() as $subfunction) { 287 $subfunctions[] = $subfunction; 288 } 289 } 290 291 foreach ($subfunctions as $subfunction) { 292 $subfunction->loadData(); 293 } 294 295 $domain = $this->getDomain($functions); 296 297 $axis = id(new PhabricatorChartAxis()) 298 ->setMinimumValue($domain->getMin()) 299 ->setMaximumValue($domain->getMax()); 300 301 $data_query = id(new PhabricatorChartDataQuery()) 302 ->setMinimumValue($domain->getMin()) 303 ->setMaximumValue($domain->getMax()) 304 ->setLimit(2000); 305 306 $wire_datasets = array(); 307 $ranges = array(); 308 foreach ($datasets as $dataset) { 309 if ($is_tabular) { 310 $display_data = $dataset->getTabularDisplayData($data_query); 311 } else { 312 $display_data = $dataset->getChartDisplayData($data_query); 313 } 314 315 $ranges[] = $display_data->getRange(); 316 $wire_datasets[] = $display_data->getWireData(); 317 } 318 319 $range = $this->getRange($ranges); 320 321 $chart_data = array( 322 'datasets' => $wire_datasets, 323 'xMin' => $domain->getMin(), 324 'xMax' => $domain->getMax(), 325 'yMin' => $range->getMin(), 326 'yMax' => $range->getMax(), 327 ); 328 329 return $chart_data; 330 } 331 332 private function getDomain(array $functions) { 333 $domains = array(); 334 foreach ($functions as $function) { 335 $domains[] = $function->getDomain(); 336 } 337 338 $domain = PhabricatorChartInterval::newFromIntervalList($domains); 339 340 // If we don't have any domain data from the actual functions, pick a 341 // plausible domain automatically. 342 343 if ($domain->getMax() === null) { 344 $domain->setMax(PhabricatorTime::getNow()); 345 } 346 347 if ($domain->getMin() === null) { 348 $domain->setMin($domain->getMax() - phutil_units('365 days in seconds')); 349 } 350 351 return $domain; 352 } 353 354 private function getRange(array $ranges) { 355 $range = PhabricatorChartInterval::newFromIntervalList($ranges); 356 357 // Start the Y axis at 0 unless the chart has negative values. 358 $min = $range->getMin(); 359 if ($min === null || $min >= 0) { 360 $range->setMin(0); 361 } 362 363 // If there's no maximum value, just pick a plausible default. 364 $max = $range->getMax(); 365 if ($max === null) { 366 $range->setMax($range->getMin() + 100); 367 } 368 369 return $range; 370 } 371 372}