mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 09:12:41 +01:00
Add Projects to Dashboards and Panels
Summary: Making an attempt here. This adds the ability to set Projects on Dashboards and Dashboard Panels. Most of this went smooth, but I can't figure out why the queries don't automatically show searching by Projects. I'm stumped. Rest seems fine. Test Plan: Assign a Project to a Dashboard and a Panel, see Project show up, edit it, see transactions. Reviewers: btrahan, epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13656
This commit is contained in:
parent
f1222f956a
commit
6be53bd916
10 changed files with 200 additions and 108 deletions
|
@ -5622,6 +5622,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorFlaggableInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
),
|
||||
'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController',
|
||||
'PhabricatorDashboardApplication' => 'PhabricatorApplication',
|
||||
|
@ -5644,6 +5645,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorFlaggableInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController',
|
||||
|
|
|
@ -27,7 +27,10 @@ final class PhabricatorDashboardEditController
|
|||
if (!$dashboard) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$dashboard->getPHID(),
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
|
||||
$v_projects = array_reverse($v_projects);
|
||||
$is_new = false;
|
||||
} else {
|
||||
if (!$request->getStr('edit')) {
|
||||
|
@ -44,7 +47,7 @@ final class PhabricatorDashboardEditController
|
|||
}
|
||||
|
||||
$dashboard = PhabricatorDashboard::initializeNewDashboard($viewer);
|
||||
|
||||
$v_projects = array();
|
||||
$is_new = true;
|
||||
}
|
||||
|
||||
|
@ -79,6 +82,7 @@ final class PhabricatorDashboardEditController
|
|||
$v_layout_mode = $request->getStr('layout_mode');
|
||||
$v_view_policy = $request->getStr('viewPolicy');
|
||||
$v_edit_policy = $request->getStr('editPolicy');
|
||||
$v_projects = $request->getArr('projects');
|
||||
|
||||
$xactions = array();
|
||||
|
||||
|
@ -100,6 +104,12 @@ final class PhabricatorDashboardEditController
|
|||
->setTransactionType($type_edit_policy)
|
||||
->setNewValue($v_edit_policy);
|
||||
|
||||
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
|
||||
$xactions[] = id(new PhabricatorDashboardTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $proj_edge_type)
|
||||
->setNewValue(array('=' => array_fuse($v_projects)));
|
||||
|
||||
try {
|
||||
$editor = id(new PhabricatorDashboardTransactionEditor())
|
||||
->setActor($viewer)
|
||||
|
@ -153,8 +163,16 @@ final class PhabricatorDashboardEditController
|
|||
->setLabel(pht('Layout Mode'))
|
||||
->setName('layout_mode')
|
||||
->setValue($v_layout_mode)
|
||||
->setOptions($layout_mode_options))
|
||||
->appendChild(
|
||||
->setOptions($layout_mode_options));
|
||||
|
||||
$form->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()));
|
||||
|
||||
$form->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue($button)
|
||||
->addCancelButton($cancel_uri));
|
||||
|
|
|
@ -170,6 +170,8 @@ final class PhabricatorDashboardManageController
|
|||
pht('Panels'),
|
||||
$viewer->renderHandleList($dashboard->getPanelPHIDs()));
|
||||
|
||||
$properties->invokeWillRenderEvent();
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ final class PhabricatorDashboardPanelEditController
|
|||
if (!$panel) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$panel->getPHID(),
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
|
||||
$v_projects = array_reverse($v_projects);
|
||||
|
||||
if ($dashboard) {
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
|
@ -86,6 +90,7 @@ final class PhabricatorDashboardPanelEditController
|
|||
if (empty($types[$type])) {
|
||||
return $this->processPanelTypeRequest($request);
|
||||
}
|
||||
$v_projects = array();
|
||||
|
||||
$panel->setPanelType($type);
|
||||
}
|
||||
|
@ -136,6 +141,7 @@ final class PhabricatorDashboardPanelEditController
|
|||
$v_name = $request->getStr('name');
|
||||
$v_view_policy = $request->getStr('viewPolicy');
|
||||
$v_edit_policy = $request->getStr('editPolicy');
|
||||
$v_projects = $request->getArr('projects');
|
||||
|
||||
$type_name = PhabricatorDashboardPanelTransaction::TYPE_NAME;
|
||||
$type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
|
@ -155,6 +161,12 @@ final class PhabricatorDashboardPanelEditController
|
|||
->setTransactionType($type_edit_policy)
|
||||
->setNewValue($v_edit_policy);
|
||||
|
||||
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
|
||||
$xactions[] = id(new PhabricatorDashboardPanelTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $proj_edge_type)
|
||||
->setNewValue(array('=' => array_fuse($v_projects)));
|
||||
|
||||
$field_xactions = $field_list->buildFieldTransactionsFromRequest(
|
||||
new PhabricatorDashboardPanelTransaction(),
|
||||
$request);
|
||||
|
@ -232,6 +244,13 @@ final class PhabricatorDashboardPanelEditController
|
|||
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
|
||||
->setPolicies($policies));
|
||||
|
||||
$form->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Projects'))
|
||||
->setName('projects')
|
||||
->setValue($v_projects)
|
||||
->setDatasource(new PhabricatorProjectDatasource()));
|
||||
|
||||
$field_list->appendFieldsToForm($form);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
|
|
|
@ -29,54 +29,53 @@ final class PhabricatorDashboardPanelQuery
|
|||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new PhabricatorDashboardPanel();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT * FROM %T %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r),
|
||||
$this->buildOrderClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
return $table->loadAllFromArray($data);
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
$where = array();
|
||||
public function newResultObject() {
|
||||
// TODO: If we don't do this, SearchEngine explodes when trying to
|
||||
// enumerate custom fields. For now, just give the panel a default panel
|
||||
// type so custom fields work. In the long run, we may want to find a
|
||||
// cleaner or more general approach for this.
|
||||
$text_type = id(new PhabricatorDashboardTextPanelType())
|
||||
->getPanelTypeKey();
|
||||
|
||||
return id(new PhabricatorDashboardPanel())
|
||||
->setPanelType($text_type);
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->archived !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'isArchived = %d',
|
||||
(int)$this->archived);
|
||||
}
|
||||
|
||||
if ($this->panelTypes !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'panelType IN (%Ls)',
|
||||
$this->panelTypes);
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
|
|
|
@ -11,18 +11,14 @@ final class PhabricatorDashboardPanelSearchEngine
|
|||
return 'PhabricatorDashboardApplication';
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromRequest(AphrontRequest $request) {
|
||||
$saved = new PhabricatorSavedQuery();
|
||||
$saved->setParameter('status', $request->getStr('status'));
|
||||
$saved->setParameter('paneltype', $request->getStr('paneltype'));
|
||||
return $saved;
|
||||
public function newQuery() {
|
||||
return new PhabricatorDashboardPanelQuery();
|
||||
}
|
||||
|
||||
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
|
||||
$query = id(new PhabricatorDashboardPanelQuery());
|
||||
|
||||
$status = $saved->getParameter('status');
|
||||
switch ($status) {
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
if ($map['status']) {
|
||||
switch ($map['status']) {
|
||||
case 'active':
|
||||
$query->withArchived(false);
|
||||
break;
|
||||
|
@ -32,45 +28,31 @@ final class PhabricatorDashboardPanelSearchEngine
|
|||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$paneltype = $saved->getParameter('paneltype');
|
||||
if ($paneltype) {
|
||||
$query->withPanelTypes(array($paneltype));
|
||||
if ($map['paneltype']) {
|
||||
$query->withPanelTypes(array($map['paneltype']));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function buildSearchForm(
|
||||
AphrontFormView $form,
|
||||
PhabricatorSavedQuery $saved_query) {
|
||||
protected function buildCustomSearchFields() {
|
||||
|
||||
$status = $saved_query->getParameter('status', '');
|
||||
$paneltype = $saved_query->getParameter('paneltype', '');
|
||||
|
||||
$panel_types = PhabricatorDashboardPanelType::getAllPanelTypes();
|
||||
$panel_types = mpull($panel_types, 'getPanelTypeName', 'getPanelTypeKey');
|
||||
asort($panel_types);
|
||||
$panel_types = (array('' => pht('(All Types)')) + $panel_types);
|
||||
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
return array(
|
||||
id(new PhabricatorSearchSelectField())
|
||||
->setKey('status')
|
||||
->setLabel(pht('Status'))
|
||||
->setName('status')
|
||||
->setValue($status)
|
||||
->setOptions(
|
||||
array(
|
||||
'' => pht('(All Panels)'),
|
||||
'active' => pht('Active Panels'),
|
||||
'archived' => pht('Archived Panels'),
|
||||
)))
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
id(new PhabricatorDashboardPanel())
|
||||
->getStatuses()),
|
||||
id(new PhabricatorSearchSelectField())
|
||||
->setKey('paneltype')
|
||||
->setLabel(pht('Panel Type'))
|
||||
->setName('paneltype')
|
||||
->setValue($paneltype)
|
||||
->setOptions($panel_types));
|
||||
->setOptions(
|
||||
id(new PhabricatorDashboardPanel())
|
||||
->getPanelTypes()),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
|
@ -117,13 +99,14 @@ final class PhabricatorDashboardPanelSearchEngine
|
|||
$impl = $panel->getImplementation();
|
||||
if ($impl) {
|
||||
$type_text = $impl->getPanelTypeName();
|
||||
$type_icon = 'none';
|
||||
} else {
|
||||
$type_text = nonempty($panel->getPanelType(), pht('Unknown Type'));
|
||||
$type_icon = 'fa-question';
|
||||
}
|
||||
$item->addAttribute($type_text);
|
||||
|
||||
$item->addIcon($type_icon, $type_text);
|
||||
$properties = $panel->getProperties();
|
||||
$class = idx($properties, 'class');
|
||||
$item->addAttribute($class);
|
||||
|
||||
if ($panel->getIsArchived()) {
|
||||
$item->setDisabled(true);
|
||||
|
|
|
@ -7,6 +7,7 @@ final class PhabricatorDashboardQuery
|
|||
private $phids;
|
||||
|
||||
private $needPanels;
|
||||
private $needProjects;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -23,25 +24,26 @@ final class PhabricatorDashboardQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needProjects($need_projects) {
|
||||
$this->needProjects = $need_projects;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new PhabricatorDashboard();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT * FROM %T %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r),
|
||||
$this->buildOrderClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
return $table->loadAllFromArray($data);
|
||||
public function newResultObject() {
|
||||
return new PhabricatorDashboard();
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $dashboards) {
|
||||
|
||||
$phids = mpull($dashboards, 'getPHID');
|
||||
|
||||
if ($this->needPanels) {
|
||||
$edge_query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs(mpull($dashboards, 'getPHID'))
|
||||
->withSourcePHIDs($phids)
|
||||
->withEdgeTypes(
|
||||
array(
|
||||
PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST,
|
||||
|
@ -70,29 +72,43 @@ final class PhabricatorDashboardQuery
|
|||
}
|
||||
}
|
||||
|
||||
if ($this->needProjects) {
|
||||
$edge_query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($phids)
|
||||
->withEdgeTypes(
|
||||
array(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
));
|
||||
$edge_query->execute();
|
||||
|
||||
foreach ($dashboards as $dashboard) {
|
||||
$project_phids = $edge_query->getDestinationPHIDs(
|
||||
array($dashboard->getPHID()));
|
||||
$dashboard->attachProjectPHIDs($project_phids);
|
||||
}
|
||||
}
|
||||
|
||||
return $dashboards;
|
||||
}
|
||||
|
||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
$where = array();
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids) {
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids) {
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
|
|
|
@ -11,18 +11,13 @@ final class PhabricatorDashboardSearchEngine
|
|||
return 'PhabricatorDashboardApplication';
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromRequest(AphrontRequest $request) {
|
||||
return new PhabricatorSavedQuery();
|
||||
public function newQuery() {
|
||||
return id(new PhabricatorDashboardQuery())
|
||||
->needProjects(true);
|
||||
}
|
||||
|
||||
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
|
||||
return new PhabricatorDashboardQuery();
|
||||
}
|
||||
|
||||
public function buildSearchForm(
|
||||
AphrontFormView $form,
|
||||
PhabricatorSavedQuery $saved_query) {
|
||||
return;
|
||||
protected function buildCustomSearchFields() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
|
@ -48,6 +43,11 @@ final class PhabricatorDashboardSearchEngine
|
|||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $dashboards,
|
||||
PhabricatorSavedQuery $query,
|
||||
|
@ -70,6 +70,18 @@ final class PhabricatorDashboardSearchEngine
|
|||
$installs = array();
|
||||
}
|
||||
|
||||
$proj_phids = array();
|
||||
foreach ($dashboards as $dashboard) {
|
||||
foreach ($dashboard->getProjectPHIDs() as $project_phid) {
|
||||
$proj_phids[] = $project_phid;
|
||||
}
|
||||
}
|
||||
|
||||
$proj_handles = id(new PhabricatorHandleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($proj_phids)
|
||||
->execute();
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
$list->setUser($viewer);
|
||||
$list->initBehavior('phabricator-tooltips', array());
|
||||
|
@ -101,6 +113,17 @@ final class PhabricatorDashboardSearchEngine
|
|||
}
|
||||
}
|
||||
|
||||
$project_handles = array_select_keys(
|
||||
$proj_handles,
|
||||
$dashboard->getProjectPHIDs());
|
||||
|
||||
$item->addAttribute(
|
||||
id(new PHUIHandleTagListView())
|
||||
->setLimit(4)
|
||||
->setNoDataString(pht('No Projects'))
|
||||
->setSlim(true)
|
||||
->setHandles($project_handles));
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
|
@ -109,7 +132,6 @@ final class PhabricatorDashboardSearchEngine
|
|||
$result->setNoDataString(pht('No dashboards found.'));
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
|
|||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorFlaggableInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorProjectInterface {
|
||||
|
||||
protected $name;
|
||||
protected $viewPolicy;
|
||||
|
@ -17,6 +18,8 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
|
|||
|
||||
private $panelPHIDs = self::ATTACHABLE;
|
||||
private $panels = self::ATTACHABLE;
|
||||
private $edgeProjectPHIDs = self::ATTACHABLE;
|
||||
|
||||
|
||||
public static function initializeNewDashboard(PhabricatorUser $actor) {
|
||||
return id(new PhabricatorDashboard())
|
||||
|
@ -65,6 +68,15 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getProjectPHIDs() {
|
||||
return $this->assertAttached($this->edgeProjectPHIDs);
|
||||
}
|
||||
|
||||
public function attachProjectPHIDs(array $phids) {
|
||||
$this->edgeProjectPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function attachPanelPHIDs(array $phids) {
|
||||
$this->panelPHIDs = $phids;
|
||||
return $this;
|
||||
|
|
|
@ -10,6 +10,7 @@ final class PhabricatorDashboardPanel
|
|||
PhabricatorPolicyInterface,
|
||||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorFlaggableInterface,
|
||||
PhabricatorProjectInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
|
||||
protected $name;
|
||||
|
@ -71,6 +72,24 @@ final class PhabricatorDashboardPanel
|
|||
return 'W'.$this->getID();
|
||||
}
|
||||
|
||||
public function getPanelTypes() {
|
||||
$panel_types = PhabricatorDashboardPanelType::getAllPanelTypes();
|
||||
$panel_types = mpull($panel_types, 'getPanelTypeName', 'getPanelTypeKey');
|
||||
asort($panel_types);
|
||||
$panel_types = (array('' => pht('(All Types)')) + $panel_types);
|
||||
return $panel_types;
|
||||
}
|
||||
|
||||
public function getStatuses() {
|
||||
$statuses =
|
||||
array(
|
||||
'' => pht('(All Panels)'),
|
||||
'active' => pht('Active Panels'),
|
||||
'archived' => pht('Archived Panels'),
|
||||
);
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public function getImplementation() {
|
||||
return idx(
|
||||
PhabricatorDashboardPanelType::getAllPanelTypes(),
|
||||
|
|
Loading…
Reference in a new issue