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

Provide "builtin" high-level result orders

Summary:
Ref T7803. Currently, available high-level orders are spread across Query and SearchEngine classes and implemented separately for each application.

Lift the concept of "builtin" (high-level, user-facing, named) orders (similar to "builtin" queries in ApplicationSearch) into the root Query class, and let it drive the SearchEngine implementation. This allows you to define a new order in one place and have it automatically work across the entire stack.

This will also let Conduit expose this information in a straightforward way.

Test Plan:
  - Used ApplicationSearch in Diffusion.
  - Used all result orderings.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T7803

Differential Revision: https://secure.phabricator.com/D12379
This commit is contained in:
epriestley 2015-04-12 15:16:55 -07:00
parent 2794c69db5
commit 6e4f508beb
6 changed files with 184 additions and 81 deletions

View file

@ -153,7 +153,7 @@ final class PhabricatorOwnersListController
$callsigns = array('' => pht('(Any Repository)')); $callsigns = array('' => pht('(Any Repository)'));
$repositories = id(new PhabricatorRepositoryQuery()) $repositories = id(new PhabricatorRepositoryQuery())
->setViewer($user) ->setViewer($user)
->setOrder(PhabricatorRepositoryQuery::ORDER_CALLSIGN) ->setOrder('callsign')
->execute(); ->execute();
foreach ($repositories as $repository) { foreach ($repositories as $repository) {
$callsigns[$repository->getCallsign()] = $callsigns[$repository->getCallsign()] =

View file

@ -3,14 +3,10 @@
final class PonderQuestionQuery final class PonderQuestionQuery
extends PhabricatorCursorPagedPolicyAwareQuery { extends PhabricatorCursorPagedPolicyAwareQuery {
const ORDER_CREATED = 'order-created';
const ORDER_HOTTEST = 'order-hottest';
private $ids; private $ids;
private $phids; private $phids;
private $authorPHIDs; private $authorPHIDs;
private $answererPHIDs; private $answererPHIDs;
private $order = self::ORDER_CREATED;
private $status = 'status-any'; private $status = 'status-any';
const STATUS_ANY = 'status-any'; const STATUS_ANY = 'status-any';
@ -55,11 +51,6 @@ final class PonderQuestionQuery
return $this; return $this;
} }
public function setOrder($order) {
$this->order = $order;
return $this;
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) { private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array(); $where = array();
@ -110,17 +101,6 @@ final class PonderQuestionQuery
return $this->formatWhereClause($where); return $this->formatWhereClause($where);
} }
private function buildOrderByClause(AphrontDatabaseConnection $conn_r) {
switch ($this->order) {
case self::ORDER_HOTTEST:
return qsprintf($conn_r, 'ORDER BY q.heat DESC, q.id DESC');
case self::ORDER_CREATED:
return qsprintf($conn_r, 'ORDER BY q.id DESC');
default:
throw new Exception("Unknown order '{$this->order}'!");
}
}
protected function loadPage() { protected function loadPage() {
$question = new PonderQuestion(); $question = new PonderQuestion();
$conn_r = $question->establishConnection('r'); $conn_r = $question->establishConnection('r');
@ -131,7 +111,7 @@ final class PonderQuestionQuery
$question->getTableName(), $question->getTableName(),
$this->buildJoinsClause($conn_r), $this->buildJoinsClause($conn_r),
$this->buildWhereClause($conn_r), $this->buildWhereClause($conn_r),
$this->buildOrderByClause($conn_r), $this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r)); $this->buildLimitClause($conn_r));
return $question->loadAllFromArray($data); return $question->loadAllFromArray($data);

View file

