diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php --- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php @@ -329,6 +329,18 @@ $actions = array(); if ($panel) { + $panel_actions = $panel->newHeaderEditActions( + $viewer, + $context_phid); + + if ($panel_actions) { + foreach ($panel_actions as $panel_action) { + $actions[] = $panel_action; + } + $actions[] = id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER); + } + $panel_id = $panel->getID(); $edit_uri = "/dashboard/panel/edit/{$panel_id}/"; diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php --- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php @@ -63,4 +63,11 @@ return array(); } + public function newHeaderEditActions( + PhabricatorDashboardPanel $panel, + PhabricatorUser $viewer, + $context_phid) { + return array(); + } + } diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php --- a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php @@ -236,4 +236,26 @@ return $engine; } + public function newHeaderEditActions( + PhabricatorDashboardPanel $panel, + PhabricatorUser $viewer, + $context_phid) { + $actions = array(); + + $engine = $this->getSearchEngine($panel); + + $customize_uri = $engine->getCustomizeURI( + $panel->getProperty('key'), + $panel->getPHID(), + $context_phid); + + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-pencil-square-o') + ->setName(pht('Customize Query')) + ->setWorkflow(true) + ->setHref($customize_uri); + + return $actions; + } + } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php --- a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php +++ b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php @@ -107,6 +107,15 @@ return $this->requireImplementation()->getEditEngineFields($this); } + public function newHeaderEditActions( + PhabricatorUser $viewer, + $context_phid) { + return $this->requireImplementation()->newHeaderEditActions( + $this, + $viewer, + $context_phid); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -80,6 +80,10 @@ return $this->processExportRequest(); } + if ($query_action === 'customize') { + return $this->processCustomizeRequest(); + } + $key = $this->getQueryKey(); if ($key == 'edit') { return $this->processEditRequest(); @@ -985,4 +989,106 @@ $editor->applyTransactions($preferences, $xactions); } + private function processCustomizeRequest() { + $viewer = $this->getViewer(); + $engine = $this->getSearchEngine(); + $request = $this->getRequest(); + + $object_phid = $request->getStr('search.objectPHID'); + $context_phid = $request->getStr('search.contextPHID'); + + // For now, the object can only be a dashboard panel, so just use a panel + // query explicitly. + $object = id(new PhabricatorDashboardPanelQuery()) + ->setViewer($viewer) + ->withPHIDs(array($object_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$object) { + return new Aphront404Response(); + } + + $object_name = pht('%s %s', $object->getMonogram(), $object->getName()); + + // Likewise, the context object can only be a dashboard. + if (strlen($context_phid)) { + $context = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withPHIDs(array($context_phid)) + ->executeOne(); + if (!$context) { + return new Aphront404Response(); + } + } else { + $context = $object; + } + + $done_uri = $context->getURI(); + + if ($request->isFormPost()) { + $saved_query = $engine->buildSavedQueryFromRequest($request); + $engine->saveQuery($saved_query); + $query_key = $saved_query->getQueryKey(); + } else { + $query_key = $this->getQueryKey(); + if ($engine->isBuiltinQuery($query_key)) { + $saved_query = $engine->buildSavedQueryFromBuiltin($query_key); + } else if ($query_key) { + $saved_query = id(new PhabricatorSavedQueryQuery()) + ->setViewer($viewer) + ->withQueryKeys(array($query_key)) + ->executeOne(); + } else { + $saved_query = null; + } + } + + if (!$saved_query) { + return new Aphront404Response(); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->addHiddenInput('search.objectPHID', $object_phid) + ->addHiddenInput('search.contextPHID', $context_phid) + ->setAction($request->getPath()); + + $engine->buildSearchForm($form, $saved_query); + + $errors = $engine->getErrors(); + if ($request->isFormPost()) { + if (!$errors) { + $xactions = array(); + + // Since this workflow is currently used only by dashboard panels, + // we can hard-code how the edit works. + $xactions[] = $object->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardQueryPanelQueryTransaction::TRANSACTIONTYPE) + ->setNewValue($query_key); + + $editor = $object->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($object, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + } + + return $this->newDialog() + ->setTitle(pht('Customize Query: %s', $object_name)) + ->setErrors($errors) + ->setWidth(AphrontDialogView::WIDTH_FULL) + ->appendForm($form) + ->addCancelButton($done_uri) + ->addSubmitButton(pht('Save Changes')); + } } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -425,6 +425,19 @@ return $this->getURI('query/'.$query_key.'/export/'); } + public function getCustomizeURI($query_key, $object_phid, $context_phid) { + $params = array( + 'search.objectPHID' => $object_phid, + 'search.contextPHID' => $context_phid, + ); + + $uri = $this->getURI('query/'.$query_key.'/customize/'); + $uri = new PhutilURI($uri, $params); + + return phutil_string_cast($uri); + } + + /** * Return the URI to a path within the application. Used to construct default