1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-04-07 01:48:30 +02:00

Separate saved queries in applications into "personal" and "global" queries

Summary:
Ref T12956. UI changes:

  - Administrators get a new `[X] Save as global query` option when saving a query.
  - "Edit Queries..." is split into "Personal" and "Global" sections. For administrators, each section can be edited. For non-admins, only the top section can be edited, but any query can be pinned.

A couple notes:

  - This doesn't support "pin for everyone by default". New users just get the first query from the bottom set. That seems reasonable for now.
  - Reordering is currently a little buggy (it works if you've reordered before, but not if you're reordering for the first time), but I need to migrate before I can fix / test that properly. So that'll get cleaned up in the next change or two.

Test Plan:
  - As an admin and non-admin, viewed, edited, disabled, saved-as-personal and saved-as-global various queries.

{F5098581}

{F5098582}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12956

Differential Revision: https://secure.phabricator.com/D18426
This commit is contained in:
epriestley 2017-08-14 12:19:06 -07:00
parent 58b889c5b0
commit 47da632a22
7 changed files with 279 additions and 110 deletions

View file

@ -33,11 +33,18 @@ final class PhabricatorSearchApplication extends PhabricatorApplication {
'index/(?P<phid>[^/]+)/' => 'PhabricatorSearchIndexController', 'index/(?P<phid>[^/]+)/' => 'PhabricatorSearchIndexController',
'hovercard/' 'hovercard/'
=> 'PhabricatorSearchHovercardController', => 'PhabricatorSearchHovercardController',
'edit/(?P<queryKey>[^/]+)/' => 'PhabricatorSearchEditController', 'edit/' => array(
'key/(?P<queryKey>[^/]+)/' => 'PhabricatorSearchEditController',
'id/(?P<id>[^/]+)/' => 'PhabricatorSearchEditController',
),
'default/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/' 'default/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'
=> 'PhabricatorSearchDefaultController', => 'PhabricatorSearchDefaultController',
'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/' 'delete/' => array(
'key/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'
=> 'PhabricatorSearchDeleteController', => 'PhabricatorSearchDeleteController',
'id/(?P<id>[^/]+)/'
=> 'PhabricatorSearchDeleteController',
),
'order/(?P<engine>[^/]+)/' => 'PhabricatorSearchOrderController', 'order/(?P<engine>[^/]+)/' => 'PhabricatorSearchOrderController',
'rel/(?P<relationshipKey>[^/]+)/(?P<sourcePHID>[^/]+)/' 'rel/(?P<relationshipKey>[^/]+)/(?P<sourcePHID>[^/]+)/'
=> 'PhabricatorSearchRelationshipController', => 'PhabricatorSearchRelationshipController',

View file

@ -174,7 +174,7 @@ final class PhabricatorApplicationSearchController
if ($run_query && !$named_query && $user->isLoggedIn()) { if ($run_query && !$named_query && $user->isLoggedIn()) {
$save_button = id(new PHUIButtonView()) $save_button = id(new PHUIButtonView())
->setTag('a') ->setTag('a')
->setHref('/search/edit/'.$saved_query->getQueryKey().'/') ->setHref('/search/edit/key/'.$saved_query->getQueryKey().'/')
->setText(pht('Save Query')) ->setText(pht('Save Query'))
->setIcon('fa-floppy-o'); ->setIcon('fa-floppy-o');
$submit->addButton($save_button); $submit->addButton($save_button);
@ -377,7 +377,7 @@ final class PhabricatorApplicationSearchController
private function processEditRequest() { private function processEditRequest() {
$parent = $this->getDelegatingController(); $parent = $this->getDelegatingController();
$request = $this->getRequest(); $request = $this->getRequest();
$user = $request->getUser(); $viewer = $request->getUser();
$engine = $this->getSearchEngine(); $engine = $this->getSearchEngine();
$nav = $this->getNavigation(); $nav = $this->getNavigation();
@ -387,10 +387,79 @@ final class PhabricatorApplicationSearchController
$named_queries = $engine->loadAllNamedQueries(); $named_queries = $engine->loadAllNamedQueries();
$list_id = celerity_generate_unique_node_id(); $can_global = $viewer->getIsAdmin();
$list = new PHUIObjectItemListView(); $groups = array(
$list->setUser($user); 'personal' => array(
'name' => pht('Personal Saved Queries'),
'items' => array(),
'edit' => true,
),
'global' => array(
'name' => pht('Global Saved Queries'),
'items' => array(),
'edit' => $can_global,
),
);
foreach ($named_queries as $named_query) {
if ($named_query->isGlobal()) {
$group = 'global';
} else {
$group = 'personal';
}
$groups[$group]['items'][] = $named_query;
}
$default_key = $engine->getDefaultQueryKey();
$lists = array();
foreach ($groups as $group) {
$lists[] = $this->newQueryListView(
$group['name'],
$group['items'],
$default_key,
$group['edit']);
}
$crumbs = $parent
->buildApplicationCrumbs()
->addTextCrumb(pht('Saved Queries'), $engine->getQueryManagementURI())
->setBorder(true);
$nav->selectFilter('query/edit');
$header = id(new PHUIHeaderView())
->setHeader(pht('Saved Queries'))
->setProfileHeader(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($lists);
return $this->newPage()
->setApplicationMenu($this->buildApplicationMenu())
->setTitle(pht('Saved Queries'))
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($view);
}
private function newQueryListView(
$list_name,
array $named_queries,
$default_key,
$can_edit) {
$engine = $this->getSearchEngine();
$viewer = $this->getViewer();
$list = id(new PHUIObjectItemListView())
->setViewer($viewer);
if ($can_edit) {
$list_id = celerity_generate_unique_node_id();
$list->setID($list_id); $list->setID($list_id);
Javelin::initBehavior( Javelin::initBehavior(
@ -399,8 +468,7 @@ final class PhabricatorApplicationSearchController
'listID' => $list_id, 'listID' => $list_id,
'orderURI' => '/search/order/'.get_class($engine).'/', 'orderURI' => '/search/order/'.get_class($engine).'/',
)); ));
}
$default_key = $engine->getDefaultQueryKey();
foreach ($named_queries as $named_query) { foreach ($named_queries as $named_query) {
$class = get_class($engine); $class = get_class($engine);
@ -410,6 +478,17 @@ final class PhabricatorApplicationSearchController
->setHeader($named_query->getQueryName()) ->setHeader($named_query->getQueryName())
->setHref($engine->getQueryResultsPageURI($key)); ->setHref($engine->getQueryResultsPageURI($key));
if ($named_query->getIsDisabled()) {
if ($can_edit) {
$item->setDisabled(true);
} else {
// If an item is disabled and you don't have permission to edit it,
// just skip it.
continue;
}
}
if ($can_edit) {
if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) { if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) {
$icon = 'fa-plus'; $icon = 'fa-plus';
$disable_name = pht('Enable'); $disable_name = pht('Enable');
@ -422,13 +501,20 @@ final class PhabricatorApplicationSearchController
} }
} }
if ($named_query->getID()) {
$disable_href = '/search/delete/id/'.$named_query->getID().'/';
} else {
$disable_href = '/search/delete/key/'.$key.'/'.$class.'/';
}
$item->addAction( $item->addAction(
id(new PHUIListItemView()) id(new PHUIListItemView())
->setIcon($icon) ->setIcon($icon)
->setHref('/search/delete/'.$key.'/'.$class.'/') ->setHref($disable_href)
->setRenderNameAsTooltip(true) ->setRenderNameAsTooltip(true)
->setName($disable_name) ->setName($disable_name)
->setWorkflow(true)); ->setWorkflow(true));
}
$default_disabled = $named_query->getIsDisabled(); $default_disabled = $named_query->getIsDisabled();
$default_icon = 'fa-thumb-tack'; $default_icon = 'fa-thumb-tack';
@ -448,6 +534,7 @@ final class PhabricatorApplicationSearchController
->setWorkflow(true) ->setWorkflow(true)
->setDisabled($default_disabled)); ->setDisabled($default_disabled));
if ($can_edit) {
if ($named_query->getIsBuiltin()) { if ($named_query->getIsBuiltin()) {
$edit_icon = 'fa-lock lightgreytext'; $edit_icon = 'fa-lock lightgreytext';
$edit_disabled = true; $edit_disabled = true;
@ -457,7 +544,7 @@ final class PhabricatorApplicationSearchController
$edit_icon = 'fa-pencil'; $edit_icon = 'fa-pencil';
$edit_disabled = false; $edit_disabled = false;
$edit_name = pht('Edit'); $edit_name = pht('Edit');
$edit_href = '/search/edit/'.$key.'/'; $edit_href = '/search/edit/id/'.$named_query->getID().'/';
} }
$item->addAction( $item->addAction(
@ -467,12 +554,9 @@ final class PhabricatorApplicationSearchController
->setRenderNameAsTooltip(true) ->setRenderNameAsTooltip(true)
->setName($edit_name) ->setName($edit_name)
->setDisabled($edit_disabled)); ->setDisabled($edit_disabled));
if ($named_query->getIsDisabled()) {
$item->setDisabled(true);
} }
$item->setGrippable(true); $item->setGrippable($can_edit);
$item->addSigil('named-query'); $item->addSigil('named-query');
$item->setMetadata( $item->setMetadata(
array( array(
@ -484,31 +568,10 @@ final class PhabricatorApplicationSearchController
$list->setNoDataString(pht('No saved queries.')); $list->setNoDataString(pht('No saved queries.'));
$crumbs = $parent return id(new PHUIObjectBoxView())
->buildApplicationCrumbs() ->setHeaderText($list_name)
->addTextCrumb(pht('Saved Queries'), $engine->getQueryManagementURI()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setBorder(true); ->setObjectList($list);
$nav->selectFilter('query/edit');
$header = id(new PHUIHeaderView())
->setHeader(pht('Saved Queries'))
->setProfileHeader(true);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->setObjectList($list)
->addClass('application-search-results');
$nav->addClass('application-search-view');
require_celerity_resource('application-search-view-css');
return $this->newPage()
->setApplicationMenu($this->buildApplicationMenu())
->setTitle(pht('Saved Queries'))
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($box);
} }
public function buildApplicationMenu() { public function buildApplicationMenu() {

View file

@ -21,7 +21,11 @@ final class PhabricatorSearchDefaultController
->setViewer($viewer) ->setViewer($viewer)
->withEngineClassNames(array($engine_class)) ->withEngineClassNames(array($engine_class))
->withQueryKeys(array($key)) ->withQueryKeys(array($key))
->withUserPHIDs(array($viewer->getPHID())) ->withUserPHIDs(
array(
$viewer->getPHID(),
PhabricatorNamedQuery::SCOPE_GLOBAL,
))
->executeOne(); ->executeOne();
if (!$named_query && $engine->isBuiltinQuery($key)) { if (!$named_query && $engine->isBuiltinQuery($key)) {

View file

@ -5,6 +5,27 @@ final class PhabricatorSearchDeleteController
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$id = $request->getURIData('id');
if ($id) {
$named_query = id(new PhabricatorNamedQueryQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$named_query) {
return new Aphront404Response();
}
$engine = newv($named_query->getEngineClassName(), array());
$engine->setViewer($viewer);
$key = $named_query->getQueryKey();
} else {
$key = $request->getURIData('queryKey'); $key = $request->getURIData('queryKey');
$engine_class = $request->getURIData('engine'); $engine_class = $request->getURIData('engine');
@ -16,19 +37,11 @@ final class PhabricatorSearchDeleteController
$engine = newv($engine_class, array()); $engine = newv($engine_class, array());
$engine->setViewer($viewer); $engine->setViewer($viewer);
$named_query = id(new PhabricatorNamedQueryQuery()) if (!$engine->isBuiltinQuery($key)) {
->setViewer($viewer) return new Aphront404Response();
->withEngineClassNames(array($engine_class))
->withQueryKeys(array($key))
->withUserPHIDs(array($viewer->getPHID()))
->executeOne();
if (!$named_query && $engine->isBuiltinQuery($key)) {
$named_query = $engine->getBuiltinQuery($key);
} }
if (!$named_query) { $named_query = $engine->getBuiltinQuery($key);
return new Aphront404Response();
} }
$builtin = null; $builtin = null;

View file

@ -6,9 +6,30 @@ final class PhabricatorSearchEditController
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$id = $request->getURIData('id');
if ($id) {
$named_query = id(new PhabricatorNamedQueryQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$named_query) {
return new Aphront404Response();
}
$query_key = $named_query->getQueryKey();
} else {
$query_key = $request->getURIData('queryKey');
$named_query = null;
}
$saved_query = id(new PhabricatorSavedQueryQuery()) $saved_query = id(new PhabricatorSavedQueryQuery())
->setViewer($viewer) ->setViewer($viewer)
->withQueryKeys(array($request->getURIData('queryKey'))) ->withQueryKeys(array($query_key))
->executeOne(); ->executeOne();
if (!$saved_query) { if (!$saved_query) {
return new Aphront404Response(); return new Aphront404Response();
@ -19,11 +40,6 @@ final class PhabricatorSearchEditController
$complete_uri = $engine->getQueryManagementURI(); $complete_uri = $engine->getQueryManagementURI();
$cancel_uri = $complete_uri; $cancel_uri = $complete_uri;
$named_query = id(new PhabricatorNamedQueryQuery())
->setViewer($viewer)
->withQueryKeys(array($saved_query->getQueryKey()))
->withUserPHIDs(array($viewer->getPHID()))
->executeOne();
if (!$named_query) { if (!$named_query) {
$named_query = id(new PhabricatorNamedQuery()) $named_query = id(new PhabricatorNamedQuery())
->setUserPHID($viewer->getPHID()) ->setUserPHID($viewer->getPHID())
@ -35,12 +51,27 @@ final class PhabricatorSearchEditController
// management interface. // management interface.
$cancel_uri = $engine->getQueryResultsPageURI( $cancel_uri = $engine->getQueryResultsPageURI(
$saved_query->getQueryKey()); $saved_query->getQueryKey());
$is_new = true;
} else {
$is_new = false;
} }
$can_global = ($viewer->getIsAdmin() && $is_new);
$v_global = false;
$e_name = true; $e_name = true;
$errors = array(); $errors = array();
if ($request->isFormPost()) { if ($request->isFormPost()) {
if ($can_global) {
$v_global = $request->getBool('global');
if ($v_global) {
$named_query->setUserPHID(PhabricatorNamedQuery::SCOPE_GLOBAL);
}
}
$named_query->setQueryName($request->getStr('name')); $named_query->setQueryName($request->getStr('name'));
if (!strlen($named_query->getQueryName())) { if (!strlen($named_query->getQueryName())) {
$e_name = pht('Required'); $e_name = pht('Required');
@ -50,6 +81,7 @@ final class PhabricatorSearchEditController
} }
if (!$errors) { if (!$errors) {
$named_query->save(); $named_query->save();
return id(new AphrontRedirectResponse())->setURI($complete_uri); return id(new AphrontRedirectResponse())->setURI($complete_uri);
} }
@ -65,6 +97,18 @@ final class PhabricatorSearchEditController
->setValue($named_query->getQueryName()) ->setValue($named_query->getQueryName())
->setError($e_name)); ->setError($e_name));
if ($can_global) {
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'global',
'1',
pht(
'Save this query as a global query, making it visible to '.
'all users.'),
$v_global));
}
$form->appendChild( $form->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->setValue(pht('Save Query')) ->setValue(pht('Save Query'))

View file

@ -474,8 +474,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
if ($this->namedQueries === null) { if ($this->namedQueries === null) {
$named_queries = id(new PhabricatorNamedQueryQuery()) $named_queries = id(new PhabricatorNamedQueryQuery())
->setViewer($viewer) ->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->withEngineClassNames(array(get_class($this))) ->withEngineClassNames(array(get_class($this)))
->withUserPHIDs(
array(
$viewer->getPHID(),
PhabricatorNamedQuery::SCOPE_GLOBAL,
))
->execute(); ->execute();
$named_queries = mpull($named_queries, null, 'getQueryKey'); $named_queries = mpull($named_queries, null, 'getQueryKey');
@ -494,7 +498,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
unset($builtin[$key]); unset($builtin[$key]);
} }
$named_queries = msort($named_queries, 'getSortKey'); $named_queries = msortv($named_queries, 'getNamedQuerySortVector');
$this->namedQueries = $named_queries; $this->namedQueries = $named_queries;
} }
@ -631,7 +635,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
$sequence = 0; $sequence = 0;
foreach ($names as $key => $name) { foreach ($names as $key => $name) {
$queries[$key] = id(new PhabricatorNamedQuery()) $queries[$key] = id(new PhabricatorNamedQuery())
->setUserPHID($this->requireViewer()->getPHID()) ->setUserPHID(PhabricatorNamedQuery::SCOPE_GLOBAL)
->setEngineClassName(get_class($this)) ->setEngineClassName(get_class($this))
->setQueryName($name) ->setQueryName($name)
->setQueryKey($key) ->setQueryKey($key)

View file

@ -12,6 +12,8 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO
protected $isDisabled = 0; protected $isDisabled = 0;
protected $sequence = 0; protected $sequence = 0;
const SCOPE_GLOBAL = 'scope.global';
protected function getConfiguration() { protected function getConfiguration() {
return array( return array(
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
@ -31,8 +33,29 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function getSortKey() { public function isGlobal() {
return sprintf('~%010d%010d', $this->sequence, $this->getID()); if ($this->getIsBuiltin()) {
return true;
}
if ($this->getUserPHID() === self::SCOPE_GLOBAL) {
return true;
}
return false;
}
public function getNamedQuerySortVector() {
if (!$this->isGlobal()) {
$phase = 0;
} else {
$phase = 1;
}
return id(new PhutilSortVector())
->addInt($phase)
->addInt($this->sequence)
->addInt($this->getID());
} }
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -41,6 +64,7 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO
public function getCapabilities() { public function getCapabilities() {
return array( return array(
PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
); );
} }
@ -49,9 +73,19 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO
} }
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($viewer->getPHID() == $this->userPHID) { if ($viewer->getPHID() == $this->getUserPHID()) {
return true; return true;
} }
if ($this->isGlobal()) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return true;
case PhabricatorPolicyCapability::CAN_EDIT:
return $viewer->getIsAdmin();
}
}
return false; return false;
} }