@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

On panel pages, show where panels are used

Summary:
Depends on D20398. Ref T13272. Fixes T6018. Previously, panels showed "used on dashboards: x, y", but this did not include cases where a panel was used by another container panel (today, a tab panel).

Do edge indexing when a dashboard or panel is saved, then pull the edges on the Panel page so we can provide a full list of uses.

Test Plan: {F6369289}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13272, T6018

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

+200 -15
+3 -3
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => '3c8a0668', 11 11 'conpherence.pkg.js' => '020aebcf', 12 - 'core.pkg.css' => '1db0892b', 12 + 'core.pkg.css' => '294e365c', 13 13 'core.pkg.js' => '794952ae', 14 14 'differential.pkg.css' => '8d8360fb', 15 15 'differential.pkg.js' => '67e02996', ··· 30 30 'rsrc/css/aphront/notification.css' => '30240bd2', 31 31 'rsrc/css/aphront/panel-view.css' => '46923d46', 32 32 'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf', 33 - 'rsrc/css/aphront/table-view.css' => '7dc3a9c2', 33 + 'rsrc/css/aphront/table-view.css' => '5f13a9e4', 34 34 'rsrc/css/aphront/tokenizer.css' => 'b52d0668', 35 35 'rsrc/css/aphront/tooltip.css' => 'e3f2412f', 36 36 'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2', ··· 531 531 'aphront-list-filter-view-css' => 'feb64255', 532 532 'aphront-multi-column-view-css' => 'fbc00ba3', 533 533 'aphront-panel-view-css' => '46923d46', 534 - 'aphront-table-view-css' => '7dc3a9c2', 534 + 'aphront-table-view-css' => '5f13a9e4', 535 535 'aphront-tokenizer-control-css' => 'b52d0668', 536 536 'aphront-tooltip-css' => 'e3f2412f', 537 537 'aphront-typeahead-control-css' => '8779483d',
+9
src/__phutil_library_map__.php
··· 2930 2930 'PhabricatorDashboardObjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php', 2931 2931 'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php', 2932 2932 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php', 2933 + 'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php', 2934 + 'PhabricatorDashboardPanelContainerInterface' => 'applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php', 2933 2935 'PhabricatorDashboardPanelDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php', 2934 2936 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php', 2935 2937 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php', ··· 2951 2953 'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php', 2952 2954 'PhabricatorDashboardPanelTransactionType' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php', 2953 2955 'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php', 2956 + 'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php', 2954 2957 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php', 2955 2958 'PhabricatorDashboardPortal' => 'applications/dashboard/storage/PhabricatorDashboardPortal.php', 2956 2959 'PhabricatorDashboardPortalController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php', ··· 3724 3727 'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php', 3725 3728 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', 3726 3729 'PhabricatorObjectStatus' => 'infrastructure/status/PhabricatorObjectStatus.php', 3730 + 'PhabricatorObjectUsesDashboardPanelEdgeType' => 'applications/search/edge/PhabricatorObjectUsesDashboardPanelEdgeType.php', 3727 3731 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', 3728 3732 'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php', 3729 3733 'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php', ··· 8888 8892 'PhabricatorDestructibleInterface', 8889 8893 'PhabricatorProjectInterface', 8890 8894 'PhabricatorNgramsInterface', 8895 + 'PhabricatorDashboardPanelContainerInterface', 8891 8896 ), 8892 8897 'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController', 8893 8898 'PhabricatorDashboardApplication' => 'PhabricatorApplication', ··· 8918 8923 'PhabricatorFlaggableInterface', 8919 8924 'PhabricatorDestructibleInterface', 8920 8925 'PhabricatorNgramsInterface', 8926 + 'PhabricatorDashboardPanelContainerInterface', 8921 8927 ), 8922 8928 'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController', 8929 + 'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension', 8923 8930 'PhabricatorDashboardPanelDatasource' => 'PhabricatorTypeaheadDatasource', 8924 8931 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 8925 8932 'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController', ··· 8941 8948 'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 8942 8949 'PhabricatorDashboardPanelTransactionType' => 'PhabricatorModularTransactionType', 8943 8950 'PhabricatorDashboardPanelType' => 'Phobject', 8951 + 'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'PhabricatorEdgeType', 8944 8952 'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController', 8945 8953 'PhabricatorDashboardPortal' => array( 8946 8954 'PhabricatorDashboardDAO', ··· 9799 9807 'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule', 9800 9808 'PhabricatorObjectSelectorDialog' => 'Phobject', 9801 9809 'PhabricatorObjectStatus' => 'Phobject', 9810 + 'PhabricatorObjectUsesDashboardPanelEdgeType' => 'PhabricatorEdgeType', 9802 9811 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', 9803 9812 'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource', 9804 9813 'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting',
+2
src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php
··· 172 172 } 173 173 174 174 $usage_table = id(new AphrontTableView($rows)) 175 + ->setNoDataString( 176 + pht('This dashboard has not been added to any menus.')) 175 177 ->setHeaders( 176 178 array( 177 179 null,
+50
src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php
··· 35 35 $header = $this->buildHeaderView($panel); 36 36 $curtain = $this->buildCurtainView($panel); 37 37 38 + $usage_box = $this->newUsageView($panel); 39 + 38 40 $timeline = $this->buildTransactionTimeline( 39 41 $panel, 40 42 new PhabricatorDashboardPanelTransactionQuery()); ··· 57 59 ->setCurtain($curtain) 58 60 ->setMainColumn(array( 59 61 $rendered_panel, 62 + $usage_box, 60 63 $timeline, 61 64 )); 62 65 ··· 122 125 return $curtain; 123 126 } 124 127 128 + private function newUsageView(PhabricatorDashboardPanel $panel) { 129 + $viewer = $this->getViewer(); 130 + 131 + $object_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 132 + $panel->getPHID(), 133 + PhabricatorDashboardPanelUsedByObjectEdgeType::EDGECONST); 134 + 135 + if ($object_phids) { 136 + $handles = $viewer->loadHandles($object_phids); 137 + } else { 138 + $handles = array(); 139 + } 140 + 141 + $rows = array(); 142 + foreach ($object_phids as $object_phid) { 143 + $handle = $handles[$object_phid]; 144 + 145 + $icon = $handle->getIcon(); 146 + 147 + $rows[] = array( 148 + id(new PHUIIconView())->setIcon($icon), 149 + $handle->getTypeName(), 150 + $handle->renderLink(), 151 + ); 152 + } 153 + 154 + $usage_table = id(new AphrontTableView($rows)) 155 + ->setNoDataString( 156 + pht( 157 + 'This panel is not used on any dashboard or inside any other '. 158 + 'panel container.')) 159 + ->setColumnClasses( 160 + array( 161 + 'center', 162 + '', 163 + 'pri wide', 164 + )); 165 + 166 + $header_view = id(new PHUIHeaderView()) 167 + ->setHeader(pht('Panel Used By')); 168 + 169 + $usage_box = id(new PHUIObjectBoxView()) 170 + ->setTable($usage_table) 171 + ->setHeader($header_view); 172 + 173 + return $usage_box; 174 + } 125 175 }
+4
src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php
··· 21 21 return $types; 22 22 } 23 23 24 + protected function supportsSearch() { 25 + return true; 26 + } 27 + 24 28 }
+3
src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
··· 178 178 return $errors; 179 179 } 180 180 181 + protected function supportsSearch() { 182 + return true; 183 + } 181 184 182 185 }
+28
src/applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorDashboardPanelContainerIndexEngineExtension 4 + extends PhabricatorEdgeIndexEngineExtension { 5 + 6 + const EXTENSIONKEY = 'dashboard.panel.container'; 7 + 8 + public function getExtensionName() { 9 + return pht('Dashboard Panel Containers'); 10 + } 11 + 12 + public function shouldIndexObject($object) { 13 + if (!($object instanceof PhabricatorDashboardPanelContainerInterface)) { 14 + return false; 15 + } 16 + 17 + return true; 18 + } 19 + 20 + protected function getIndexEdgeType() { 21 + return PhabricatorObjectUsesDashboardPanelEdgeType::EDGECONST; 22 + } 23 + 24 + protected function getIndexDestinationPHIDs($object) { 25 + return $object->getDashboardPanelContainerPanelPHIDs(); 26 + } 27 + 28 + }
+12
src/applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php
··· 1 + <?php 2 + 3 + interface PhabricatorDashboardPanelContainerInterface { 4 + 5 + /** 6 + * Return a list of Dashboard Panel PHIDs used by this container. 7 + * 8 + * @return list<phid> 9 + */ 10 + public function getDashboardPanelContainerPanelPHIDs(); 11 + 12 + }
+4
src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
··· 59 59 abstract protected function newEditEngineFields( 60 60 PhabricatorDashboardPanel $panel); 61 61 62 + public function getSubpanelPHIDs(PhabricatorDashboardPanel $panel) { 63 + return array(); 64 + } 65 + 62 66 }
+21 -1
src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php
··· 105 105 106 106 $tab_view = id(new PHUIListItemView()) 107 107 ->setHref('#') 108 - ->setSelected($idx == $selected) 108 + ->setSelected((string)$idx === (string)$selected) 109 109 ->addSigil('dashboard-tab-panel-tab') 110 110 ->setMetadata(array('panelKey' => $idx)) 111 111 ->setName($name); ··· 290 290 $list, 291 291 $content, 292 292 )); 293 + } 294 + 295 + public function getSubpanelPHIDs(PhabricatorDashboardPanel $panel) { 296 + $config = $this->getPanelConfiguration($panel); 297 + 298 + $panel_ids = array(); 299 + foreach ($config as $tab_key => $tab_spec) { 300 + $panel_ids[] = $tab_spec['panelID']; 301 + } 302 + 303 + if ($panel_ids) { 304 + $panels = id(new PhabricatorDashboardPanelQuery()) 305 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 306 + ->withIDs($panel_ids) 307 + ->execute(); 308 + } else { 309 + $panels = array(); 310 + } 311 + 312 + return mpull($panels, 'getPHID'); 293 313 } 294 314 295 315 }
+5 -3
src/applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php
··· 35 35 $name = $panel->getName(); 36 36 $monogram = $panel->getMonogram(); 37 37 38 - $handle->setName($panel->getMonogram()); 39 - $handle->setFullName("{$monogram} {$name}"); 40 - $handle->setURI("/{$monogram}"); 38 + $handle 39 + ->setIcon('fa-window-maximize') 40 + ->setName($name) 41 + ->setFullName("{$monogram} {$name}") 42 + ->setURI($panel->getURI()); 41 43 42 44 if ($panel->getIsArchived()) { 43 45 $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
+6 -6
src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php
··· 62 62 if ($this->ids !== null) { 63 63 $where[] = qsprintf( 64 64 $conn, 65 - 'id IN (%Ld)', 65 + 'panel.id IN (%Ld)', 66 66 $this->ids); 67 67 } 68 68 69 69 if ($this->phids !== null) { 70 70 $where[] = qsprintf( 71 71 $conn, 72 - 'phid IN (%Ls)', 72 + 'panel.phid IN (%Ls)', 73 73 $this->phids); 74 74 } 75 75 76 76 if ($this->archived !== null) { 77 77 $where[] = qsprintf( 78 78 $conn, 79 - 'isArchived = %d', 79 + 'panel.isArchived = %d', 80 80 (int)$this->archived); 81 81 } 82 82 83 83 if ($this->panelTypes !== null) { 84 84 $where[] = qsprintf( 85 85 $conn, 86 - 'panelType IN (%Ls)', 86 + 'panel.panelType IN (%Ls)', 87 87 $this->panelTypes); 88 88 } 89 89 90 90 if ($this->authorPHIDs !== null) { 91 91 $where[] = qsprintf( 92 92 $conn, 93 - 'authorPHID IN (%Ls)', 93 + 'panel.authorPHID IN (%Ls)', 94 94 $this->authorPHIDs); 95 95 } 96 96 ··· 102 102 } 103 103 104 104 protected function getPrimaryTableAlias() { 105 - return 'dashboard_panel'; 105 + return 'panel'; 106 106 } 107 107 108 108 }
+10 -1
src/applications/dashboard/storage/PhabricatorDashboard.php
··· 10 10 PhabricatorFlaggableInterface, 11 11 PhabricatorDestructibleInterface, 12 12 PhabricatorProjectInterface, 13 - PhabricatorNgramsInterface { 13 + PhabricatorNgramsInterface, 14 + PhabricatorDashboardPanelContainerInterface { 14 15 15 16 protected $name; 16 17 protected $authorPHID; ··· 189 190 id(new PhabricatorDashboardNgrams()) 190 191 ->setValue($this->getName()), 191 192 ); 193 + } 194 + 195 + /* -( PhabricatorDashboardPanelContainerInterface )------------------------ */ 196 + 197 + public function getDashboardPanelContainerPanelPHIDs() { 198 + return PhabricatorEdgeQuery::loadDestinationPHIDs( 199 + $this->getPHID(), 200 + PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST); 192 201 } 193 202 194 203 }
+8 -1
src/applications/dashboard/storage/PhabricatorDashboardPanel.php
··· 10 10 PhabricatorPolicyInterface, 11 11 PhabricatorFlaggableInterface, 12 12 PhabricatorDestructibleInterface, 13 - PhabricatorNgramsInterface { 13 + PhabricatorNgramsInterface, 14 + PhabricatorDashboardPanelContainerInterface { 14 15 15 16 protected $name; 16 17 protected $panelType; ··· 163 164 id(new PhabricatorDashboardPanelNgrams()) 164 165 ->setValue($this->getName()), 165 166 ); 167 + } 168 + 169 + /* -( PhabricatorDashboardPanelContainerInterface )------------------------ */ 170 + 171 + public function getDashboardPanelContainerPanelPHIDs() { 172 + return $this->requireImplementation()->getSubpanelPHIDs($this); 166 173 } 167 174 168 175 }
+16
src/applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php
··· 1 + <?php 2 + 3 + final class PhabricatorDashboardPanelUsedByObjectEdgeType 4 + extends PhabricatorEdgeType { 5 + 6 + const EDGECONST = 72; 7 + 8 + public function getInverseEdgeConstant() { 9 + return PhabricatorObjectUsesDashboardPanelEdgeType::EDGECONST; 10 + } 11 + 12 + public function shouldWriteInverseTransactions() { 13 + return true; 14 + } 15 + 16 + }
+16
src/applications/search/edge/PhabricatorObjectUsesDashboardPanelEdgeType.php
··· 1 + <?php 2 + 3 + final class PhabricatorObjectUsesDashboardPanelEdgeType 4 + extends PhabricatorEdgeType { 5 + 6 + const EDGECONST = 71; 7 + 8 + public function getInverseEdgeConstant() { 9 + return PhabricatorDashboardPanelUsedByObjectEdgeType::EDGECONST; 10 + } 11 + 12 + public function shouldWriteInverseTransactions() { 13 + return true; 14 + } 15 + 16 + }
+3
webroot/rsrc/css/aphront/table-view.css
··· 160 160 vertical-align: top; 161 161 } 162 162 163 + /* Apply this rule to both "<th />" and "<td />" so that the header widths 164 + are correct if the table has no rows. */ 165 + .aphront-table-view th.wide, 163 166 .aphront-table-view td.wide { 164 167 white-space: normal; 165 168 width: 100%;