@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
at upstream/main 302 lines 7.0 kB view raw
1<?php 2 3abstract 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 = 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}