@ -23,12 +23,6 @@ final class PhabricatorRepositoryQuery
const STATUS_ALL = 'status-all'; const STATUS_ALL = 'status-all';
private $status = self::STATUS_ALL; private $status = self::STATUS_ALL;
const ORDER_CREATED = 'order-created';
const ORDER_COMMITTED = 'order-committed';
const ORDER_CALLSIGN = 'order-callsign';
const ORDER_NAME = 'order-name';
const ORDER_SIZE = 'order-size';
const HOSTED_PHABRICATOR = 'hosted-phab'; const HOSTED_PHABRICATOR = 'hosted-phab';
const HOSTED_REMOTE = 'hosted-remote'; const HOSTED_REMOTE = 'hosted-remote';
const HOSTED_ALL = 'hosted-all'; const HOSTED_ALL = 'hosted-all';
@ -124,27 +118,25 @@ final class PhabricatorRepositoryQuery
return $this; return $this;
} }
public function setOrder($order) { public function getBuiltinOrders() {
switch ($order) { return array(
case self::ORDER_CREATED: 'committed' => array(
$this->setOrderVector(array('id')); 'vector' => array('committed', 'id'),
break; 'name' => pht('Most Recent Commit'),
case self::ORDER_COMMITTED: ),
$this->setOrderVector(array('committed', 'id')); 'name' => array(
break; 'vector' => array('name', 'id'),
case self::ORDER_CALLSIGN: 'name' => pht('Name'),
$this->setOrderVector(array('callsign')); ),
break; 'callsign' => array(
case self::ORDER_NAME: 'vector' => array('callsign'),
$this->setOrderVector(array('name', 'id')); 'name' => pht('Callsign'),
break; ),
case self::ORDER_SIZE: 'size' => array(
$this->setOrderVector(array('size', 'id')); 'vector' => array('size', 'id'),
break; 'name' => pht('Size'),
default: ),
throw new Exception(pht('Unknown order "%s".', $order)); ) + parent::getBuiltinOrders();
}
return $this;
} }
public function getIdentifierMap() { public function getIdentifierMap() {

View file

@ -27,6 +27,7 @@ final class PhabricatorRepositorySearchEngine
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new PhabricatorRepositoryQuery()) $query = id(new PhabricatorRepositoryQuery())
->setDefaultBuiltinOrder()
->needProjectPHIDs(true) ->needProjectPHIDs(true)
->needCommitCounts(true) ->needCommitCounts(true)
->needMostRecentCommits(true); ->needMostRecentCommits(true);
@ -43,11 +44,8 @@ final class PhabricatorRepositorySearchEngine
} }
$order = $saved->getParameter('order'); $order = $saved->getParameter('order');
$order = idx($this->getOrderValues(), $order);
if ($order) { if ($order) {
$query->setOrder($order); $query->setOrder($order);
} else {
$query->setOrder(head($this->getOrderValues()));
} }
$hosted = $saved->getParameter('hosted'); $hosted = $saved->getParameter('hosted');
@ -125,15 +123,12 @@ final class PhabricatorRepositorySearchEngine
$name, $name,
isset($types[$key])); isset($types[$key]));
} }
$form->appendChild($type_control);
$form $this->appendOrderFieldsToForm(
->appendChild($type_control) $form,
->appendChild( $saved_query,
id(new AphrontFormSelectControl()) new PhabricatorRepositoryQuery());
->setName('order')
->setLabel(pht('Order'))
->setValue($saved_query->getParameter('order'))
->setOptions($this->getOrderOptions()));
} }
protected function getURI($path) { protected function getURI($path) {
@ -180,26 +175,6 @@ final class PhabricatorRepositorySearchEngine
); );
} }
private function getOrderOptions() {
return array(
'committed' => pht('Most Recent Commit'),
'name' => pht('Name'),
'callsign' => pht('Callsign'),
'created' => pht('Date Created'),
'size' => pht('Commit Count'),
);
}
private function getOrderValues() {
return array(
'committed' => PhabricatorRepositoryQuery::ORDER_COMMITTED,
'name' => PhabricatorRepositoryQuery::ORDER_NAME,
'callsign' => PhabricatorRepositoryQuery::ORDER_CALLSIGN,
'created' => PhabricatorRepositoryQuery::ORDER_CREATED,
'size' => PhabricatorRepositoryQuery::ORDER_SIZE,
);
}
private function getHostedOptions() { private function getHostedOptions() {
return array( return array(
'' => pht('Hosted and Remote Repositories'), '' => pht('Hosted and Remote Repositories'),

View file

@ -9,6 +9,7 @@
* @task builtin Builtin Queries * @task builtin Builtin Queries
* @task uri Query URIs * @task uri Query URIs
* @task dates Date Filters * @task dates Date Filters
* @task order Result Ordering
* @task read Reading Utilities * @task read Reading Utilities
* @task exec Paging and Executing Queries * @task exec Paging and Executing Queries
* @task render Rendering Results * @task render Rendering Results
@ -577,6 +578,24 @@ abstract class PhabricatorApplicationSearchEngine {
} }
/* -( Result Ordering )---------------------------------------------------- */
protected function appendOrderFieldsToForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved,
PhabricatorCursorPagedPolicyAwareQuery $query) {
$orders = $query->getBuiltinOrders();
$orders = ipull($orders, 'name');
$form->appendControl(
id(new AphrontFormSelectControl())
->setLabel(pht('Order'))
->setName('order')
->setOptions($orders)
->setValue($saved->getParameter('order')));
}
/* -( Paging and Executing Queries )--------------------------------------- */ /* -( Paging and Executing Queries )--------------------------------------- */

View file

@ -17,6 +17,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
protected $applicationSearchOrders = array(); protected $applicationSearchOrders = array();
private $internalPaging; private $internalPaging;
private $orderVector; private $orderVector;
private $builtinOrder;
protected function getPagingValue($result) { protected function getPagingValue($result) {
if (!is_object($result)) { if (!is_object($result)) {
@ -427,6 +428,134 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
/** /**
* Select a result ordering.
*
* This is a high-level method which selects an ordering from a predefined
* list of builtin orders, as provided by @{method:getBuiltinOrders}. These
* options are user-facing and not exhaustive, but are generally convenient
* and meaningful.
*
* You can also use @{method:setOrderVector} to specify a low-level ordering
* across individual orderable columns. This offers greater control but is
* also more involved.
*
* @param string Key of a builtin order supported by this query.
* @return this
* @task order
*/
public function setOrder($order) {
$orders = $this->getBuiltinOrders();
if (empty($orders[$order])) {
throw new Exception(
pht(
'Query "%s" does not support a builtin order "%s". Supported orders '.
'are: %s.',
get_class($this),
$order,
implode(', ', array_keys($orders))));
}
$this->builtinOrder = $order;
$this->orderVector = null;
return $this;
}
/**
* Select the default builtin result ordering.
*
* This sets the result order to the default order among the builtin result
* orders (see @{method:getBuiltinOrders}). This is often the same as the
* query's builtin default order vector, but some objects have different
* default vectors (which are internally-facing) and builtin orders (which
* are user-facing).
*
* For example, repositories sort by ID internally (which is efficient and
* consistent), but sort by most recent commit as a default builtin (which
* better aligns with user expectations).
*
* @return this
*/
public function setDefaultBuiltinOrder() {
return $this->setOrder(head_key($this->getBuiltinOrders()));
}
/**
* Get builtin orders for this class.
*
* In application UIs, we want to be able to present users with a small
* selection of meaningful order options (like "Order by Title") rather than
* an exhaustive set of column ordering options.
*
* Meaningful user-facing orders are often really orders across multiple
* columns: for example, a "title" ordering is usually implemented as a
* "title, id" ordering under the hood.
*
* Builtin orders provide a mapping from convenient, understandable
* user-facing orders to implementations.
*
* A builtin order should provide these keys:
*
* - `vector` (`list<string>`): The actual order vector to use.
* - `name` (`string`): Human-readable order name.
*
* @return map<string, wild> Map from builtin order keys to specification.
* @task order
*/
public function getBuiltinOrders() {
$orders = array(
'newest' => array(
'vector' => array('id'),
'name' => pht('Creation (Newest First)'),
'aliases' => array('created'),
),
'oldest' => array(
'vector' => array('-id'),
'name' => pht('Creation (Oldest First)'),
),
);
$object = $this->newResultObject();
if ($object instanceof PhabricatorCustomFieldInterface) {
$list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
foreach ($list->getFields() as $field) {
$index = $field->buildOrderIndex();
if (!$index) {
continue;
}
$key = $field->getFieldKey();
$digest = $field->getFieldIndex();
$full_key = 'custom:'.$key;
$orders[$full_key] = array(
'vector' => array($full_key, 'id'),
'name' => $field->getFieldName(),
);
}
}
return $orders;
}
/**
* Set a low-level column ordering.
*
* This is a low-level method which offers granular control over column
* ordering. In most cases, applications can more easily use
* @{method:setOrder} to choose a high-level builtin order.
*
* To set an order vector, specify a list of order keys as provided by
* @{method:getOrderableColumns}.
*
* @param PhabricatorQueryOrderVector|list<string> List of order keys.
* @return this
* @task order * @task order
*/ */
public function setOrderVector($vector) { public function setOrderVector($vector) {
@ -485,11 +614,19 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
/** /**
* Get the effective order vector.
*
* @return PhabricatorQueryOrderVector Effective vector.
* @task order * @task order
*/ */
protected function getOrderVector() { protected function getOrderVector() {
if (!$this->orderVector) { if (!$this->orderVector) {
$vector = $this->getDefaultOrderVector(); if ($this->builtinOrder !== null) {
$builtin_order = idx($this->getBuiltinOrders(), $this->builtinOrder);
$vector = $builtin_order['vector'];
} else {
$vector = $this->getDefaultOrderVector();
}
$vector = PhabricatorQueryOrderVector::newFromVector($vector); $vector = PhabricatorQueryOrderVector::newFromVector($vector);
// We call setOrderVector() here to apply checks to the default vector. // We call setOrderVector() here to apply checks to the default vector.