From bf87976d25dee933a2d155728d5fd8ced0d3884e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 Jun 2015 12:21:48 -0700 Subject: [PATCH] Support ordering in SearchField Summary: Ref T8441. Ref T7715. Automatically generate a modern "Order" control in ApplicationSearch for engines which fully support SearchField. Notably, this allows the standard "Order" control to automatically support custom field orders. We do this in Maniphest today, but in an ad-hoc way. Test Plan: Performed order-by queries in Almanac (Services), Pholio, Files, People, Projects, and Paste. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7715, T8441 Differential Revision: https://secure.phabricator.com/D13193 --- src/__phutil_library_map__.php | 2 + .../almanac/query/AlmanacServiceQuery.php | 52 +++++------ .../query/AlmanacServiceSearchEngine.php | 34 +++----- .../files/query/PhabricatorFileQuery.php | 4 + .../query/PhabricatorFileSearchEngine.php | 6 +- .../maniphest/query/ManiphestTaskQuery.php | 2 +- .../paste/query/PhabricatorPasteQuery.php | 2 +- .../query/PhabricatorPasteSearchEngine.php | 8 +- .../people/query/PhabricatorPeopleQuery.php | 73 +++++++--------- .../query/PhabricatorPeopleSearchEngine.php | 10 +-- .../pholio/query/PholioMockQuery.php | 4 + .../pholio/query/PholioMockSearchEngine.php | 14 +-- .../project/query/PhabricatorProjectQuery.php | 15 +++- .../query/PhabricatorProjectSearchEngine.php | 8 +- .../PhabricatorApplicationSearchEngine.php | 87 ++++++++++++------- .../field/PhabricatorSearchOrderField.php | 30 +++++++ ...PhabricatorCursorPagedPolicyAwareQuery.php | 32 ++++++- 17 files changed, 229 insertions(+), 154 deletions(-) create mode 100644 src/applications/search/field/PhabricatorSearchOrderField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9ebf482748..1af2166ce1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2520,6 +2520,7 @@ phutil_register_library_map(array( 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', 'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php', 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', + 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', 'PhabricatorSearchOwnersField' => 'applications/search/field/PhabricatorSearchOwnersField.php', 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', 'PhabricatorSearchProjectsField' => 'applications/search/field/PhabricatorSearchProjectsField.php', @@ -6024,6 +6025,7 @@ phutil_register_library_map(array( 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', 'PhabricatorSearchOwnersField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSearchProjectsField' => 'PhabricatorSearchTokenizerField', diff --git a/src/applications/almanac/query/AlmanacServiceQuery.php b/src/applications/almanac/query/AlmanacServiceQuery.php index 85217d1247..34879d5ca5 100644 --- a/src/applications/almanac/query/AlmanacServiceQuery.php +++ b/src/applications/almanac/query/AlmanacServiceQuery.php @@ -60,47 +60,35 @@ final class AlmanacServiceQuery } protected function loadPage() { - $table = new AlmanacService(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT service.* FROM %T service %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage(new AlmanacService()); } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->devicePHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T binding ON service.phid = binding.servicePHID', id(new AlmanacBinding())->getTableName()); } - return implode(' ', $joins); + return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.phid IN (%Ls)', $this->phids); } @@ -112,49 +100,47 @@ final class AlmanacServiceQuery } $where[] = qsprintf( - $conn_r, + $conn, 'service.nameIndex IN (%Ls)', $hashes); } if ($this->serviceClasses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.serviceClass IN (%Ls)', $this->serviceClasses); } if ($this->devicePHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'binding.devicePHID IN (%Ls)', $this->devicePHIDs); } if ($this->locked !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.isLocked = %d', (int)$this->locked); } if ($this->namePrefix !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.name LIKE %>', $this->namePrefix); } if ($this->nameSuffix !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'service.name LIKE %<', $this->nameSuffix); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function willFilterPage(array $services) { @@ -192,10 +178,14 @@ final class AlmanacServiceQuery return parent::didFilterPage($services); } + public function getPrimaryTableAlias() { + return 'service'; + } + public function getOrderableColumns() { return parent::getOrderableColumns() + array( 'name' => array( - 'table' => 'service', + 'table' => $this->getPrimaryTableAlias(), 'column' => 'name', 'type' => 'string', 'unique' => true, diff --git a/src/applications/almanac/query/AlmanacServiceSearchEngine.php b/src/applications/almanac/query/AlmanacServiceSearchEngine.php index 8610f86572..27672e8bf9 100644 --- a/src/applications/almanac/query/AlmanacServiceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacServiceSearchEngine.php @@ -11,30 +11,26 @@ final class AlmanacServiceSearchEngine return 'PhabricatorAlmanacApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $this->saveQueryOrder($saved, $request); - - return $saved; + public function newQuery() { + return new AlmanacServiceQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new AlmanacServiceQuery()); + public function newResultObject() { + // NOTE: We need to attach a service type in order to generate custom + // field definitions. + return AlmanacService::initializeNewService() + ->attachServiceType(new AlmanacCustomServiceType()); + } - $this->setQueryOrder($query, $saved); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - $this->appendOrderFieldsToForm( - $form, - $saved, - new AlmanacServiceQuery()); + protected function buildCustomSearchFields() { + return array(); } protected function getURI($path) { @@ -62,12 +58,6 @@ final class AlmanacServiceSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $services, - PhabricatorSavedQuery $query) { - return array(); - } - protected function renderResultList( array $services, PhabricatorSavedQuery $query, diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index 95a7f4fa1b..ad13908d7d 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -117,6 +117,10 @@ final class PhabricatorFileQuery return $this; } + public function newResultObject() { + return new PhabricatorFile(); + } + protected function loadPage() { $files = $this->loadStandardPage(new PhabricatorFile()); diff --git a/src/applications/files/query/PhabricatorFileSearchEngine.php b/src/applications/files/query/PhabricatorFileSearchEngine.php index e3db58b311..69eeb0e98a 100644 --- a/src/applications/files/query/PhabricatorFileSearchEngine.php +++ b/src/applications/files/query/PhabricatorFileSearchEngine.php @@ -11,8 +11,8 @@ final class PhabricatorFileSearchEngine return 'PhabricatorFilesApplication'; } - public function newResultObject() { - return new PhabricatorFile(); + public function newQuery() { + return new PhabricatorFileQuery(); } protected function buildCustomSearchFields() { @@ -46,7 +46,7 @@ final class PhabricatorFileSearchEngine } public function buildQueryFromParameters(array $map) { - $query = id(new PhabricatorFileQuery()); + $query = $this->newQuery(); if ($map['authorPHIDs']) { $query->withAuthorPHIDs($map['authorPHIDs']); diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 09472be2ab..636fea218d 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -193,7 +193,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } - protected function newResultObject() { + public function newResultObject() { return new ManiphestTask(); } diff --git a/src/applications/paste/query/PhabricatorPasteQuery.php b/src/applications/paste/query/PhabricatorPasteQuery.php index 75bc4d7084..5c31b65931 100644 --- a/src/applications/paste/query/PhabricatorPasteQuery.php +++ b/src/applications/paste/query/PhabricatorPasteQuery.php @@ -67,7 +67,7 @@ final class PhabricatorPasteQuery return $this; } - protected function newResultObject() { + public function newResultObject() { return new PhabricatorPaste(); } diff --git a/src/applications/paste/query/PhabricatorPasteSearchEngine.php b/src/applications/paste/query/PhabricatorPasteSearchEngine.php index a443472b55..b6dcfce104 100644 --- a/src/applications/paste/query/PhabricatorPasteSearchEngine.php +++ b/src/applications/paste/query/PhabricatorPasteSearchEngine.php @@ -11,13 +11,13 @@ final class PhabricatorPasteSearchEngine return 'PhabricatorPasteApplication'; } - public function newResultObject() { - return new PhabricatorPaste(); + public function newQuery() { + return id(new PhabricatorPasteQuery()) + ->needContent(true); } protected function buildQueryFromParameters(array $map) { - $query = id(new PhabricatorPasteQuery()) - ->needContent(true); + $query = $this->newQuery(); if ($map['authorPHIDs']) { $query->withAuthorPHIDs($map['authorPHIDs']); diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 09dfd51f6d..b8a0851252 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -113,19 +113,13 @@ final class PhabricatorPeopleQuery return $this; } - protected function loadPage() { - $table = new PhabricatorUser(); - $conn_r = $table->establishConnection('r'); + public function newResultObject() { + return new PhabricatorUser(); + } - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T user %Q %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinsClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + protected function loadPage() { + $table = new PhabricatorUser(); + $data = $this->loadStandardPageRows($table); if ($this->needPrimaryEmail) { $table->putInSet(new LiskDAOSet()); @@ -225,23 +219,21 @@ final class PhabricatorPeopleQuery return $users; } - protected function buildGroupClause(AphrontDatabaseConnection $conn) { + protected function shouldGroupQueryResultRows() { if ($this->nameTokens) { - return qsprintf( - $conn, - 'GROUP BY user.id'); - } else { - return $this->buildApplicationSearchGroupClause($conn); + return true; } + + return parent::shouldGroupQueryResultRows(); } - private function buildJoinsClause($conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->emails) { $email_table = new PhabricatorUserEmail(); $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T email ON email.userPHID = user.PHID', $email_table->getTableName()); } @@ -250,7 +242,7 @@ final class PhabricatorPeopleQuery foreach ($this->nameTokens as $key => $token) { $token_table = 'token_'.$key; $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T %T ON %T.userID = user.id AND %T.token LIKE %>', PhabricatorUser::NAMETOKEN_TABLE, $token_table, @@ -260,110 +252,105 @@ final class PhabricatorPeopleQuery } } - $joins[] = $this->buildApplicationSearchJoinClause($conn_r); - - $joins = implode(' ', $joins); return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->usernames !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.userName IN (%Ls)', $this->usernames); } if ($this->emails !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'email.address IN (%Ls)', $this->emails); } if ($this->realnames !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.realName IN (%Ls)', $this->realnames); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.phid IN (%Ls)', $this->phids); } if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.id IN (%Ld)', $this->ids); } if ($this->dateCreatedAfter) { $where[] = qsprintf( - $conn_r, + $conn, 'user.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( - $conn_r, + $conn, 'user.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->isAdmin !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.isAdmin = %d', (int)$this->isAdmin); } if ($this->isDisabled !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.isDisabled = %d', (int)$this->isDisabled); } if ($this->isApproved !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.isApproved = %d', (int)$this->isApproved); } if ($this->isSystemAgent !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.isSystemAgent = %d', (int)$this->isSystemAgent); } if ($this->isMailingList !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'user.isMailingList = %d', (int)$this->isMailingList); } if (strlen($this->nameLike)) { $where[] = qsprintf( - $conn_r, + $conn, 'user.username LIKE %~ OR user.realname LIKE %~', $this->nameLike, $this->nameLike); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function getPrimaryTableAlias() { diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index f141b256c6..64b9f9db62 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -11,8 +11,10 @@ final class PhabricatorPeopleSearchEngine return 'PhabricatorPeopleApplication'; } - public function newResultObject() { - return new PhabricatorUser(); + public function newQuery() { + return id(new PhabricatorPeopleQuery()) + ->needPrimaryEmail(true) + ->needProfileImage(true); } protected function buildCustomSearchFields() { @@ -77,9 +79,7 @@ final class PhabricatorPeopleSearchEngine } public function buildQueryFromParameters(array $map) { - $query = id(new PhabricatorPeopleQuery()) - ->needPrimaryEmail(true) - ->needProfileImage(true); + $query = $this->newQuery(); $viewer = $this->requireViewer(); diff --git a/src/applications/pholio/query/PholioMockQuery.php b/src/applications/pholio/query/PholioMockQuery.php index 3f0ca2c66b..5f1711def6 100644 --- a/src/applications/pholio/query/PholioMockQuery.php +++ b/src/applications/pholio/query/PholioMockQuery.php @@ -53,6 +53,10 @@ final class PholioMockQuery return $this; } + public function newResultObject() { + return new PholioMock(); + } + protected function loadPage() { $mocks = $this->loadStandardPage(new PholioMock()); diff --git a/src/applications/pholio/query/PholioMockSearchEngine.php b/src/applications/pholio/query/PholioMockSearchEngine.php index e410d6ce39..8b0f290c25 100644 --- a/src/applications/pholio/query/PholioMockSearchEngine.php +++ b/src/applications/pholio/query/PholioMockSearchEngine.php @@ -10,11 +10,14 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine { return 'PhabricatorPholioApplication'; } - public function newResultObject() { - return new PholioMock(); + public function newQuery() { + return id(new PholioMockQuery()) + ->needCoverFiles(true) + ->needImages(true) + ->needTokenCounts(true); } - public function buildCustomSearchFields() { + protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchUsersField()) ->setKey('authorPHIDs') @@ -30,10 +33,7 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine { } public function buildQueryFromParameters(array $map) { - $query = id(new PholioMockQuery()) - ->needCoverFiles(true) - ->needImages(true) - ->needTokenCounts(true); + $query = $this->newQuery(); if ($map['authorPHIDs']) { $query->withAuthorPHIDs($map['authorPHIDs']); diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 711df75c06..bbb0857315 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -95,12 +95,25 @@ final class PhabricatorProjectQuery return $this; } + public function newResultObject() { + return new PhabricatorProject(); + } + protected function getDefaultOrderVector() { return array('name'); } - public function getOrderableColumns() { + public function getBuiltinOrders() { return array( + 'name' => array( + 'vector' => array('name'), + 'name' => pht('Name'), + ), + ) + parent::getBuiltinOrders(); + } + + public function getOrderableColumns() { + return parent::getOrderableColumns() + array( 'name' => array( 'table' => $this->getPrimaryTableAlias(), 'column' => 'name', diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 50e7d17340..4a384b492a 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -11,8 +11,9 @@ final class PhabricatorProjectSearchEngine return 'PhabricatorProjectApplication'; } - public function newResultObject() { - return new PhabricatorProject(); + public function newQuery() { + return id(new PhabricatorProjectQuery()) + ->needImages(true); } protected function buildCustomSearchFields() { @@ -41,8 +42,7 @@ final class PhabricatorProjectSearchEngine public function buildQueryFromParameters(array $map) { - $query = id(new PhabricatorProjectQuery()) - ->needImages(true); + $query = $this->newQuery(); if (strlen($map['name'])) { $tokens = PhabricatorTypeaheadDatasource::tokenizeString($map['name']); diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 89e2d05c46..11e562b43f 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -27,6 +27,19 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { const CONTEXT_PANEL = 'panel'; public function newResultObject() { + // We may be able to get this automatically if newQuery() is implemented. + $query = $this->newQuery(); + if ($query) { + $object = $query->newResultObject(); + if ($object) { + return $object; + } + } + + return null; + } + + public function newQuery() { return null; } @@ -98,15 +111,15 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $fields = $this->buildSearchFields(); $viewer = $this->requireViewer(); - $parameters = array(); + $map = array(); foreach ($fields as $field) { $field->setViewer($viewer); $field->readValueFromSavedQuery($saved); $value = $field->getValueForQuery($field->getValue()); - $parameters[$field->getKey()] = $value; + $map[$field->getKey()] = $value; } - $query = $this->buildQueryFromParameters($parameters); + $query = $this->buildQueryFromParameters($map); $object = $this->newResultObject(); if (!$object) { @@ -114,25 +127,25 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } if ($object instanceof PhabricatorSubscribableInterface) { - if (!empty($parameters['subscriberPHIDs'])) { + if (!empty($map['subscriberPHIDs'])) { $query->withEdgeLogicPHIDs( PhabricatorObjectHasSubscriberEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_OR, - $parameters['subscriberPHIDs']); + $map['subscriberPHIDs']); } } if ($object instanceof PhabricatorProjectInterface) { - if (!empty($parameters['projectPHIDs'])) { + if (!empty($map['projectPHIDs'])) { $query->withEdgeLogicConstraints( PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, - $parameters['projectPHIDs']); + $map['projectPHIDs']); } } if ($object instanceof PhabricatorSpacesInterface) { - if (!empty($parameters['spacePHIDs'])) { - $query->withSpacePHIDs($parameters['spacePHIDs']); + if (!empty($map['spacePHIDs'])) { + $query->withSpacePHIDs($map['spacePHIDs']); } } @@ -140,6 +153,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $this->applyCustomFieldsToQuery($query, $saved); } + $this->setQueryOrder($query, $saved); + return $query; } @@ -185,30 +200,28 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } $object = $this->newResultObject(); - if (!$object) { - return $fields; - } + if ($object) { + if ($object instanceof PhabricatorSubscribableInterface) { + $fields[] = id(new PhabricatorSearchSubscribersField()) + ->setLabel(pht('Subscribers')) + ->setKey('subscriberPHIDs') + ->setAliases(array('subscriber', 'subscribers')); + } - if ($object instanceof PhabricatorSubscribableInterface) { - $fields[] = id(new PhabricatorSearchSubscribersField()) - ->setLabel(pht('Subscribers')) - ->setKey('subscriberPHIDs') - ->setAliases(array('subscriber', 'subscribers')); - } + if ($object instanceof PhabricatorProjectInterface) { + $fields[] = id(new PhabricatorSearchProjectsField()) + ->setKey('projectPHIDs') + ->setAliases(array('project', 'projects')) + ->setLabel(pht('Projects')); + } - if ($object instanceof PhabricatorProjectInterface) { - $fields[] = id(new PhabricatorSearchProjectsField()) - ->setKey('projectPHIDs') - ->setAliases(array('project', 'projects')) - ->setLabel(pht('Projects')); - } - - if ($object instanceof PhabricatorSpacesInterface) { - if (PhabricatorSpacesNamespaceQuery::getSpacesExist()) { - $fields[] = id(new PhabricatorSearchSpacesField()) - ->setKey('spacePHIDs') - ->setAliases(array('space', 'spaces')) - ->setLabel(pht('Spaces')); + if ($object instanceof PhabricatorSpacesInterface) { + if (PhabricatorSpacesNamespaceQuery::getSpacesExist()) { + $fields[] = id(new PhabricatorSearchSpacesField()) + ->setKey('spacePHIDs') + ->setAliases(array('space', 'spaces')) + ->setLabel(pht('Spaces')); + } } } @@ -216,6 +229,17 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $fields[] = $custom_field; } + $query = $this->newQuery(); + if ($query) { + $orders = $query->getBuiltinOrders(); + $orders = ipull($orders, 'name'); + + $fields[] = id(new PhabricatorSearchOrderField()) + ->setLabel(pht('Order')) + ->setKey('order') + ->setOptions($orders); + } + $field_map = array(); foreach ($fields as $field) { $key = $field->getKey(); @@ -890,6 +914,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $order = $saved->getParameter('order'); $builtin = $query->getBuiltinOrders(); + if (strlen($order) && isset($builtin[$order])) { $query->setOrder($order); } else { diff --git a/src/applications/search/field/PhabricatorSearchOrderField.php b/src/applications/search/field/PhabricatorSearchOrderField.php new file mode 100644 index 0000000000..98e3b2beb3 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchOrderField.php @@ -0,0 +1,30 @@ +options = $options; + return $this; + } + + public function getOptions() { + return $this->options; + } + + protected function getDefaultValue() { + return null; + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $request->getStr($key); + } + + protected function newControl() { + return id(new AphrontFormSelectControl()) + ->setOptions($this->getOptions()); + } + +} diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 18686b84ad..aace1c728c 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -200,7 +200,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery return null; } - protected function newResultObject() { + public function newResultObject() { return null; } @@ -844,6 +844,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery 'column' => 'indexValue', 'type' => $index->getIndexValueType(), 'null' => 'tail', + 'customfield' => true, + 'customfield.index.table' => $index->getTableName(), + 'customfield.index.key' => $digest, ); } } @@ -1229,6 +1232,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } + // TODO: Get rid of this. foreach ($this->applicationSearchOrders as $key => $order) { $table = $order['table']; $index = $order['index']; @@ -1247,6 +1251,32 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $index); } + $phid_column = $this->getApplicationSearchObjectPHIDColumn(); + $orderable = $this->getOrderableColumns(); + + $vector = $this->getOrderVector(); + foreach ($vector as $order) { + $spec = $orderable[$order->getOrderKey()]; + if (empty($spec['customfield'])) { + continue; + } + + $table = $spec['customfield.index.table']; + $alias = $spec['table']; + $key = $spec['customfield.index.key']; + + $joins[] = qsprintf( + $conn_r, + 'LEFT JOIN %T %T ON %T.objectPHID = %Q + AND %T.indexKey = %s', + $table, + $alias, + $alias, + $phid_column, + $alias, + $key); + } + return implode(' ', $joins); }