@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.

Make the revision graph view more flexible

Summary:
Ref T4788. This separates the revision graph view into a base class with core logic and a revision class with Differential-specific logic, so I can subclass it in Maniphest, etc., and try using it in other applications to show similar graphs.

Not sure if we'll stick with it, but even if we don't this makes the code a bit cleaner and gets custom rendering logic out of the RevisionViewController, which is nice.

Test Plan: Viewed revisions, saw the stack UI completely unchanged.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4788

Differential Revision: https://secure.phabricator.com/D16213

+259 -210
+4 -2
src/__phutil_library_map__.php
··· 521 521 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', 522 522 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 523 523 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', 524 + 'DifferentialRevisionGraph' => 'infrastructure/graph/DifferentialRevisionGraph.php', 524 525 'DifferentialRevisionHasChildRelationship' => 'applications/differential/relationships/DifferentialRevisionHasChildRelationship.php', 525 526 'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php', 526 527 'DifferentialRevisionHasCommitRelationship' => 'applications/differential/relationships/DifferentialRevisionHasCommitRelationship.php', ··· 556 557 'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php', 557 558 'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php', 558 559 'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php', 559 - 'DifferentialStackGraph' => 'applications/differential/edge/DifferentialStackGraph.php', 560 560 'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php', 561 561 'DifferentialSubscribersField' => 'applications/differential/customfield/DifferentialSubscribersField.php', 562 562 'DifferentialSummaryField' => 'applications/differential/customfield/DifferentialSummaryField.php', ··· 2868 2868 'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php', 2869 2869 'PhabricatorOAuthServerTransaction' => 'applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php', 2870 2870 'PhabricatorOAuthServerTransactionQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php', 2871 + 'PhabricatorObjectGraph' => 'infrastructure/graph/PhabricatorObjectGraph.php', 2871 2872 'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php', 2872 2873 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php', 2873 2874 'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php', ··· 4895 4896 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', 4896 4897 'DifferentialRevisionEditController' => 'DifferentialController', 4897 4898 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', 4899 + 'DifferentialRevisionGraph' => 'PhabricatorObjectGraph', 4898 4900 'DifferentialRevisionHasChildRelationship' => 'DifferentialRevisionRelationship', 4899 4901 'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType', 4900 4902 'DifferentialRevisionHasCommitRelationship' => 'DifferentialRevisionRelationship', ··· 4930 4932 'DifferentialRevisionViewController' => 'DifferentialController', 4931 4933 'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec', 4932 4934 'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod', 4933 - 'DifferentialStackGraph' => 'AbstractDirectedGraph', 4934 4935 'DifferentialStoredCustomField' => 'DifferentialCustomField', 4935 4936 'DifferentialSubscribersField' => 'DifferentialCoreCustomField', 4936 4937 'DifferentialSummaryField' => 'DifferentialCoreCustomField', ··· 7582 7583 'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController', 7583 7584 'PhabricatorOAuthServerTransaction' => 'PhabricatorApplicationTransaction', 7584 7585 'PhabricatorOAuthServerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 7586 + 'PhabricatorObjectGraph' => 'AbstractDirectedGraph', 7585 7587 'PhabricatorObjectHandle' => array( 7586 7588 'Phobject', 7587 7589 'PhabricatorPolicyInterface',
+21 -150
src/applications/differential/controller/DifferentialRevisionViewController.php
··· 341 341 ->setKey('commits') 342 342 ->appendChild($local_table)); 343 343 344 - $stack_graph = id(new DifferentialStackGraph()) 345 - ->setSeedRevision($revision) 344 + $stack_graph = id(new DifferentialRevisionGraph()) 345 + ->setViewer($viewer) 346 + ->setSeedPHID($revision->getPHID()) 346 347 ->loadGraph(); 347 348 if (!$stack_graph->isEmpty()) { 348 - $stack_view = $this->renderStackView($revision, $stack_graph); 349 - list($stack_name, $stack_color, $stack_table) = $stack_view; 349 + $stack_table = $stack_graph->newGraphTable(); 350 + 351 + $parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST; 352 + $reachable = $stack_graph->getReachableObjects($parent_type); 353 + 354 + foreach ($reachable as $key => $reachable_revision) { 355 + if ($reachable_revision->isClosed()) { 356 + unset($reachable[$key]); 357 + } 358 + } 359 + 360 + if ($reachable) { 361 + $stack_name = pht('Stack (%s Open)', phutil_count($reachable)); 362 + $stack_color = PHUIListItemView::STATUS_FAIL; 363 + } else { 364 + $stack_name = pht('Stack'); 365 + $stack_color = null; 366 + } 350 367 351 368 $tab_group->addTab( 352 369 id(new PHUITabView()) ··· 1210 1227 ->setUnitMessages($diff->getUnitMessages()) 1211 1228 ->setLimit(5) 1212 1229 ->setShowViewAll(true); 1213 - } 1214 - 1215 - 1216 - private function renderStackView( 1217 - DifferentialRevision $current, 1218 - DifferentialStackGraph $graph) { 1219 - 1220 - $ancestry = $graph->getParentEdges(); 1221 - $viewer = $this->getViewer(); 1222 - 1223 - $revisions = id(new DifferentialRevisionQuery()) 1224 - ->setViewer($viewer) 1225 - ->withPHIDs(array_keys($ancestry)) 1226 - ->execute(); 1227 - $revisions = mpull($revisions, null, 'getPHID'); 1228 - 1229 - $order = id(new PhutilDirectedScalarGraph()) 1230 - ->addNodes($ancestry) 1231 - ->getTopographicallySortedNodes(); 1232 - 1233 - $ancestry = array_select_keys($ancestry, $order); 1234 - 1235 - $traces = id(new PHUIDiffGraphView()) 1236 - ->renderGraph($ancestry); 1237 - 1238 - // Load author handles, and also revision handles for any revisions which 1239 - // we failed to load (they might be policy restricted). 1240 - $handle_phids = mpull($revisions, 'getAuthorPHID'); 1241 - foreach ($order as $phid) { 1242 - if (empty($revisions[$phid])) { 1243 - $handle_phids[] = $phid; 1244 - } 1245 - } 1246 - $handles = $viewer->loadHandles($handle_phids); 1247 - 1248 - $rows = array(); 1249 - $rowc = array(); 1250 - 1251 - $ii = 0; 1252 - $seen = false; 1253 - foreach ($ancestry as $phid => $ignored) { 1254 - $revision = idx($revisions, $phid); 1255 - if ($revision) { 1256 - $status_icon = $revision->getStatusIcon(); 1257 - $status_name = $revision->getStatusDisplayName(); 1258 - 1259 - $status = array( 1260 - id(new PHUIIconView())->setIcon($status_icon), 1261 - ' ', 1262 - $status_name, 1263 - ); 1264 - 1265 - $author = $viewer->renderHandle($revision->getAuthorPHID()); 1266 - $title = phutil_tag( 1267 - 'a', 1268 - array( 1269 - 'href' => $revision->getURI(), 1270 - ), 1271 - array( 1272 - $revision->getMonogram(), 1273 - ' ', 1274 - $revision->getTitle(), 1275 - )); 1276 - } else { 1277 - $status = null; 1278 - $author = null; 1279 - $title = $viewer->renderHandle($phid); 1280 - } 1281 - 1282 - $rows[] = array( 1283 - $traces[$ii++], 1284 - $status, 1285 - $author, 1286 - $title, 1287 - ); 1288 - 1289 - if ($phid == $current->getPHID()) { 1290 - $rowc[] = 'highlighted'; 1291 - } else { 1292 - $rowc[] = null; 1293 - } 1294 - } 1295 - 1296 - $stack_table = id(new AphrontTableView($rows)) 1297 - ->setHeaders( 1298 - array( 1299 - null, 1300 - pht('Status'), 1301 - pht('Author'), 1302 - pht('Revision'), 1303 - )) 1304 - ->setRowClasses($rowc) 1305 - ->setColumnClasses( 1306 - array( 1307 - 'threads', 1308 - null, 1309 - null, 1310 - 'wide', 1311 - )); 1312 - 1313 - // Count how many revisions this one depends on that are not yet closed. 1314 - $seen = array(); 1315 - $look = array($current->getPHID()); 1316 - while ($look) { 1317 - $phid = array_pop($look); 1318 - 1319 - $parents = idx($ancestry, $phid, array()); 1320 - foreach ($parents as $parent) { 1321 - if (isset($seen[$parent])) { 1322 - continue; 1323 - } 1324 - 1325 - $seen[$parent] = $parent; 1326 - $look[] = $parent; 1327 - } 1328 - } 1329 - 1330 - $blocking_count = 0; 1331 - foreach ($seen as $parent) { 1332 - if ($parent == $current->getPHID()) { 1333 - continue; 1334 - } 1335 - 1336 - $revision = idx($revisions, $parent); 1337 - if (!$revision) { 1338 - continue; 1339 - } 1340 - 1341 - if ($revision->isClosed()) { 1342 - continue; 1343 - } 1344 - 1345 - $blocking_count++; 1346 - } 1347 - 1348 - if (!$blocking_count) { 1349 - $stack_name = pht('Stack'); 1350 - $stack_color = null; 1351 - } else { 1352 - $stack_name = pht( 1353 - 'Stack (%s Open)', 1354 - new PhutilNumber($blocking_count)); 1355 - $stack_color = PHUIListItemView::STATUS_FAIL; 1356 - } 1357 - 1358 - return array($stack_name, $stack_color, $stack_table); 1359 1230 } 1360 1231 1361 1232 }
-58
src/applications/differential/edge/DifferentialStackGraph.php
··· 1 - <?php 2 - 3 - final class DifferentialStackGraph 4 - extends AbstractDirectedGraph { 5 - 6 - private $parentEdges = array(); 7 - private $childEdges = array(); 8 - 9 - public function setSeedRevision(DifferentialRevision $revision) { 10 - return $this->addNodes( 11 - array( 12 - '<seed>' => array($revision->getPHID()), 13 - )); 14 - } 15 - 16 - public function isEmpty() { 17 - return (count($this->getNodes()) <= 2); 18 - } 19 - 20 - public function getParentEdges() { 21 - return $this->parentEdges; 22 - } 23 - 24 - protected function loadEdges(array $nodes) { 25 - $query = id(new PhabricatorEdgeQuery()) 26 - ->withSourcePHIDs($nodes) 27 - ->withEdgeTypes( 28 - array( 29 - DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST, 30 - DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST, 31 - )); 32 - 33 - $query->execute(); 34 - 35 - $map = array(); 36 - foreach ($nodes as $node) { 37 - $parents = $query->getDestinationPHIDs( 38 - array($node), 39 - array( 40 - DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST, 41 - )); 42 - 43 - $children = $query->getDestinationPHIDs( 44 - array($node), 45 - array( 46 - DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST, 47 - )); 48 - 49 - $this->parentEdges[$node] = $parents; 50 - $this->childEdges[$node] = $children; 51 - 52 - $map[$node] = array_values(array_fuse($parents) + array_fuse($children)); 53 - } 54 - 55 - return $map; 56 - } 57 - 58 - }
+77
src/infrastructure/graph/DifferentialRevisionGraph.php
··· 1 + <?php 2 + 3 + final class DifferentialRevisionGraph 4 + extends PhabricatorObjectGraph { 5 + 6 + protected function getEdgeTypes() { 7 + return array( 8 + DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST, 9 + DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST, 10 + ); 11 + } 12 + 13 + protected function getParentEdgeType() { 14 + return DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST; 15 + } 16 + 17 + protected function newQuery() { 18 + return new DifferentialRevisionQuery(); 19 + } 20 + 21 + protected function newTableRow($phid, $object, $trace) { 22 + $viewer = $this->getViewer(); 23 + 24 + if ($object) { 25 + $status_icon = $object->getStatusIcon(); 26 + $status_name = $object->getStatusDisplayName(); 27 + 28 + $status = array( 29 + id(new PHUIIconView())->setIcon($status_icon), 30 + ' ', 31 + $status_name, 32 + ); 33 + 34 + $author = $viewer->renderHandle($object->getAuthorPHID()); 35 + $link = phutil_tag( 36 + 'a', 37 + array( 38 + 'href' => $object->getURI(), 39 + ), 40 + array( 41 + $object->getMonogram(), 42 + ' ', 43 + $object->getTitle(), 44 + )); 45 + } else { 46 + $status = null; 47 + $author = null; 48 + $link = $viewer->renderHandle($phid); 49 + } 50 + 51 + return array( 52 + $trace, 53 + $status, 54 + $author, 55 + $link, 56 + ); 57 + } 58 + 59 + protected function newTable(AphrontTableView $table) { 60 + return $table 61 + ->setHeaders( 62 + array( 63 + null, 64 + pht('Status'), 65 + pht('Author'), 66 + pht('Revision'), 67 + )) 68 + ->setColumnClasses( 69 + array( 70 + 'threads', 71 + null, 72 + null, 73 + 'wide', 74 + )); 75 + } 76 + 77 + }
+157
src/infrastructure/graph/PhabricatorObjectGraph.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorObjectGraph 4 + extends AbstractDirectedGraph { 5 + 6 + private $viewer; 7 + private $edges = array(); 8 + private $seedPHID; 9 + private $objects; 10 + 11 + public function setViewer(PhabricatorUser $viewer) { 12 + $this->viewer = $viewer; 13 + return $this; 14 + } 15 + 16 + public function getViewer() { 17 + if (!$this->viewer) { 18 + throw new PhutilInvalidStateException('setViewer'); 19 + } 20 + 21 + return $this->viewer; 22 + } 23 + 24 + abstract protected function getEdgeTypes(); 25 + abstract protected function getParentEdgeType(); 26 + abstract protected function newQuery(); 27 + abstract protected function newTableRow($phid, $object, $trace); 28 + abstract protected function newTable(AphrontTableView $table); 29 + 30 + final public function setSeedPHID($phid) { 31 + $this->seedPHID = $phid; 32 + 33 + return $this->addNodes( 34 + array( 35 + '<seed>' => array($phid), 36 + )); 37 + } 38 + 39 + final public function isEmpty() { 40 + return (count($this->getNodes()) <= 2); 41 + } 42 + 43 + final public function getEdges($type) { 44 + return idx($this->edges, $type, array()); 45 + } 46 + 47 + final protected function loadEdges(array $nodes) { 48 + $edge_types = $this->getEdgeTypes(); 49 + 50 + $query = id(new PhabricatorEdgeQuery()) 51 + ->withSourcePHIDs($nodes) 52 + ->withEdgeTypes($edge_types); 53 + 54 + $query->execute(); 55 + 56 + $map = array(); 57 + foreach ($nodes as $node) { 58 + foreach ($edge_types as $edge_type) { 59 + $dst_phids = $query->getDestinationPHIDs( 60 + array($node), 61 + array($edge_type)); 62 + 63 + $this->edges[$edge_type][$node] = $dst_phids; 64 + foreach ($dst_phids as $dst_phid) { 65 + $map[$node][] = $dst_phid; 66 + } 67 + } 68 + 69 + $map[$node] = array_values(array_fuse($map[$node])); 70 + } 71 + 72 + return $map; 73 + } 74 + 75 + final public function newGraphTable() { 76 + $viewer = $this->getViewer(); 77 + 78 + $ancestry = $this->getEdges($this->getParentEdgeType()); 79 + 80 + $objects = $this->newQuery() 81 + ->setViewer($viewer) 82 + ->withPHIDs(array_keys($ancestry)) 83 + ->execute(); 84 + $objects = mpull($objects, null, 'getPHID'); 85 + 86 + $order = id(new PhutilDirectedScalarGraph()) 87 + ->addNodes($ancestry) 88 + ->getTopographicallySortedNodes(); 89 + 90 + $ancestry = array_select_keys($ancestry, $order); 91 + 92 + $traces = id(new PHUIDiffGraphView()) 93 + ->renderGraph($ancestry); 94 + 95 + $ii = 0; 96 + $rows = array(); 97 + $rowc = array(); 98 + foreach ($ancestry as $phid => $ignored) { 99 + $object = idx($objects, $phid); 100 + $rows[] = $this->newTableRow($phid, $object, $traces[$ii++]); 101 + 102 + if ($phid == $this->seedPHID) { 103 + $rowc[] = 'highlighted'; 104 + } else { 105 + $rowc[] = null; 106 + } 107 + } 108 + 109 + $table = id(new AphrontTableView($rows)) 110 + ->setRowClasses($rowc); 111 + 112 + $this->objects = $objects; 113 + 114 + return $this->newTable($table); 115 + } 116 + 117 + final public function getReachableObjects($edge_type) { 118 + if ($this->objects === null) { 119 + throw new PhutilInvalidStateException('newGraphTable'); 120 + } 121 + 122 + $graph = $this->getEdges($edge_type); 123 + 124 + $seen = array(); 125 + $look = array($this->seedPHID); 126 + while ($look) { 127 + $phid = array_pop($look); 128 + 129 + $parents = idx($graph, $phid, array()); 130 + foreach ($parents as $parent) { 131 + if (isset($seen[$parent])) { 132 + continue; 133 + } 134 + 135 + $seen[$parent] = $parent; 136 + $look[] = $parent; 137 + } 138 + } 139 + 140 + $reachable = array(); 141 + foreach ($seen as $phid) { 142 + if ($phid == $this->seedPHID) { 143 + continue; 144 + } 145 + 146 + $object = idx($this->objects, $phid); 147 + if (!$object) { 148 + continue; 149 + } 150 + 151 + $reachable[] = $object; 152 + } 153 + 154 + return $reachable; 155 + } 156 + 157 + }