1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

Make the default ApplicationSearch query explicit, not just the first item in the list

Summary:
Ref T12956. Currently, when you visit `/maniphest/` (or any other ApplicationSearch application) we execute the first query in the list by default.

In T12956, I plan to make changes so that personal queries are always first, then global/builtin queries. Without changing the "default query" rule, this will make it harder to have, for example, some custom queries in Differential but still run a global query like "Active" by default. To make this work, you'd have to save a personal copy of the "Active" query, then put it at the top.

This feels a bit cumbersome and this rule is kind of implicit and a little weird anyway. To make this work a little better as we make changes here, add an explicit pinning action, like the one we have in Project ProfileMenus.

You can now explicitly choose a query to make default.

Test Plan:
  - Browsed without pinning anything, saw normal behavior.
  - Pinned queries, viewed `/maniphest/`, saw a non-initial query selected by default.
  - Pinned a query, deleted it, nothing exploded.

{F5098484}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12956

Differential Revision: https://secure.phabricator.com/D18422
This commit is contained in:
epriestley 2017-08-14 09:42:39 -07:00
parent ec88917dd7
commit 58b889c5b0
9 changed files with 336 additions and 14 deletions

View file

@ -0,0 +1,9 @@
CREATE TABLE {$NAMESPACE}_search.search_namedqueryconfig (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
engineClassName VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT},
scopePHID VARBINARY(64) NOT NULL,
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_scope` (engineClassName, scopePHID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -3189,6 +3189,8 @@ phutil_register_library_map(array(
'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php',
'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php',
'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php',
'PhabricatorNamedQueryConfig' => 'applications/search/storage/PhabricatorNamedQueryConfig.php',
'PhabricatorNamedQueryConfigQuery' => 'applications/search/query/PhabricatorNamedQueryConfigQuery.php',
'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php',
'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php',
'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php',
@ -3908,6 +3910,7 @@ phutil_register_library_map(array(
'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php',
'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php',
'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php',
'PhabricatorSearchDefaultController' => 'applications/search/controller/PhabricatorSearchDefaultController.php',
'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php',
'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php',
'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php',
@ -8552,6 +8555,11 @@ phutil_register_library_map(array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorNamedQueryConfig' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorNamedQueryConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock',
@ -9448,6 +9456,7 @@ phutil_register_library_map(array(
'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField',
'PhabricatorSearchDateControlField' => 'PhabricatorSearchField',
'PhabricatorSearchDateField' => 'PhabricatorSearchField',
'PhabricatorSearchDefaultController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO',

View file

@ -34,6 +34,8 @@ final class PhabricatorSearchApplication extends PhabricatorApplication {
'hovercard/'
=> 'PhabricatorSearchHovercardController',
'edit/(?P<queryKey>[^/]+)/' => 'PhabricatorSearchEditController',
'default/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'
=> 'PhabricatorSearchDefaultController',
'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/'
=> 'PhabricatorSearchDeleteController',
'order/(?P<engine>[^/]+)/' => 'PhabricatorSearchOrderController',

View file

@ -127,7 +127,7 @@ final class PhabricatorApplicationSearchController
if (!$found_query_data) {
// Otherwise, there's no query data so just run the user's default
// query for this application.
$query_key = head_key($engine->loadEnabledNamedQueries());
$query_key = $engine->getDefaultQueryKey();
}
}
@ -400,6 +400,8 @@ final class PhabricatorApplicationSearchController
'orderURI' => '/search/order/'.get_class($engine).'/',
));
$default_key = $engine->getDefaultQueryKey();
foreach ($named_queries as $named_query) {
$class = get_class($engine);
$key = $named_query->getQueryKey();
@ -410,28 +412,64 @@ final class PhabricatorApplicationSearchController
if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) {
$icon = 'fa-plus';
$disable_name = pht('Enable');
} else {
$icon = 'fa-times';
if ($named_query->getIsBuiltin()) {
$disable_name = pht('Disable');
} else {
$disable_name = pht('Delete');
}
}
$item->addAction(
id(new PHUIListItemView())
->setIcon($icon)
->setHref('/search/delete/'.$key.'/'.$class.'/')
->setRenderNameAsTooltip(true)
->setName($disable_name)
->setWorkflow(true));
if ($named_query->getIsBuiltin()) {
if ($named_query->getIsDisabled()) {
$item->addIcon('fa-times lightgreytext', pht('Disabled'));
$item->setDisabled(true);
} else {
$item->addIcon('fa-lock lightgreytext', pht('Builtin'));
}
$default_disabled = $named_query->getIsDisabled();
$default_icon = 'fa-thumb-tack';
if ($default_key === $key) {
$default_color = 'green';
} else {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')
->setHref('/search/edit/'.$key.'/'));
$default_color = null;
}
$item->addAction(
id(new PHUIListItemView())
->setIcon("{$default_icon} {$default_color}")
->setHref('/search/default/'.$key.'/'.$class.'/')
->setRenderNameAsTooltip(true)
->setName(pht('Make Default'))
->setWorkflow(true)
->setDisabled($default_disabled));
if ($named_query->getIsBuiltin()) {
$edit_icon = 'fa-lock lightgreytext';
$edit_disabled = true;
$edit_name = pht('Builtin');
$edit_href = null;
} else {
$edit_icon = 'fa-pencil';
$edit_disabled = false;
$edit_name = pht('Edit');
$edit_href = '/search/edit/'.$key.'/';
}
$item->addAction(
id(new PHUIListItemView())
->setIcon($edit_icon)
->setHref($edit_href)
->setRenderNameAsTooltip(true)
->setName($edit_name)
->setDisabled($edit_disabled));
if ($named_query->getIsDisabled()) {
$item->setDisabled(true);
}
$item->setGrippable(true);
@ -610,7 +648,7 @@ final class PhabricatorApplicationSearchController
$engine_class = get_class($engine);
$query_key = $this->getQueryKey();
if (!$query_key) {
$query_key = head_key($engine->loadEnabledNamedQueries());
$query_key = $engine->getDefaultQueryKey();
}
$can_use = $engine->canUseInPanelContext();

View file

@ -0,0 +1,81 @@
<?php
final class PhabricatorSearchDefaultController
extends PhabricatorSearchBaseController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$engine_class = $request->getURIData('engine');
$base_class = 'PhabricatorApplicationSearchEngine';
if (!is_subclass_of($engine_class, $base_class)) {
return new Aphront400Response();
}
$engine = newv($engine_class, array());
$engine->setViewer($viewer);
$key = $request->getURIData('queryKey');
$named_query = id(new PhabricatorNamedQueryQuery())
->setViewer($viewer)
->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) {
return new Aphront404Response();
}
$return_uri = $engine->getQueryManagementURI();
$builtin = null;
if ($engine->isBuiltinQuery($key)) {
$builtin = $engine->getBuiltinQuery($key);
}
if ($request->isFormPost()) {
$config = id(new PhabricatorNamedQueryConfigQuery())
->setViewer($viewer)
->withEngineClassNames(array($engine_class))
->withScopePHIDs(array($viewer->getPHID()))
->executeOne();
if (!$config) {
$config = PhabricatorNamedQueryConfig::initializeNewQueryConfig()
->setEngineClassName($engine_class)
->setScopePHID($viewer->getPHID());
}
$config->setConfigProperty(
PhabricatorNamedQueryConfig::PROPERTY_PINNED,
$key);
$config->save();
return id(new AphrontRedirectResponse())->setURI($return_uri);
}
if ($named_query->getIsBuiltin()) {
$query_name = $builtin->getQueryName();
} else {
$query_name = $named_query->getQueryName();
}
$title = pht('Set Default Query');
$body = pht(
'This query will become your default query in the current application.');
$button = pht('Set Default Query');
return $this->newDialog()
->setTitle($title)
->appendChild($body)
->addCancelButton($return_uri)
->addSubmitButton($button);
}
}

View file

@ -10,7 +10,6 @@ final class PhabricatorSearchEditController
->setViewer($viewer)
->withQueryKeys(array($request->getURIData('queryKey')))
->executeOne();
if (!$saved_query) {
return new Aphront404Response();
}

View file

@ -511,6 +511,34 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
return $named_queries;
}
public function getDefaultQueryKey() {
$viewer = $this->requireViewer();
$configs = id(new PhabricatorNamedQueryConfigQuery())
->setViewer($viewer)
->withEngineClassNames(array(get_class($this)))
->withScopePHIDs(
array(
$viewer->getPHID(),
PhabricatorNamedQueryConfig::SCOPE_GLOBAL,
))
->execute();
$configs = msortv($configs, 'getStrengthSortVector');
$key_pinned = PhabricatorNamedQueryConfig::PROPERTY_PINNED;
$map = $this->loadEnabledNamedQueries();
foreach ($configs as $config) {
$pinned = $config->getConfigProperty($key_pinned);
if (!isset($map[$pinned])) {
continue;
}
return $pinned;
}
return head_key($map);
}
protected function setQueryProjects(
PhabricatorCursorPagedPolicyAwareQuery $query,
PhabricatorSavedQuery $saved) {

View file

@ -0,0 +1,64 @@
<?php
final class PhabricatorNamedQueryConfigQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $engineClassNames;
private $scopePHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withScopePHIDs(array $scope_phids) {
$this->scopePHIDs = $scope_phids;
return $this;
}
public function withEngineClassNames(array $engine_class_names) {
$this->engineClassNames = $engine_class_names;
return $this;
}
public function newResultObject() {
return new PhabricatorNamedQueryConfig();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->engineClassNames !== null) {
$where[] = qsprintf(
$conn,
'engineClassName IN (%Ls)',
$this->engineClassNames);
}
if ($this->scopePHIDs !== null) {
$where[] = qsprintf(
$conn,
'scopePHID IN (%Ls)',
$this->scopePHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorSearchApplication';
}
}

View file

@ -0,0 +1,92 @@
<?php
final class PhabricatorNamedQueryConfig
extends PhabricatorSearchDAO
implements PhabricatorPolicyInterface {
protected $engineClassName;
protected $scopePHID;
protected $properties = array();
const SCOPE_GLOBAL = 'scope.global';
const PROPERTY_PINNED = 'pinned';
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'engineClassName' => 'text128',
),
self::CONFIG_KEY_SCHEMA => array(
'key_scope' => array(
'columns' => array('engineClassName', 'scopePHID'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public static function initializeNewQueryConfig() {
return new self();
}
public function isGlobal() {
return ($this->getScopePHID() == self::SCOPE_GLOBAL);
}
public function getConfigProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setConfigProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getStrengthSortVector() {
// Apply personal preferences before global preferences.
if (!$this->isGlobal()) {
$phase = 0;
} else {
$phase = 1;
}
return id(new PhutilSortVector())
->addInt($phase)
->addInt($this->getID());
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_NOONE;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->isGlobal()) {
return true;
}
if ($viewer->getPHID() == $this->getScopePHID()) {
return true;
}
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}