diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 7d5bb68e50..359cfb88b7 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -1,172 +1,306 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withCallsigns(array $callsigns) { $this->callsigns = $callsigns; return $this; } - protected function getReversePaging() { - return true; - } - public function withStatus($status) { $this->status = $status; return $this; } public function needCommitCounts($need_counts) { $this->needCommitCounts = $need_counts; return $this; } public function needMostRecentCommits($need_commits) { $this->needMostRecentCommits = $need_commits; return $this; } + public function setOrder($order) { + $this->order = $order; + return $this; + } protected function loadPage() { $table = new PhabricatorRepository(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T r %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $repositories = $table->loadAllFromArray($data); if ($this->needCommitCounts) { $sizes = ipull($data, 'size', 'id'); foreach ($repositories as $id => $repository) { $repository->attachCommitCount(nonempty($sizes[$id], 0)); } } if ($this->needMostRecentCommits) { $commit_ids = ipull($data, 'lastCommitID', 'id'); $commit_ids = array_filter($commit_ids); if ($commit_ids) { $commits = id(new DiffusionCommitQuery()) ->setViewer($this->getViewer()) ->withIDs($commit_ids) ->execute(); } else { $commits = array(); } foreach ($repositories as $id => $repository) { $commit = null; if (idx($commit_ids, $id)) { $commit = idx($commits, $commit_ids[$id]); } $repository->attachMostRecentCommit($commit); } } return $repositories; } public function willFilterPage(array $repositories) { assert_instances_of($repositories, 'PhabricatorRepository'); // TODO: Denormalize repository status into the PhabricatorRepository // table so we can do this filtering in the database. foreach ($repositories as $key => $repo) { $status = $this->status; switch ($status) { case self::STATUS_OPEN: if (!$repo->isTracked()) { unset($repositories[$key]); } break; case self::STATUS_CLOSED: if ($repo->isTracked()) { unset($repositories[$key]); } break; case self::STATUS_ALL: break; default: throw new Exception("Unknown status '{$status}'!"); } } return $repositories; } + public function getReversePaging() { + switch ($this->order) { + case self::ORDER_CALLSIGN: + case self::ORDER_NAME: + return true; + } + return false; + } + + protected function getPagingColumn() { + + // TODO: Add a key for ORDER_NAME. + // TODO: Add a key for ORDER_COMMITTED. + + $order = $this->order; + switch ($order) { + case self::ORDER_CREATED: + return 'r.id'; + case self::ORDER_COMMITTED: + return 's.epoch'; + case self::ORDER_CALLSIGN: + return 'r.callsign'; + case self::ORDER_NAME: + return 'r.name'; + default: + throw new Exception("Unknown order '{$order}!'"); + } + } + + private function loadCursorObject($id) { + $results = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withIDs(array($id)) + ->execute(); + return head($results); + } + + protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { + $default = parent::buildPagingClause($conn_r); + + $before_id = $this->getBeforeID(); + $after_id = $this->getAfterID(); + + if (!$before_id && !$after_id) { + return $default; + } + + $order = $this->order; + if ($order == self::ORDER_CREATED) { + return $default; + } + + if ($before_id) { + $cursor = $this->loadCursorObject($before_id); + } else { + $cursor = $this->loadCursorObject($after_id); + } + + if (!$cursor) { + return null; + } + + switch ($order) { + case self::ORDER_COMMITTED: + $commit = $cursor->getMostRecentCommit(); + if (!$commit) { + return null; + } + $epoch = $commit->getEpoch(); + if ($before_id) { + return qsprintf( + $conn_r, + '(s.epoch %Q %d OR (s.epoch = %d AND r.id %Q %d))', + $this->getReversePaging() ? '<' : '>', + $epoch, + $epoch, + $this->getReversePaging() ? '<' : '>', + $cursor->getID()); + } else { + return qsprintf( + $conn_r, + '(s.epoch %Q %d OR (s.epoch = %d AND r.id %Q %d))', + $this->getReversePaging() ? '>' : '<', + $epoch, + $epoch, + $this->getReversePaging() ? '>' : '<', + $cursor->getID()); + } + case self::ORDER_CALLSIGN: + if ($before_id) { + return qsprintf( + $conn_r, + '(r.callsign %Q %s)', + $this->getReversePaging() ? '<' : '>', + $cursor->getCallsign()); + } else { + return qsprintf( + $conn_r, + '(r.callsign %Q %s)', + $this->getReversePaging() ? '>' : '<', + $cursor->getCallsign()); + } + case self::ORDER_NAME: + if ($before_id) { + return qsprintf( + $conn_r, + '(r.name %Q %s OR (r.name = %s AND r.id %Q %d))', + $this->getReversePaging() ? '<' : '>', + $cursor->getName(), + $cursor->getName(), + $this->getReversePaging() ? '<' : '>', + $cursor->getID()); + } else { + return qsprintf( + $conn_r, + '(r.name %Q %s OR (r.name = %s AND r.id %Q %d))', + $this->getReversePaging() ? '>' : '<', + $cursor->getName(), + $cursor->getName(), + $this->getReversePaging() ? '>' : '<', + $cursor->getID()); + } + default: + throw new Exception("Unknown order '{$order}'!"); + } + } + private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { $joins = array(); $join_summary_table = $this->needCommitCounts || - $this->needMostRecentCommits; + $this->needMostRecentCommits || + ($this->order == self::ORDER_COMMITTED); if ($join_summary_table) { $joins[] = qsprintf( $conn_r, - 'LEFT JOIN %T summary ON r.id = summary.repositoryID', + 'LEFT JOIN %T s ON r.id = s.repositoryID', PhabricatorRepository::TABLE_SUMMARY); } return implode(' ', $joins); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'r.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'r.phid IN (%Ls)', $this->phids); } if ($this->callsigns) { $where[] = qsprintf( $conn_r, 'r.callsign IN (%Ls)', $this->callsigns); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } } diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 51c8d68641..9cad9b98bb 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -1,98 +1,132 @@ setParameter('callsigns', $request->getStrList('callsigns')); $saved->setParameter('status', $request->getStr('status')); + $saved->setParameter('order', $request->getStr('order')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorRepositoryQuery()) ->needCommitCounts(true) ->needMostRecentCommits(true); $callsigns = $saved->getParameter('callsigns'); if ($callsigns) { $query->withCallsigns($callsigns); } $status = $saved->getParameter('status'); $status = idx($this->getStatusValues(), $status); if ($status) { $query->withStatus($status); } + $order = $saved->getParameter('order'); + $order = idx($this->getOrderValues(), $order); + if ($order) { + $query->setOrder($order); + } else { + $query->setOrder(head($this->getOrderValues())); + } + return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $callsigns = $saved_query->getParameter('callsigns', array()); $form ->appendChild( id(new AphrontFormTextControl()) ->setName('callsigns') ->setLabel(pht('Callsigns')) ->setValue(implode(', ', $callsigns))) ->appendChild( id(new AphrontFormSelectControl()) ->setName('status') ->setLabel(pht('Status')) ->setValue($saved_query->getParameter('status')) - ->setOptions($this->getStatusOptions())); + ->setOptions($this->getStatusOptions())) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setName('order') + ->setLabel(pht('Order')) + ->setValue($saved_query->getParameter('order')) + ->setOptions($this->getOrderOptions())); } protected function getURI($path) { return '/diffusion/'.$path; } public function getBuiltinQueryNames() { $names = array( 'active' => pht('Active Repositories'), 'all' => pht('All Repositories'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'active': return $query->setParameter('status', 'open'); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( '' => pht('Active and Inactive Repositories'), 'open' => pht('Active Repositories'), 'closed' => pht('Inactive Repositories'), ); } private function getStatusValues() { return array( '' => PhabricatorRepositoryQuery::STATUS_ALL, 'open' => PhabricatorRepositoryQuery::STATUS_OPEN, 'closed' => PhabricatorRepositoryQuery::STATUS_CLOSED, ); } + private function getOrderOptions() { + return array( + 'committed' => pht('Most Recent Commit'), + 'name' => pht('Name'), + 'callsign' => pht('Callsign'), + 'created' => pht('Date Created'), + ); + } + + private function getOrderValues() { + return array( + 'committed' => PhabricatorRepositoryQuery::ORDER_COMMITTED, + 'name' => PhabricatorRepositoryQuery::ORDER_NAME, + 'callsign' => PhabricatorRepositoryQuery::ORDER_CALLSIGN, + 'created' => PhabricatorRepositoryQuery::ORDER_CREATED, + ); + } + + }