diff --git a/resources/sql/autopatches/20140430.dash.2.edge.sql b/resources/sql/autopatches/20140430.dash.2.edge.sql new file mode 100644 index 0000000000..0e3ef01f60 --- /dev/null +++ b/resources/sql/autopatches/20140430.dash.2.edge.sql @@ -0,0 +1,15 @@ +CREATE TABLE {$NAMESPACE}_dashboard.edge ( + src VARCHAR(64) NOT NULL COLLATE utf8_bin, + type VARCHAR(64) NOT NULL COLLATE utf8_bin, + dst VARCHAR(64) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + seq INT UNSIGNED NOT NULL, + dataID INT UNSIGNED, + PRIMARY KEY (src, type, dst), + KEY (src, type, dateCreated, seq) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_dashboard.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE utf8_bin +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fb486a2134..085c9d628e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1432,6 +1432,7 @@ phutil_register_library_map(array( 'PhabricatorDaemonReference' => 'infrastructure/daemon/control/PhabricatorDaemonReference.php', 'PhabricatorDaemonTaskGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php', 'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php', + 'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php', 'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php', 'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php', 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php', @@ -4232,6 +4233,7 @@ phutil_register_library_map(array( 0 => 'PhabricatorDashboardDAO', 1 => 'PhabricatorPolicyInterface', ), + 'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardController' => 'PhabricatorController', 'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', diff --git a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php index c4f5d05fbc..71f6a289b7 100644 --- a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php +++ b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php @@ -23,6 +23,7 @@ final class PhabricatorApplicationDashboard extends PhabricatorApplication { 'view/(?P\d+)/' => 'PhabricatorDashboardViewController', 'create/' => 'PhabricatorDashboardEditController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardEditController', + 'addpanel/(?P\d+)/' => 'PhabricatorDashboardAddPanelController', 'panel/' => array( '(?:query/(?P[^/]+)/)?' diff --git a/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php new file mode 100644 index 0000000000..9fc8d9720c --- /dev/null +++ b/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php @@ -0,0 +1,94 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$dashboard) { + return new Aphront404Response(); + } + + $dashboard_uri = $this->getApplicationURI('view/'.$dashboard->getID().'/'); + + $v_panel = $request->getStr('panel'); + $e_panel = true; + $errors = array(); + if ($request->isFormPost()) { + if (strlen($v_panel)) { + $panel = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($v_panel)) + ->withTypes(array(PhabricatorDashboardPHIDTypePanel::TYPECONST)) + ->executeOne(); + if (!$panel) { + $errors[] = pht('No such panel!'); + $e_panel = pht('Invalid'); + } + } else { + $errors[] = pht('Name a panel to add.'); + $e_panel = pht('Required'); + } + + if (!$errors) { + $xactions = array(); + $xactions[] = id(new PhabricatorDashboardTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorEdgeConfig::TYPE_DASHBOARD_HAS_PANEL) + ->setNewValue( + array( + '+' => array( + $panel->getPHID() => $panel->getPHID(), + ), + )); + + $editor = id(new PhabricatorDashboardTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($dashboard_uri); + } + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendRemarkupInstructions( + pht('Enter a panel monogram like `W123`.')) + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('panel') + ->setLabel(pht('Panel')) + ->setValue($v_panel) + ->setError($e_panel)); + + return $this->newDialog() + ->setTitle(pht('Add Panel')) + ->setErrors($errors) + ->appendChild($form->buildLayoutView()) + ->addCancelButton($dashboard_uri) + ->addSubmitButton(pht('Add Panel')); + } + +} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php index a3a63a0965..9cc06a8595 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php @@ -124,6 +124,15 @@ final class PhabricatorDashboardPanelViewController pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); + $dashboard_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $panel->getPHID(), + PhabricatorEdgeConfig::TYPE_PANEL_HAS_DASHBOARD); + $this->loadHandles($dashboard_phids); + + $properties->addProperty( + pht('Appears On'), + $this->renderHandlesForPHIDs($dashboard_phids)); + return $properties; } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php index acd3c03768..94c0416792 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php @@ -16,6 +16,7 @@ final class PhabricatorDashboardViewController $dashboard = id(new PhabricatorDashboardQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) + ->needPanels(true) ->executeOne(); if (!$dashboard) { return new Aphront404Response(); @@ -77,6 +78,14 @@ final class PhabricatorDashboardViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Add Panel')) + ->setIcon('new') + ->setHref($this->getApplicationURI("addpanel/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + return $actions; } @@ -95,6 +104,13 @@ final class PhabricatorDashboardViewController pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); + $panel_phids = $dashboard->getPanelPHIDs(); + $this->loadHandles($panel_phids); + + $properties->addProperty( + pht('Panels'), + $this->renderHandlesForPHIDs($panel_phids)); + return $properties; } diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php index 19dc8da69e..15b32cedb0 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php @@ -8,6 +8,7 @@ final class PhabricatorDashboardPanelTransactionEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorDashboardPanelTransaction::TYPE_NAME; diff --git a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php index 023dd704f2..968c79468a 100644 --- a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php +++ b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php @@ -8,6 +8,7 @@ final class PhabricatorDashboardTransactionEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorDashboardTransaction::TYPE_NAME; @@ -51,6 +52,8 @@ final class PhabricatorDashboardTransactionEditor case PhabricatorTransactions::TYPE_EDIT_POLICY: $object->setEditPolicy($xaction->getNewValue()); return; + case PhabricatorTransactions::TYPE_EDGE: + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -63,6 +66,8 @@ final class PhabricatorDashboardTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorDashboardTransaction::TYPE_NAME: return; + case PhabricatorTransactions::TYPE_EDGE: + return; } return parent::applyCustomExternalTransaction($object, $xaction); diff --git a/src/applications/dashboard/query/PhabricatorDashboardQuery.php b/src/applications/dashboard/query/PhabricatorDashboardQuery.php index 461ec32a41..f236fb0066 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardQuery.php +++ b/src/applications/dashboard/query/PhabricatorDashboardQuery.php @@ -6,6 +6,8 @@ final class PhabricatorDashboardQuery private $ids; private $phids; + private $needPanels; + public function withIDs(array $ids) { $this->ids = $ids; return $this; @@ -16,6 +18,11 @@ final class PhabricatorDashboardQuery return $this; } + public function needPanels($need_panels) { + $this->needPanels = $need_panels; + return $this; + } + protected function loadPage() { $table = new PhabricatorDashboard(); $conn_r = $table->establishConnection('r'); @@ -31,6 +38,41 @@ final class PhabricatorDashboardQuery return $table->loadAllFromArray($data); } + protected function didFilterPage(array $dashboards) { + if ($this->needPanels) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($dashboards, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorEdgeConfig::TYPE_DASHBOARD_HAS_PANEL, + )); + $edge_query->execute(); + + $panel_phids = $edge_query->getDestinationPHIDs(); + if ($panel_phids) { + $panels = id(new PhabricatorDashboardPanelQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($panel_phids) + ->execute(); + $panels = mpull($panels, null, 'getPHID'); + } else { + $panels = array(); + } + + foreach ($dashboards as $dashboard) { + $dashboard_phids = $edge_query->getDestinationPHIDs( + array($dashboard->getPHID())); + $dashboard_panels = array_select_keys($panels, $dashboard_phids); + + $dashboard->attachPanelPHIDs($dashboard_phids); + $dashboard->attachPanels($dashboard_panels); + } + } + + return $dashboards; + } + protected function buildWhereClause($conn_r) { $where = array(); diff --git a/src/applications/dashboard/storage/PhabricatorDashboard.php b/src/applications/dashboard/storage/PhabricatorDashboard.php index 0cefda2cd7..8753e0d484 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboard.php +++ b/src/applications/dashboard/storage/PhabricatorDashboard.php @@ -10,6 +10,9 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO protected $viewPolicy; protected $editPolicy; + private $panelPHIDs = self::ATTACHABLE; + private $panels = self::ATTACHABLE; + public static function initializeNewDashboard(PhabricatorUser $actor) { return id(new PhabricatorDashboard()) ->setName('') @@ -28,6 +31,25 @@ final class PhabricatorDashboard extends PhabricatorDashboardDAO PhabricatorDashboardPHIDTypeDashboard::TYPECONST); } + public function attachPanelPHIDs(array $phids) { + $this->panelPHIDs = $phids; + return $this; + } + + public function getPanelPHIDs() { + return $this->assertAttached($this->panelPHIDs); + } + + public function attachPanels(array $panels) { + assert_instances_of($panels, 'PhabricatorDashboardPanel'); + $this->panels = $panels; + return $this; + } + + public function getPanels() { + return $this->assertAttached($this->panels); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php index 8614a38372..26969ebb16 100644 --- a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php +++ b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php @@ -69,6 +69,9 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { const TYPE_OBJECT_HAS_COLUMN = 43; const TYPE_COLUMN_HAS_OBJECT = 44; + const TYPE_DASHBOARD_HAS_PANEL = 45; + const TYPE_PANEL_HAS_DASHBOARD = 46; + const TYPE_TEST_NO_CYCLE = 9000; const TYPE_PHOB_HAS_ASANATASK = 80001; @@ -153,6 +156,9 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { self::TYPE_OBJECT_HAS_COLUMN => self::TYPE_COLUMN_HAS_OBJECT, self::TYPE_COLUMN_HAS_OBJECT => self::TYPE_OBJECT_HAS_COLUMN, + + self::TYPE_PANEL_HAS_DASHBOARD => self::TYPE_DASHBOARD_HAS_PANEL, + self::TYPE_DASHBOARD_HAS_PANEL => self::TYPE_PANEL_HAS_DASHBOARD, ); return idx($map, $edge_type); @@ -259,6 +265,10 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { return '%s edited reviewer(s), added %d: %s; removed %d: %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s edited mock(s), added %d: %s; removed %d: %s.'; + case self::TYPE_DASHBOARD_HAS_PANEL: + return '%s edited panel(s), added %d: %s; removed %d: %s.'; + case self::TYPE_PANEL_HAS_DASHBOARD: + return '%s edited dashboard(s), added %d: %s; removed %d: %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: @@ -329,6 +339,10 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { return '%s added %d reviewer(s): %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s added %d mock(s): %s.'; + case self::TYPE_DASHBOARD_HAS_PANEL: + return '%s added %d panel(s): %s.'; + case self::TYPE_PANEL_HAS_DASHBOARD: + return '%s added %d dashboard(s): %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: @@ -400,6 +414,10 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { return '%s removed %d reviewer(s): %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s removed %d mock(s): %s.'; + case self::TYPE_DASHBOARD_HAS_PANEL: + return '%s removed %d panel(s): %s.'; + case self::TYPE_PANEL_HAS_DASHBOARD: + return '%s removed %d dashboard(s): %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: @@ -469,6 +487,10 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { return '%s updated reviewers of %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s updated mocks of %s.'; + case self::TYPE_PANEL_HAS_DASHBOARD: + return '%s updated panels for %s.'; + case self::TYPE_PANEL_HAS_DASHBOARD: + return '%s updated dashboards for %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: