Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/graph/PhabricatorObjectGraph.php
12241 views
1
<?php
2
3
abstract class PhabricatorObjectGraph
4
extends AbstractDirectedGraph {
5
6
private $viewer;
7
private $edges = array();
8
private $edgeReach = array();
9
private $seedPHID;
10
private $objects;
11
private $loadEntireGraph = false;
12
private $limit;
13
private $adjacent;
14
private $height;
15
16
public function setViewer(PhabricatorUser $viewer) {
17
$this->viewer = $viewer;
18
return $this;
19
}
20
21
public function getViewer() {
22
if (!$this->viewer) {
23
throw new PhutilInvalidStateException('setViewer');
24
}
25
26
return $this->viewer;
27
}
28
29
public function setLimit($limit) {
30
$this->limit = $limit;
31
return $this;
32
}
33
34
public function getLimit() {
35
return $this->limit;
36
}
37
38
public function setHeight($height) {
39
$this->height = $height;
40
return $this;
41
}
42
43
public function getHeight() {
44
return $this->height;
45
}
46
47
final public function setRenderOnlyAdjacentNodes($adjacent) {
48
$this->adjacent = $adjacent;
49
return $this;
50
}
51
52
final public function getRenderOnlyAdjacentNodes() {
53
return $this->adjacent;
54
}
55
56
abstract protected function getEdgeTypes();
57
abstract protected function getParentEdgeType();
58
abstract protected function newQuery();
59
abstract protected function newTableRow($phid, $object, $trace);
60
abstract protected function newTable(AphrontTableView $table);
61
abstract protected function isClosed($object);
62
63
protected function newEllipsisRow() {
64
return array(
65
'...',
66
);
67
}
68
69
final public function setSeedPHID($phid) {
70
$this->seedPHID = $phid;
71
$this->edgeReach[$phid] = array_fill_keys($this->getEdgeTypes(), true);
72
73
return $this->addNodes(
74
array(
75
'<seed>' => array($phid),
76
));
77
}
78
79
final public function getSeedPHID() {
80
return $this->seedPHID;
81
}
82
83
final public function isEmpty() {
84
return (count($this->getNodes()) <= 2);
85
}
86
87
final public function isOverLimit() {
88
$limit = $this->getLimit();
89
90
if (!$limit) {
91
return false;
92
}
93
94
return (count($this->edgeReach) > $limit);
95
}
96
97
final public function getEdges($type) {
98
$edges = idx($this->edges, $type, array());
99
100
// Remove any nodes which we never reached. We can get these when loading
101
// only part of the graph: for example, they point at other subtasks of
102
// parents or other parents of subtasks.
103
$nodes = $this->getNodes();
104
foreach ($edges as $src => $dsts) {
105
foreach ($dsts as $key => $dst) {
106
if (!isset($nodes[$dst])) {
107
unset($edges[$src][$key]);
108
}
109
}
110
}
111
112
return $edges;
113
}
114
115
final public function setLoadEntireGraph($load_entire_graph) {
116
$this->loadEntireGraph = $load_entire_graph;
117
return $this;
118
}
119
120
final public function getLoadEntireGraph() {
121
return $this->loadEntireGraph;
122
}
123
124
final protected function loadEdges(array $nodes) {
125
if ($this->isOverLimit()) {
126
return array_fill_keys($nodes, array());
127
}
128
129
$edge_types = $this->getEdgeTypes();
130
131
$query = id(new PhabricatorEdgeQuery())
132
->withSourcePHIDs($nodes)
133
->withEdgeTypes($edge_types);
134
135
$query->execute();
136
137
$whole_graph = $this->getLoadEntireGraph();
138
139
$map = array();
140
foreach ($nodes as $node) {
141
$map[$node] = array();
142
143
foreach ($edge_types as $edge_type) {
144
$dst_phids = $query->getDestinationPHIDs(
145
array($node),
146
array($edge_type));
147
148
$this->edges[$edge_type][$node] = $dst_phids;
149
foreach ($dst_phids as $dst_phid) {
150
if ($whole_graph || isset($this->edgeReach[$node][$edge_type])) {
151
$map[$node][] = $dst_phid;
152
}
153
$this->edgeReach[$dst_phid][$edge_type] = true;
154
}
155
}
156
157
$map[$node] = array_values(array_fuse($map[$node]));
158
}
159
160
return $map;
161
}
162
163
final public function newGraphTable() {
164
$viewer = $this->getViewer();
165
166
$ancestry = $this->getEdges($this->getParentEdgeType());
167
168
$only_adjacent = $this->getRenderOnlyAdjacentNodes();
169
if ($only_adjacent) {
170
$adjacent = array(
171
$this->getSeedPHID() => $this->getSeedPHID(),
172
);
173
174
foreach ($this->getEdgeTypes() as $edge_type) {
175
$map = $this->getEdges($edge_type);
176
$direct = idx($map, $this->getSeedPHID(), array());
177
$adjacent += array_fuse($direct);
178
}
179
180
foreach ($ancestry as $key => $list) {
181
if (!isset($adjacent[$key])) {
182
unset($ancestry[$key]);
183
continue;
184
}
185
186
foreach ($list as $list_key => $item) {
187
if (!isset($adjacent[$item])) {
188
unset($ancestry[$key][$list_key]);
189
}
190
}
191
}
192
}
193
194
$objects = $this->newQuery()
195
->setViewer($viewer)
196
->withPHIDs(array_keys($ancestry))
197
->execute();
198
$objects = mpull($objects, null, 'getPHID');
199
200
$order = id(new PhutilDirectedScalarGraph())
201
->addNodes($ancestry)
202
->getNodesInTopologicalOrder();
203
204
$ancestry = array_select_keys($ancestry, $order);
205
206
$graph_view = id(new PHUIDiffGraphView());
207
208
$height = $this->getHeight();
209
if ($height !== null) {
210
$graph_view->setHeight($height);
211
}
212
213
$traces = $graph_view->renderGraph($ancestry);
214
215
$ii = 0;
216
$rows = array();
217
$rowc = array();
218
219
if ($only_adjacent) {
220
$rows[] = $this->newEllipsisRow();
221
$rowc[] = 'more';
222
}
223
224
foreach ($ancestry as $phid => $ignored) {
225
$object = idx($objects, $phid);
226
$rows[] = $this->newTableRow($phid, $object, $traces[$ii++]);
227
228
$classes = array();
229
if ($phid == $this->seedPHID) {
230
$classes[] = 'highlighted';
231
}
232
233
if ($object) {
234
if ($this->isClosed($object)) {
235
$classes[] = 'closed';
236
}
237
}
238
239
if ($classes) {
240
$classes = implode(' ', $classes);
241
} else {
242
$classes = null;
243
}
244
245
$rowc[] = $classes;
246
}
247
248
if ($only_adjacent) {
249
$rows[] = $this->newEllipsisRow();
250
$rowc[] = 'more';
251
}
252
253
$table = id(new AphrontTableView($rows))
254
->setClassName('object-graph-table')
255
->setRowClasses($rowc);
256
257
$this->objects = $objects;
258
259
return $this->newTable($table);
260
}
261
262
final public function getReachableObjects($edge_type) {
263
if ($this->objects === null) {
264
throw new PhutilInvalidStateException('newGraphTable');
265
}
266
267
$graph = $this->getEdges($edge_type);
268
269
$seen = array();
270
$look = array($this->seedPHID);
271
while ($look) {
272
$phid = array_pop($look);
273
274
$parents = idx($graph, $phid, array());
275
foreach ($parents as $parent) {
276
if (isset($seen[$parent])) {
277
continue;
278
}
279
280
$seen[$parent] = $parent;
281
$look[] = $parent;
282
}
283
}
284
285
$reachable = array();
286
foreach ($seen as $phid) {
287
if ($phid == $this->seedPHID) {
288
continue;
289
}
290
291
$object = idx($this->objects, $phid);
292
if (!$object) {
293
continue;
294
}
295
296
$reachable[] = $object;
297
}
298
299
return $reachable;
300
}
301
302
}
303
304