diff --git a/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php index 6f3040e4df..a229fcb41c 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php @@ -1,195 +1,169 @@ getViewer(); $v_dashboard = null; - $v_name = null; - $v_column = 0; - $v_engine = $request->getURIData('engineKey'); - $v_query = $request->getURIData('queryKey'); + $e_dashboard = null; + $v_name = null; $e_name = true; - // Validate Engines - $engines = PhabricatorApplicationSearchEngine::getAllEngines(); - foreach ($engines as $name => $engine) { - if (!$engine->canUseInPanelContext()) { - unset($engines[$name]); - } - } - if (!in_array($v_engine, array_keys($engines))) { - return new Aphront404Response(); + $v_engine = $request->getStr('engine'); + if (!strlen($v_engine)) { + $v_engine = $request->getURIData('engineKey'); } - // Validate Queries - $engine = $engines[$v_engine]; - $engine->setViewer($viewer); - $good_query = false; - if ($engine->isBuiltinQuery($v_query)) { - $good_query = true; - } else { - $saved_query = id(new PhabricatorSavedQueryQuery()) - ->setViewer($viewer) - ->withEngineClassNames(array($v_engine)) - ->withQueryKeys(array($v_query)) - ->executeOne(); - if ($saved_query) { - $good_query = true; - } - } - if (!$good_query) { - return new Aphront404Response(); + $v_query = $request->getStr('query'); + if (!strlen($v_query)) { + $v_query = $request->getURIData('queryKey'); } - $named_query = idx($engine->loadEnabledNamedQueries(), $v_query); - if ($named_query) { - $v_name = $named_query->getQueryName(); + $engines = PhabricatorApplicationSearchEngine::getAllEngines(); + $engine = idx($engines, $v_engine); + if ($engine) { + $engine = id(clone $engine) + ->setViewer($viewer); + + $redirect_uri = $engine->getQueryResultsPageURI($v_query); + + $named_query = idx($engine->loadEnabledNamedQueries(), $v_query); + if ($named_query) { + $v_name = $named_query->getQueryName(); + } + } else { + $redirect_uri = '/'; } $errors = array(); + $xaction_name = PhabricatorDashboardPanelNameTransaction::TRANSACTIONTYPE; + $xaction_engine = + PhabricatorDashboardQueryPanelApplicationTransaction::TRANSACTIONTYPE; + $xaction_query = + PhabricatorDashboardQueryPanelQueryTransaction::TRANSACTIONTYPE; + if ($request->isFormPost()) { - $v_dashboard = $request->getInt('dashboardID'); $v_name = $request->getStr('name'); if (!$v_name) { $errors[] = pht('You must provide a name for this panel.'); $e_name = pht('Required'); } - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($v_dashboard)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - if (!$dashboard) { - $errors[] = pht('Please select a valid dashboard.'); + $v_dashboard = head($request->getArr('dashboardPHIDs')); + if (!$v_dashboard) { + $errors[] = pht('You must select a dashboard.'); + $e_dashboard = pht('Required'); + } else { + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withPHIDs(array($v_dashboard)) + ->executeOne(); + if (!$dashboard) { + $errors[] = pht('You must select a valid dashboard.'); + $e_dashboard = pht('Invalid'); + } + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $dashboard, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $errors[] = pht( + 'You must select a dashboard you have permission to edit.'); + } } if (!$errors) { - $redirect_uri = "/dashboard/view/{$v_dashboard}/"; + $done_uri = $dashboard->getURI(); + + // First, create a new panel. $panel_type = id(new PhabricatorDashboardQueryPanelType()) ->getPanelTypeKey(); - $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); - $panel->setPanelType($panel_type); - $field_list = PhabricatorCustomField::getObjectFields( - $panel, - PhabricatorCustomField::ROLE_EDIT); - - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($panel); - - $panel->requireImplementation()->initializeFieldsFromRequest( - $panel, - $field_list, - $request); + $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer) + ->setPanelType($panel_type); $xactions = array(); - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType( - PhabricatorDashboardPanelNameTransaction::TRANSACTIONTYPE) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) - ->setMetadataValue('customfield:key', 'std:dashboard:core:class') - ->setOldValue(null) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_engine) ->setNewValue($v_engine); - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) - ->setMetadataValue('customfield:key', 'std:dashboard:core:key') - ->setOldValue(null) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_query) ->setNewValue($v_query); - $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + $xactions[] = $panel->getApplicationTransactionTemplate() + ->setTransactionType($xaction_name) + ->setNewValue($v_name); + + $editor = $panel->getApplicationTransactionEditor() ->setActor($viewer) - ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->applyTransactions($panel, $xactions); - PhabricatorDashboardTransactionEditor::addPanelToDashboard( - $viewer, - PhabricatorContentSource::newFromRequest($request), - $panel, - $dashboard, - $request->getInt('column', 0)); + // Now that we've created a panel, add it to the dashboard. + + $xactions = array(); + + $ref_list = clone $dashboard->getPanelRefList(); + $ref_list->newPanelRef($panel); + $new_panels = $ref_list->toDictionary(); + + $xactions[] = $dashboard->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorDashboardPanelsTransaction::TRANSACTIONTYPE) + ->setNewValue($new_panels); - return id(new AphrontRedirectResponse())->setURI($redirect_uri); + $editor = $dashboard->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($dashboard, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); } } - // Make this a select for now, as we don't expect someone to have - // edit access to a vast number of dashboards. - // Can add optiongroup if needed down the road. - $dashboards = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withStatuses(array( - PhabricatorDashboard::STATUS_ACTIVE, - )) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - $options = mpull($dashboards, 'getName', 'getID'); - asort($options); - - $redirect_uri = $engine->getQueryResultsPageURI($v_query); - - if (!$options) { - $notice = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('You do not have access to any dashboards. To '. - 'continue, please create a dashboard first.')); - - return $this->newDialog() - ->setTitle(pht('No Dashboards')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($notice) - ->addCancelButton($redirect_uri); + if ($v_dashboard) { + $dashboard_phids = array($v_dashboard); + } else { + $dashboard_phids = array(); } $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('engine', $v_engine) - ->addHiddenInput('query', $v_query) - ->addHiddenInput('column', $v_column) - ->appendChild( + ->setViewer($viewer) + ->appendControl( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($v_name) ->setError($e_name)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setUser($this->getViewer()) - ->setValue($v_dashboard) - ->setName('dashboardID') - ->setOptions($options) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setValue($dashboard_phids) + ->setError($e_dashboard) + ->setName('dashboardPHIDs') + ->setLimit(1) + ->setDatasource(new PhabricatorDashboardDatasource()) ->setLabel(pht('Dashboard'))); return $this->newDialog() ->setTitle(pht('Add Panel to Dashboard')) ->setErrors($errors) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) + ->addHiddenInput('engine', $v_engine) + ->addHiddenInput('query', $v_query) + ->appendForm($form) ->addCancelButton($redirect_uri) ->addSubmitButton(pht('Add Panel')); } } diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php index dc05bff994..2f74d0a937 100644 --- a/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php +++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php @@ -1,156 +1,163 @@ getLayoutModeColumns(); $columns = mpull($columns, null, 'getColumnKey'); $default_column = head($columns); $panels = idx($config, 'panels'); if (!is_array($panels)) { $panels = array(); } $seen_panels = array(); $refs = array(); foreach ($panels as $panel) { $panel_phid = idx($panel, 'panelPHID'); if (!strlen($panel_phid)) { continue; } $panel_key = idx($panel, 'panelKey'); if (!strlen($panel_key)) { continue; } if (isset($seen_panels[$panel_key])) { continue; } $seen_panels[$panel_key] = true; $column_key = idx($panel, 'columnKey'); $column = idx($columns, $column_key, $default_column); $ref = id(new PhabricatorDashboardPanelRef()) ->setPanelPHID($panel_phid) ->setPanelKey($panel_key) ->setColumnKey($column->getColumnKey()); $column->addPanelRef($ref); $refs[] = $ref; } $list = new self(); $list->columns = $columns; $list->refs = $refs; return $list; } public function getColumns() { return $this->columns; } public function getPanelRefs() { return $this->refs; } public function getPanelRef($panel_key) { foreach ($this->getPanelRefs() as $ref) { if ($ref->getPanelKey() === $panel_key) { return $ref; } } return null; } public function toDictionary() { return array_values(mpull($this->getPanelRefs(), 'toDictionary')); } - public function newPanelRef(PhabricatorDashboardPanel $panel, $column_key) { + public function newPanelRef( + PhabricatorDashboardPanel $panel, + $column_key = null) { + + if ($column_key === null) { + $column_key = head_key($this->columns); + } + $ref = id(new PhabricatorDashboardPanelRef()) ->setPanelKey($this->newPanelKey()) ->setPanelPHID($panel->getPHID()) ->setColumnKey($column_key); $this->refs[] = $ref; return $ref; } public function removePanelRef(PhabricatorDashboardPanelRef $target) { foreach ($this->refs as $key => $ref) { if ($ref->getPanelKey() !== $target->getPanelKey()) { continue; } unset($this->refs[$key]); return $ref; } return null; } public function movePanelRef( PhabricatorDashboardPanelRef $target, $column_key, PhabricatorDashboardPanelRef $after = null) { $target->setColumnKey($column_key); $results = array(); if (!$after) { $results[] = $target; } foreach ($this->refs as $ref) { if ($ref->getPanelKey() === $target->getPanelKey()) { continue; } $results[] = $ref; if ($after) { if ($ref->getPanelKey() === $after->getPanelKey()) { $results[] = $target; } } } $this->refs = $results; $column_map = mgroup($results, 'getColumnKey'); foreach ($this->columns as $column_key => $column) { $column->setPanelRefs(idx($column_map, $column_key, array())); } return $ref; } private function newPanelKey() { return Filesystem::readRandomCharacters(8); } } diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php index 6cf9d45a8c..a8bf58e8ab 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php +++ b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php @@ -1,177 +1,176 @@ setName('') ->setAuthorPHID($actor->getPHID()) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy($actor->getPHID()); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'properties' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'sort255', 'panelType' => 'text64', 'authorPHID' => 'phid', 'isArchived' => 'bool', ), ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - PhabricatorDashboardPanelPHIDType::TYPECONST); + public function getPHIDType() { + return PhabricatorDashboardPanelPHIDType::TYPECONST; } public function getProperty($key, $default = null) { return idx($this->properties, $key, $default); } public function setProperty($key, $value) { $this->properties[$key] = $value; return $this; } public function getMonogram() { return 'W'.$this->getID(); } public function getURI() { return '/'.$this->getMonogram(); } 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(), $this->getPanelType()); } public function requireImplementation() { $impl = $this->getImplementation(); if (!$impl) { throw new Exception( pht( 'Attempting to use a panel in a way that requires an '. 'implementation, but the panel implementation ("%s") is unknown to '. 'Phabricator.', $this->getPanelType())); } return $impl; } public function getEditEngineFields() { return $this->requireImplementation()->getEditEngineFields($this); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorDashboardPanelTransactionEditor(); } public function getApplicationTransactionTemplate() { return new PhabricatorDashboardPanelTransaction(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $this->saveTransaction(); } /* -( PhabricatorDashboardPanelContainerInterface )------------------------ */ public function getDashboardPanelContainerPanelPHIDs() { return $this->requireImplementation()->getSubpanelPHIDs($this); } /* -( PhabricatorFulltextInterface )--------------------------------------- */ public function newFulltextEngine() { return new PhabricatorDashboardPanelFulltextEngine(); } /* -( PhabricatorFerretInterface )----------------------------------------- */ public function newFerretEngine() { return new PhabricatorDashboardPanelFerretEngine(); } } diff --git a/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelApplicationTransaction.php b/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelApplicationTransaction.php index 37262cd108..eca3ba4e56 100644 --- a/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelApplicationTransaction.php +++ b/src/applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelApplicationTransaction.php @@ -1,12 +1,39 @@ getProperty($this->getPropertyKey()); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + if ($new_value === $old_value) { + continue; + } + + if (!isset($engines[$new_value])) { + $errors[] = $this->newInvalidError( + pht( + 'Application search engine class "%s" is unknown. Query panels '. + 'must use a known search engine class.', + $new_value), + $xaction); + continue; + } + } + + return $errors; + } + }