1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Index and surface usage sites for Dashboards

Summary:
Depends on D20397. Ref T13272. Similar to the recent "where are Herald rules used" stuff, show which menus Dashboards are installed in.

This is mostly straightforward, except that I pulled some of the Herald logic into a parent class so it could be shared.

Test Plan: {F6369164}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13272

Differential Revision: https://secure.phabricator.com/D20398
This commit is contained in:
epriestley 2019-04-11 09:58:48 -07:00
parent cbe13b3065
commit d62f4dbfc9
14 changed files with 306 additions and 61 deletions

View file

@ -9,7 +9,7 @@ return array(
'names' => array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '9d654dff',
'core.pkg.css' => '1db0892b',
'core.pkg.js' => '794952ae',
'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996',
@ -164,7 +164,7 @@ return array(
'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
'rsrc/css/phui/phui-list.css' => '734a1039',
'rsrc/css/phui/phui-list.css' => 'b05144dd',
'rsrc/css/phui/phui-object-box.css' => 'f434b6be',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
@ -847,7 +847,7 @@ return array(
'phui-invisible-character-view-css' => 'c694c4a4',
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '734a1039',
'phui-list-view-css' => 'b05144dd',
'phui-object-box-css' => 'f434b6be',
'phui-oi-big-ui-css' => 'fa74cc35',
'phui-oi-color-css' => 'b517bfa0',

View file

@ -3064,6 +3064,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php',
'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php',
'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php',
'PhabricatorEdgeIndexEngineExtension' => 'applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php',
'PhabricatorEdgeObject' => 'infrastructure/edges/conduit/PhabricatorEdgeObject.php',
'PhabricatorEdgeObjectQuery' => 'infrastructure/edges/query/PhabricatorEdgeObjectQuery.php',
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php',
@ -4067,11 +4068,13 @@ phutil_register_library_map(array(
'PhabricatorProfileMenuEditor' => 'applications/search/editor/PhabricatorProfileMenuEditor.php',
'PhabricatorProfileMenuEngine' => 'applications/search/engine/PhabricatorProfileMenuEngine.php',
'PhabricatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorProfileMenuItem.php',
'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'applications/search/edge/PhabricatorProfileMenuItemAffectsObjectEdgeType.php',
'PhabricatorProfileMenuItemConfiguration' => 'applications/search/storage/PhabricatorProfileMenuItemConfiguration.php',
'PhabricatorProfileMenuItemConfigurationQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php',
'PhabricatorProfileMenuItemConfigurationTransaction' => 'applications/search/storage/PhabricatorProfileMenuItemConfigurationTransaction.php',
'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationTransactionQuery.php',
'PhabricatorProfileMenuItemIconSet' => 'applications/search/menuitem/PhabricatorProfileMenuItemIconSet.php',
'PhabricatorProfileMenuItemIndexEngineExtension' => 'applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php',
'PhabricatorProfileMenuItemPHIDType' => 'applications/search/phidtype/PhabricatorProfileMenuItemPHIDType.php',
'PhabricatorProfileMenuItemView' => 'applications/search/engine/PhabricatorProfileMenuItemView.php',
'PhabricatorProfileMenuItemViewList' => 'applications/search/engine/PhabricatorProfileMenuItemViewList.php',
@ -7292,7 +7295,7 @@ phutil_register_library_map(array(
'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor',
'HeraldRuleField' => 'HeraldField',
'HeraldRuleFieldGroup' => 'HeraldFieldGroup',
'HeraldRuleIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'HeraldRuleIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension',
'HeraldRuleListController' => 'HeraldController',
'HeraldRuleListView' => 'AphrontView',
'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType',
@ -9056,6 +9059,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeEditType' => 'PhabricatorPHIDListEditType',
'PhabricatorEdgeEditor' => 'Phobject',
'PhabricatorEdgeGraph' => 'AbstractDirectedGraph',
'PhabricatorEdgeIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorEdgeObject' => array(
'Phobject',
'PhabricatorPolicyInterface',
@ -10211,16 +10215,19 @@ phutil_register_library_map(array(
'PhabricatorProfileMenuEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProfileMenuEngine' => 'Phobject',
'PhabricatorProfileMenuItem' => 'Phobject',
'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProfileMenuItemConfiguration' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorIndexableInterface',
),
'PhabricatorProfileMenuItemConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProfileMenuItemConfigurationTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProfileMenuItemIconSet' => 'PhabricatorIconSet',
'PhabricatorProfileMenuItemIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension',
'PhabricatorProfileMenuItemPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProfileMenuItemView' => 'Phobject',
'PhabricatorProfileMenuItemViewList' => 'Phobject',

View file

@ -32,6 +32,8 @@ final class PhabricatorDashboardViewController
$curtain = $this->buildCurtainView($dashboard);
$usage_box = $this->newUsageView($dashboard);
$timeline = $this->buildTransactionTimeline(
$dashboard,
new PhabricatorDashboardTransactionQuery());
@ -53,6 +55,7 @@ final class PhabricatorDashboardViewController
->setMainColumn(
array(
$dashboard_box,
$usage_box,
$timeline,
));
@ -110,5 +113,89 @@ final class PhabricatorDashboardViewController
return $curtain;
}
private function newUsageView(PhabricatorDashboard $dashboard) {
$viewer = $this->getViewer();
$custom_phids = array();
if ($viewer->getPHID()) {
$custom_phids[] = $viewer->getPHID();
}
$items = id(new PhabricatorProfileMenuItemConfigurationQuery())
->setViewer($viewer)
->withAffectedObjectPHIDs(
array(
$dashboard->getPHID(),
))
->withCustomPHIDs($custom_phids, $include_global = true)
->execute();
$handle_phids = array();
foreach ($items as $item) {
$handle_phids[] = $item->getProfilePHID();
$custom_phid = $item->getCustomPHID();
if ($custom_phid) {
$handle_phids[] = $custom_phid;
}
}
if ($handle_phids) {
$handles = $viewer->loadHandles($handle_phids);
} else {
$handles = array();
}
$items = msortv($items, 'newUsageSortVector');
$rows = array();
foreach ($items as $item) {
$profile_phid = $item->getProfilePHID();
$custom_phid = $item->getCustomPHID();
$profile = $handles[$profile_phid]->renderLink();
$profile_icon = $handles[$profile_phid]->getIcon();
if ($custom_phid) {
$custom = $handles[$custom_phid]->renderLink();
} else {
$custom = pht('Global');
}
$type = $item->getProfileMenuTypeDescription();
$rows[] = array(
id(new PHUIIconView())->setIcon($profile_icon),
$type,
$profile,
$custom,
);
}
$usage_table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Type'),
pht('Menu'),
pht('Global/Personal'),
))
->setColumnClasses(
array(
'center',
null,
'pri',
'wide',
));
$header_view = id(new PHUIHeaderView())
->setHeader(pht('Dashboard Used By'));
$usage_box = id(new PHUIObjectBoxView())
->setTable($usage_table)
->setHeader($header_view);
return $usage_box;
}
}

View file

@ -34,6 +34,7 @@ final class PhabricatorDashboardPortalPHIDType
$portal = $objects[$phid];
$handle
->setIcon('fa-compass')
->setName($portal->getName())
->setURI($portal->getURI());
}

View file

@ -1,7 +1,7 @@
<?php
final class HeraldRuleIndexEngineExtension
extends PhabricatorIndexEngineExtension {
extends PhabricatorEdgeIndexEngineExtension {
const EXTENSIONKEY = 'herald.actions';
@ -17,48 +17,13 @@ final class HeraldRuleIndexEngineExtension
return true;
}
public function indexObject(
PhabricatorIndexEngine $engine,
$object) {
$edge_type = HeraldRuleActionAffectsObjectEdgeType::EDGECONST;
$old_edges = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
$edge_type);
$old_edges = array_fuse($old_edges);
$new_edges = $this->getPHIDsAffectedByActions($object);
$new_edges = array_fuse($new_edges);
$add_edges = array_diff_key($new_edges, $old_edges);
$rem_edges = array_diff_key($old_edges, $new_edges);
if (!$add_edges && !$rem_edges) {
return;
protected function getIndexEdgeType() {
return HeraldRuleActionAffectsObjectEdgeType::EDGECONST;
}
$editor = new PhabricatorEdgeEditor();
protected function getIndexDestinationPHIDs($object) {
$rule = $object;
foreach ($add_edges as $phid) {
$editor->addEdge($object->getPHID(), $edge_type, $phid);
}
foreach ($rem_edges as $phid) {
$editor->removeEdge($object->getPHID(), $edge_type, $phid);
}
$editor->save();
}
public function getIndexVersion($object) {
$phids = $this->getPHIDsAffectedByActions($object);
sort($phids);
$phids = implode(':', $phids);
return PhabricatorHash::digestForIndex($phids);
}
private function getPHIDsAffectedByActions(HeraldRule $rule) {
$viewer = $this->getViewer();
$rule = id(new HeraldRuleQuery())

View file

@ -0,0 +1,8 @@
<?php
final class PhabricatorProfileMenuItemAffectsObjectEdgeType
extends PhabricatorEdgeType {
const EDGECONST = 70;
}

View file

@ -121,5 +121,8 @@ final class PhabricatorProfileMenuEditor
return $errors;
}
protected function supportsSearch() {
return true;
}
}

View file

@ -0,0 +1,50 @@
<?php
abstract class PhabricatorEdgeIndexEngineExtension
extends PhabricatorIndexEngineExtension {
abstract protected function getIndexEdgeType();
abstract protected function getIndexDestinationPHIDs($object);
final public function indexObject(
PhabricatorIndexEngine $engine,
$object) {
$edge_type = $this->getIndexEdgeType();
$old_edges = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
$edge_type);
$old_edges = array_fuse($old_edges);
$new_edges = $this->getIndexDestinationPHIDs($object);
$new_edges = array_fuse($new_edges);
$add_edges = array_diff_key($new_edges, $old_edges);
$rem_edges = array_diff_key($old_edges, $new_edges);
if (!$add_edges && !$rem_edges) {
return;
}
$editor = new PhabricatorEdgeEditor();
foreach ($add_edges as $phid) {
$editor->addEdge($object->getPHID(), $edge_type, $phid);
}
foreach ($rem_edges as $phid) {
$editor->removeEdge($object->getPHID(), $edge_type, $phid);
}
$editor->save();
}
final public function getIndexVersion($object) {
$phids = $this->getIndexDestinationPHIDs($object);
sort($phids);
$phids = implode(':', $phids);
return PhabricatorHash::digestForIndex($phids);
}
}

View file

@ -0,0 +1,28 @@
<?php
final class PhabricatorProfileMenuItemIndexEngineExtension
extends PhabricatorEdgeIndexEngineExtension {
const EXTENSIONKEY = 'profile.menu.item';
public function getExtensionName() {
return pht('Profile Menu Item');
}
public function shouldIndexObject($object) {
if (!($object instanceof PhabricatorProfileMenuItemConfiguration)) {
return false;
}
return true;
}
protected function getIndexEdgeType() {
return PhabricatorProfileMenuItemAffectsObjectEdgeType::EDGECONST;
}
protected function getIndexDestinationPHIDs($object) {
return $object->getAffectedObjectPHIDs();
}
}

View file

@ -36,11 +36,19 @@ final class PhabricatorDashboardProfileMenuItem
return $this->dashboard;
}
public function getAffectedObjectPHIDs(
PhabricatorProfileMenuItemConfiguration $config) {
return array(
$this->getDashboardPHID($config),
);
}
public function newPageContent(
PhabricatorProfileMenuItemConfiguration $config) {
$viewer = $this->getViewer();
$dashboard_phid = $config->getMenuItemProperty('dashboardPHID');
$dashboard_phid = $this->getDashboardPHID($config);
// Reload the dashboard to attach panels, which we need for rendering.
$dashboard = id(new PhabricatorDashboardQuery())
@ -71,7 +79,7 @@ final class PhabricatorDashboardProfileMenuItem
$viewer = $this->getViewer();
$dashboard_phids = array();
foreach ($items as $item) {
$dashboard_phids[] = $item->getMenuItemProperty('dashboardPHID');
$dashboard_phids[] = $this->getDashboardPHID($item);
}
$dashboards = id(new PhabricatorDashboardQuery())
@ -83,7 +91,7 @@ final class PhabricatorDashboardProfileMenuItem
$dashboards = mpull($dashboards, null, 'getPHID');
foreach ($items as $item) {
$dashboard_phid = $item->getMenuItemProperty('dashboardPHID');
$dashboard_phid = $this->getDashboardPHID($item);
$dashboard = idx($dashboards, $dashboard_phid, null);
$menu_item = $item->getMenuItem();
@ -125,7 +133,7 @@ final class PhabricatorDashboardProfileMenuItem
->setLabel(pht('Dashboard'))
->setIsRequired(true)
->setDatasource(new PhabricatorDashboardDatasource())
->setSingleValue($config->getMenuItemProperty('dashboardPHID')),
->setSingleValue($this->getDashboardPHID($config)),
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
@ -226,6 +234,11 @@ final class PhabricatorDashboardProfileMenuItem
return $errors;
}
private function getDashboardPHID(
PhabricatorProfileMenuItemConfiguration $config) {
return $config->getMenuItemProperty('dashboardPHID');
}
private function getDashboardHandle() {
return $this->dashboardHandle;
}

View file

@ -159,4 +159,9 @@ abstract class PhabricatorProfileMenuItem extends Phobject {
));
}
public function getAffectedObjectPHIDs(
PhabricatorProfileMenuItemConfiguration $config) {
return array();
}
}

View file

@ -8,6 +8,7 @@ final class PhabricatorProfileMenuItemConfigurationQuery
private $profilePHIDs;
private $customPHIDs;
private $includeGlobal;
private $affectedObjectPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -30,6 +31,11 @@ final class PhabricatorProfileMenuItemConfigurationQuery
return $this;
}
public function withAffectedObjectPHIDs(array $phids) {
$this->affectedObjectPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorProfileMenuItemConfiguration();
}
@ -44,21 +50,21 @@ final class PhabricatorProfileMenuItemConfigurationQuery
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
'config.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
'config.phid IN (%Ls)',
$this->phids);
}
if ($this->profilePHIDs !== null) {
$where[] = qsprintf(
$conn,
'profilePHID IN (%Ls)',
'config.profilePHID IN (%Ls)',
$this->profilePHIDs);
}
@ -66,23 +72,45 @@ final class PhabricatorProfileMenuItemConfigurationQuery
if ($this->customPHIDs && $this->includeGlobal) {
$where[] = qsprintf(
$conn,
'customPHID IN (%Ls) OR customPHID IS NULL',
'config.customPHID IN (%Ls) OR config.customPHID IS NULL',
$this->customPHIDs);
} else if ($this->customPHIDs) {
$where[] = qsprintf(
$conn,
'customPHID IN (%Ls)',
'config.customPHID IN (%Ls)',
$this->customPHIDs);
} else {
$where[] = qsprintf(
$conn,
'customPHID IS NULL');
'config.customPHID IS NULL');
}
}
if ($this->affectedObjectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'affected.dst IN (%Ls)',
$this->affectedObjectPHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->affectedObjectPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T affected ON affected.src = config.phid
AND affected.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorProfileMenuItemAffectsObjectEdgeType::EDGECONST);
}
return $joins;
}
protected function willFilterPage(array $page) {
$items = PhabricatorProfileMenuItem::getAllMenuItems();
foreach ($page as $key => $item) {
@ -128,4 +156,8 @@ final class PhabricatorProfileMenuItemConfigurationQuery
return 'PhabricatorSearchApplication';
}
protected function getPrimaryTableAlias() {
return 'config';
}
}

