mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 14:52:41 +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:
parent
ec88917dd7
commit
58b889c5b0
9 changed files with 336 additions and 14 deletions
9
resources/sql/autopatches/20170814.search.01.qconfig.sql
Normal file
9
resources/sql/autopatches/20170814.search.01.qconfig.sql
Normal 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};
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,6 @@ final class PhabricatorSearchEditController
|
|||
->setViewer($viewer)
|
||||
->withQueryKeys(array($request->getURIData('queryKey')))
|
||||
->executeOne();
|
||||
|
||||
if (!$saved_query) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue