@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+335 -13
+9
resources/sql/autopatches/20170814.search.01.qconfig.sql
··· 1 + CREATE TABLE {$NAMESPACE}_search.search_namedqueryconfig ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + engineClassName VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, 4 + scopePHID VARBINARY(64) NOT NULL, 5 + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 6 + dateCreated INT UNSIGNED NOT NULL, 7 + dateModified INT UNSIGNED NOT NULL, 8 + UNIQUE KEY `key_scope` (engineClassName, scopePHID) 9 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+9
src/__phutil_library_map__.php
··· 3189 3189 'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php', 3190 3190 'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php', 3191 3191 'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php', 3192 + 'PhabricatorNamedQueryConfig' => 'applications/search/storage/PhabricatorNamedQueryConfig.php', 3193 + 'PhabricatorNamedQueryConfigQuery' => 'applications/search/query/PhabricatorNamedQueryConfigQuery.php', 3192 3194 'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php', 3193 3195 'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php', 3194 3196 'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php', ··· 3908 3910 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', 3909 3911 'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php', 3910 3912 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', 3913 + 'PhabricatorSearchDefaultController' => 'applications/search/controller/PhabricatorSearchDefaultController.php', 3911 3914 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 3912 3915 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', 3913 3916 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php', ··· 8552 8555 'PhabricatorSearchDAO', 8553 8556 'PhabricatorPolicyInterface', 8554 8557 ), 8558 + 'PhabricatorNamedQueryConfig' => array( 8559 + 'PhabricatorSearchDAO', 8560 + 'PhabricatorPolicyInterface', 8561 + ), 8562 + 'PhabricatorNamedQueryConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 8555 8563 'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 8556 8564 'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule', 8557 8565 'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock', ··· 9448 9456 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', 9449 9457 'PhabricatorSearchDateControlField' => 'PhabricatorSearchField', 9450 9458 'PhabricatorSearchDateField' => 'PhabricatorSearchField', 9459 + 'PhabricatorSearchDefaultController' => 'PhabricatorSearchBaseController', 9451 9460 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 9452 9461 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 9453 9462 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO',
+2
src/applications/search/application/PhabricatorSearchApplication.php
··· 34 34 'hovercard/' 35 35 => 'PhabricatorSearchHovercardController', 36 36 'edit/(?P<queryKey>[^/]+)/' => 'PhabricatorSearchEditController', 37 + 'default/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/' 38 + => 'PhabricatorSearchDefaultController', 37 39 'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/' 38 40 => 'PhabricatorSearchDeleteController', 39 41 'order/(?P<engine>[^/]+)/' => 'PhabricatorSearchOrderController',
+50 -12
src/applications/search/controller/PhabricatorApplicationSearchController.php
··· 127 127 if (!$found_query_data) { 128 128 // Otherwise, there's no query data so just run the user's default 129 129 // query for this application. 130 - $query_key = head_key($engine->loadEnabledNamedQueries()); 130 + $query_key = $engine->getDefaultQueryKey(); 131 131 } 132 132 } 133 133 ··· 400 400 'orderURI' => '/search/order/'.get_class($engine).'/', 401 401 )); 402 402 403 + $default_key = $engine->getDefaultQueryKey(); 404 + 403 405 foreach ($named_queries as $named_query) { 404 406 $class = get_class($engine); 405 407 $key = $named_query->getQueryKey(); ··· 410 412 411 413 if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) { 412 414 $icon = 'fa-plus'; 415 + $disable_name = pht('Enable'); 413 416 } else { 414 417 $icon = 'fa-times'; 418 + if ($named_query->getIsBuiltin()) { 419 + $disable_name = pht('Disable'); 420 + } else { 421 + $disable_name = pht('Delete'); 422 + } 415 423 } 416 424 417 425 $item->addAction( 418 426 id(new PHUIListItemView()) 419 427 ->setIcon($icon) 420 428 ->setHref('/search/delete/'.$key.'/'.$class.'/') 429 + ->setRenderNameAsTooltip(true) 430 + ->setName($disable_name) 421 431 ->setWorkflow(true)); 422 432 433 + $default_disabled = $named_query->getIsDisabled(); 434 + $default_icon = 'fa-thumb-tack'; 435 + 436 + if ($default_key === $key) { 437 + $default_color = 'green'; 438 + } else { 439 + $default_color = null; 440 + } 441 + 442 + $item->addAction( 443 + id(new PHUIListItemView()) 444 + ->setIcon("{$default_icon} {$default_color}") 445 + ->setHref('/search/default/'.$key.'/'.$class.'/') 446 + ->setRenderNameAsTooltip(true) 447 + ->setName(pht('Make Default')) 448 + ->setWorkflow(true) 449 + ->setDisabled($default_disabled)); 450 + 423 451 if ($named_query->getIsBuiltin()) { 424 - if ($named_query->getIsDisabled()) { 425 - $item->addIcon('fa-times lightgreytext', pht('Disabled')); 426 - $item->setDisabled(true); 427 - } else { 428 - $item->addIcon('fa-lock lightgreytext', pht('Builtin')); 429 - } 452 + $edit_icon = 'fa-lock lightgreytext'; 453 + $edit_disabled = true; 454 + $edit_name = pht('Builtin'); 455 + $edit_href = null; 430 456 } else { 431 - $item->addAction( 432 - id(new PHUIListItemView()) 433 - ->setIcon('fa-pencil') 434 - ->setHref('/search/edit/'.$key.'/')); 457 + $edit_icon = 'fa-pencil'; 458 + $edit_disabled = false; 459 + $edit_name = pht('Edit'); 460 + $edit_href = '/search/edit/'.$key.'/'; 461 + } 462 + 463 + $item->addAction( 464 + id(new PHUIListItemView()) 465 + ->setIcon($edit_icon) 466 + ->setHref($edit_href) 467 + ->setRenderNameAsTooltip(true) 468 + ->setName($edit_name) 469 + ->setDisabled($edit_disabled)); 470 + 471 + if ($named_query->getIsDisabled()) { 472 + $item->setDisabled(true); 435 473 } 436 474 437 475 $item->setGrippable(true); ··· 610 648 $engine_class = get_class($engine); 611 649 $query_key = $this->getQueryKey(); 612 650 if (!$query_key) { 613 - $query_key = head_key($engine->loadEnabledNamedQueries()); 651 + $query_key = $engine->getDefaultQueryKey(); 614 652 } 615 653 616 654 $can_use = $engine->canUseInPanelContext();
+81
src/applications/search/controller/PhabricatorSearchDefaultController.php
··· 1 + <?php 2 + 3 + final class PhabricatorSearchDefaultController 4 + extends PhabricatorSearchBaseController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + $engine_class = $request->getURIData('engine'); 9 + 10 + $base_class = 'PhabricatorApplicationSearchEngine'; 11 + if (!is_subclass_of($engine_class, $base_class)) { 12 + return new Aphront400Response(); 13 + } 14 + 15 + $engine = newv($engine_class, array()); 16 + $engine->setViewer($viewer); 17 + 18 + $key = $request->getURIData('queryKey'); 19 + 20 + $named_query = id(new PhabricatorNamedQueryQuery()) 21 + ->setViewer($viewer) 22 + ->withEngineClassNames(array($engine_class)) 23 + ->withQueryKeys(array($key)) 24 + ->withUserPHIDs(array($viewer->getPHID())) 25 + ->executeOne(); 26 + 27 + if (!$named_query && $engine->isBuiltinQuery($key)) { 28 + $named_query = $engine->getBuiltinQuery($key); 29 + } 30 + 31 + if (!$named_query) { 32 + return new Aphront404Response(); 33 + } 34 + 35 + $return_uri = $engine->getQueryManagementURI(); 36 + 37 + $builtin = null; 38 + if ($engine->isBuiltinQuery($key)) { 39 + $builtin = $engine->getBuiltinQuery($key); 40 + } 41 + 42 + if ($request->isFormPost()) { 43 + $config = id(new PhabricatorNamedQueryConfigQuery()) 44 + ->setViewer($viewer) 45 + ->withEngineClassNames(array($engine_class)) 46 + ->withScopePHIDs(array($viewer->getPHID())) 47 + ->executeOne(); 48 + if (!$config) { 49 + $config = PhabricatorNamedQueryConfig::initializeNewQueryConfig() 50 + ->setEngineClassName($engine_class) 51 + ->setScopePHID($viewer->getPHID()); 52 + } 53 + 54 + $config->setConfigProperty( 55 + PhabricatorNamedQueryConfig::PROPERTY_PINNED, 56 + $key); 57 + 58 + $config->save(); 59 + 60 + return id(new AphrontRedirectResponse())->setURI($return_uri); 61 + } 62 + 63 + if ($named_query->getIsBuiltin()) { 64 + $query_name = $builtin->getQueryName(); 65 + } else { 66 + $query_name = $named_query->getQueryName(); 67 + } 68 + 69 + $title = pht('Set Default Query'); 70 + $body = pht( 71 + 'This query will become your default query in the current application.'); 72 + $button = pht('Set Default Query'); 73 + 74 + return $this->newDialog() 75 + ->setTitle($title) 76 + ->appendChild($body) 77 + ->addCancelButton($return_uri) 78 + ->addSubmitButton($button); 79 + } 80 + 81 + }
-1
src/applications/search/controller/PhabricatorSearchEditController.php
··· 10 10 ->setViewer($viewer) 11 11 ->withQueryKeys(array($request->getURIData('queryKey'))) 12 12 ->executeOne(); 13 - 14 13 if (!$saved_query) { 15 14 return new Aphront404Response(); 16 15 }
+28
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 511 511 return $named_queries; 512 512 } 513 513 514 + public function getDefaultQueryKey() { 515 + $viewer = $this->requireViewer(); 516 + 517 + $configs = id(new PhabricatorNamedQueryConfigQuery()) 518 + ->setViewer($viewer) 519 + ->withEngineClassNames(array(get_class($this))) 520 + ->withScopePHIDs( 521 + array( 522 + $viewer->getPHID(), 523 + PhabricatorNamedQueryConfig::SCOPE_GLOBAL, 524 + )) 525 + ->execute(); 526 + $configs = msortv($configs, 'getStrengthSortVector'); 527 + 528 + $key_pinned = PhabricatorNamedQueryConfig::PROPERTY_PINNED; 529 + $map = $this->loadEnabledNamedQueries(); 530 + foreach ($configs as $config) { 531 + $pinned = $config->getConfigProperty($key_pinned); 532 + if (!isset($map[$pinned])) { 533 + continue; 534 + } 535 + 536 + return $pinned; 537 + } 538 + 539 + return head_key($map); 540 + } 541 + 514 542 protected function setQueryProjects( 515 543 PhabricatorCursorPagedPolicyAwareQuery $query, 516 544 PhabricatorSavedQuery $saved) {
+64
src/applications/search/query/PhabricatorNamedQueryConfigQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorNamedQueryConfigQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $engineClassNames; 8 + private $scopePHIDs; 9 + 10 + public function withIDs(array $ids) { 11 + $this->ids = $ids; 12 + return $this; 13 + } 14 + 15 + public function withScopePHIDs(array $scope_phids) { 16 + $this->scopePHIDs = $scope_phids; 17 + return $this; 18 + } 19 + 20 + public function withEngineClassNames(array $engine_class_names) { 21 + $this->engineClassNames = $engine_class_names; 22 + return $this; 23 + } 24 + 25 + public function newResultObject() { 26 + return new PhabricatorNamedQueryConfig(); 27 + } 28 + 29 + protected function loadPage() { 30 + return $this->loadStandardPage($this->newResultObject()); 31 + } 32 + 33 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 34 + $where = parent::buildWhereClauseParts($conn); 35 + 36 + if ($this->ids !== null) { 37 + $where[] = qsprintf( 38 + $conn, 39 + 'id IN (%Ld)', 40 + $this->ids); 41 + } 42 + 43 + if ($this->engineClassNames !== null) { 44 + $where[] = qsprintf( 45 + $conn, 46 + 'engineClassName IN (%Ls)', 47 + $this->engineClassNames); 48 + } 49 + 50 + if ($this->scopePHIDs !== null) { 51 + $where[] = qsprintf( 52 + $conn, 53 + 'scopePHID IN (%Ls)', 54 + $this->scopePHIDs); 55 + } 56 + 57 + return $where; 58 + } 59 + 60 + public function getQueryApplicationClass() { 61 + return 'PhabricatorSearchApplication'; 62 + } 63 + 64 + }
+92
src/applications/search/storage/PhabricatorNamedQueryConfig.php
··· 1 + <?php 2 + 3 + final class PhabricatorNamedQueryConfig 4 + extends PhabricatorSearchDAO 5 + implements PhabricatorPolicyInterface { 6 + 7 + protected $engineClassName; 8 + protected $scopePHID; 9 + protected $properties = array(); 10 + 11 + const SCOPE_GLOBAL = 'scope.global'; 12 + 13 + const PROPERTY_PINNED = 'pinned'; 14 + 15 + protected function getConfiguration() { 16 + return array( 17 + self::CONFIG_SERIALIZATION => array( 18 + 'properties' => self::SERIALIZATION_JSON, 19 + ), 20 + self::CONFIG_COLUMN_SCHEMA => array( 21 + 'engineClassName' => 'text128', 22 + ), 23 + self::CONFIG_KEY_SCHEMA => array( 24 + 'key_scope' => array( 25 + 'columns' => array('engineClassName', 'scopePHID'), 26 + 'unique' => true, 27 + ), 28 + ), 29 + ) + parent::getConfiguration(); 30 + } 31 + 32 + public static function initializeNewQueryConfig() { 33 + return new self(); 34 + } 35 + 36 + public function isGlobal() { 37 + return ($this->getScopePHID() == self::SCOPE_GLOBAL); 38 + } 39 + 40 + public function getConfigProperty($key, $default = null) { 41 + return idx($this->properties, $key, $default); 42 + } 43 + 44 + public function setConfigProperty($key, $value) { 45 + $this->properties[$key] = $value; 46 + return $this; 47 + } 48 + 49 + public function getStrengthSortVector() { 50 + // Apply personal preferences before global preferences. 51 + if (!$this->isGlobal()) { 52 + $phase = 0; 53 + } else { 54 + $phase = 1; 55 + } 56 + 57 + return id(new PhutilSortVector()) 58 + ->addInt($phase) 59 + ->addInt($this->getID()); 60 + } 61 + 62 + 63 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 64 + 65 + 66 + public function getCapabilities() { 67 + return array( 68 + PhabricatorPolicyCapability::CAN_VIEW, 69 + ); 70 + } 71 + 72 + public function getPolicy($capability) { 73 + return PhabricatorPolicies::POLICY_NOONE; 74 + } 75 + 76 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 77 + if ($this->isGlobal()) { 78 + return true; 79 + } 80 + 81 + if ($viewer->getPHID() == $this->getScopePHID()) { 82 + return true; 83 + } 84 + 85 + return false; 86 + } 87 + 88 + public function describeAutomaticCapability($capability) { 89 + return null; 90 + } 91 + 92 + }