Something went wrong. Try again.
@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
Something went wrong. Try again.
7.0 kB · 302 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303<?php
abstract class PhabricatorObjectGraph extends AbstractDirectedGraph {
private $viewer; private $edges = array(); private $edgeReach = array(); private $seedPHID; private $objects; private $loadEntireGraph = false; private $limit; private $adjacent; private $height;
public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; }
public function getViewer() { if (!$this->viewer) { throw new PhutilInvalidStateException('setViewer'); }
return $this->viewer; }
public function setLimit($limit) { $this->limit = $limit; return $this; }
public function getLimit() { return $this->limit; }
public function setHeight($height) { $this->height = $height; return $this; }
public function getHeight() { return $this->height; }
final public function setRenderOnlyAdjacentNodes($adjacent) { $this->adjacent = $adjacent; return $this; }
final public function getRenderOnlyAdjacentNodes() { return $this->adjacent; }
abstract protected function getEdgeTypes(); abstract protected function getParentEdgeType(); abstract protected function newQuery(); abstract protected function newTableRow($phid, $object, $trace); abstract protected function newTable(AphrontTableView $table); abstract protected function isClosed($object);
protected function newEllipsisRow() { return array( '...', ); }
final public function setSeedPHID($phid) { $this->seedPHID = $phid; $this->edgeReach[$phid] = array_fill_keys($this->getEdgeTypes(), true);
return $this->addNodes( array( '<seed>' => array($phid), )); }
final public function getSeedPHID() { return $this->seedPHID; }
final public function isEmpty() { return (count($this->getNodes()) <= 2); }
final public function isOverLimit() { $limit = $this->getLimit();
if (!$limit) { return false; }
return (count($this->edgeReach) > $limit); }
final public function getEdges($type) { $edges = idx($this->edges, $type, array());
// Remove any nodes which we never reached. We can get these when loading // only part of the graph: for example, they point at other subtasks of // parents or other parents of subtasks. $nodes = $this->getNodes(); foreach ($edges as $src => $dsts) { foreach ($dsts as $key => $dst) { if (!isset($nodes[$dst])) { unset($edges[$src][$key]); } } }
return $edges; }
final public function setLoadEntireGraph($load_entire_graph) { $this->loadEntireGraph = $load_entire_graph; return $this; }
final public function getLoadEntireGraph() { return $this->loadEntireGraph; }
final protected function loadEdges(array $nodes) { if ($this->isOverLimit()) { return array_fill_keys($nodes, array()); }
$edge_types = $this->getEdgeTypes();
$query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($nodes) ->withEdgeTypes($edge_types);
$query->execute();
$whole_graph = $this->getLoadEntireGraph();
$map = array(); foreach ($nodes as $node) { $map[$node] = array();
foreach ($edge_types as $edge_type) { $dst_phids = $query->getDestinationPHIDs( array($node), array($edge_type));
$this->edges[$edge_type][$node] = $dst_phids; foreach ($dst_phids as $dst_phid) { if ($whole_graph || isset($this->edgeReach[$node][$edge_type])) { $map[$node][] = $dst_phid; } $this->edgeReach[$dst_phid][$edge_type] = true; } }
$map[$node] = array_values(array_fuse($map[$node])); }
return $map; }
final public function newGraphTable() { $viewer = $this->getViewer();
$ancestry = $this->getEdges($this->getParentEdgeType());
$only_adjacent = $this->getRenderOnlyAdjacentNodes(); if ($only_adjacent) { $adjacent = array( $this->getSeedPHID() => $this->getSeedPHID(), );
foreach ($this->getEdgeTypes() as $edge_type) { $map = $this->getEdges($edge_type); $direct = idx($map, $this->getSeedPHID(), array()); $adjacent += array_fuse($direct); }
foreach ($ancestry as $key => $list) { if (!isset($adjacent[$key])) { unset($ancestry[$key]); continue; }
foreach ($list as $list_key => $item) { if (!isset($adjacent[$item])) { unset($ancestry[$key][$list_key]); } } } }
$objects = $this->newQuery() ->setViewer($viewer) ->withPHIDs(array_keys($ancestry)) ->execute(); $objects = mpull($objects, null, 'getPHID');
$order = id(new PhutilDirectedScalarGraph()) ->addNodes($ancestry) ->getNodesInTopologicalOrder();
$ancestry = array_select_keys($ancestry, $order);
$graph_view = new PHUIDiffGraphView();
$height = $this->getHeight(); if ($height !== null) { $graph_view->setHeight($height); }
$traces = $graph_view->renderGraph($ancestry);
$ii = 0; $rows = array(); $rowc = array();
if ($only_adjacent) { $rows[] = $this->newEllipsisRow(); $rowc[] = 'more'; }
foreach ($ancestry as $phid => $ignored) { $object = idx($objects, $phid); $rows[] = $this->newTableRow($phid, $object, $traces[$ii++]);
$classes = array(); if ($phid == $this->seedPHID) { $classes[] = 'highlighted'; }
if ($object) { if ($this->isClosed($object)) { $classes[] = 'closed'; } }
if ($classes) { $classes = implode(' ', $classes); } else { $classes = null; }
$rowc[] = $classes; }
if ($only_adjacent) { $rows[] = $this->newEllipsisRow(); $rowc[] = 'more'; }
$table = id(new AphrontTableView($rows)) ->setClassName('object-graph-table') ->setRowClasses($rowc);
$this->objects = $objects;
return $this->newTable($table); }
final public function getReachableObjects($edge_type) { if ($this->objects === null) { throw new PhutilInvalidStateException('newGraphTable'); }
$graph = $this->getEdges($edge_type);
$seen = array(); $look = array($this->seedPHID); while ($look) { $phid = array_pop($look);
$parents = idx($graph, $phid, array()); foreach ($parents as $parent) { if (isset($seen[$parent])) { continue; }
$seen[$parent] = $parent; $look[] = $parent; } }
$reachable = array(); foreach ($seen as $phid) { if ($phid == $this->seedPHID) { continue; }
$object = idx($this->objects, $phid); if (!$object) { continue; }
$reachable[] = $object; }
return $reachable; }
}