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