View file

@ -5,7 +5,8 @@ final class PhabricatorProfileMenuItemConfiguration
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorApplicationTransactionInterface {
PhabricatorApplicationTransactionInterface,
PhabricatorIndexableInterface {
protected $profilePHID;
protected $menuItemKey;
@ -255,6 +256,49 @@ final class PhabricatorProfileMenuItemConfiguration
return false;
}
public function getAffectedObjectPHIDs() {
return $this->getMenuItem()->getAffectedObjectPHIDs($this);
}
public function getProfileMenuTypeDescription() {
$profile_phid = $this->getProfilePHID();
$home_phid = id(new PhabricatorHomeApplication())->getPHID();
if ($profile_phid === $home_phid) {
return pht('Home Menu');
}
$favorites_phid = id(new PhabricatorFavoritesApplication())->getPHID();
if ($profile_phid === $favorites_phid) {
return pht('Favorites Menu');
}
switch (phid_get_type($profile_phid)) {
case PhabricatorProjectProjectPHIDType::TYPECONST:
return pht('Project Menu');
case PhabricatorDashboardPortalPHIDType::TYPECONST:
return pht('Portal Menu');
}
return pht('Profile Menu');
}
public function newUsageSortVector() {
// Used to sort items in contexts where we're showing the usage of an
// object in menus, like "Dashboard Used By" on Dashboard pages.
// Sort usage as a custom item after usage as a global item.
if ($this->getCustomPHID()) {
$is_personal = 1;
} else {
$is_personal = 0;
}
return id(new PhutilSortVector())
->addInt($is_personal)
->addInt($this->getID());
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -261,7 +261,8 @@
/* - Action Icon ----------------------------------------------------------- */
.phui-list-sidenav .phui-list-item-has-action-icon .phui-list-item-action-href {
.phabricator-nav-local .phui-list-item-has-action-icon
.phui-list-item-action-href {
position: absolute;
width: 28px;
top: 0;
@ -273,26 +274,27 @@
display: none;
}
.phui-list-sidenav .phui-list-item-has-action-icon.phui-list-item-selected
.phabricator-nav-local .phui-list-item-has-action-icon.phui-list-item-selected
.phui-list-item-href {
padding-right: 32px;
}
.phui-list-sidenav .phui-list-item-has-action-icon.phui-list-item-selected
.phabricator-nav-local .phui-list-item-has-action-icon.phui-list-item-selected
.phui-list-item-action-href {
display: block;
}
.phui-list-sidenav .phui-list-item-has-action-icon
.phabricator-nav-local .phui-list-item-has-action-icon
.phui-list-item-action-href:hover {
background-color: rgba({$alphablack},.05);
}
.phui-list-sidenav .phui-list-item-has-action-icon .phui-list-item-action-icon {
.phabricator-nav-local .phui-list-item-has-action-icon
.phui-list-item-action-icon {
opacity: 0.5;
}
.phui-list-sidenav .phui-list-item-has-action-icon
.phabricator-nav-local .phui-list-item-has-action-icon
.phui-list-item-action-href:hover
.phui-list-item-action-icon {
opacity: 1;