diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index af37dfc05c..5e485d6360 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -1,1190 +1,1178 @@ withStatus(DifferentialRevisionQuery::STATUS_OPEN) * ->execute(); * * @task config Query Configuration * @task exec Query Execution * @task internal Internals */ final class DifferentialRevisionQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $pathIDs = array(); private $status = 'status-any'; const STATUS_ANY = 'status-any'; const STATUS_OPEN = 'status-open'; const STATUS_ACCEPTED = 'status-accepted'; const STATUS_NEEDS_REVIEW = 'status-needs-review'; const STATUS_NEEDS_REVISION = 'status-needs-revision'; const STATUS_CLOSED = 'status-closed'; const STATUS_ABANDONED = 'status-abandoned'; private $authors = array(); private $draftAuthors = array(); private $ccs = array(); private $reviewers = array(); private $revIDs = array(); private $commitHashes = array(); private $commitPHIDs = array(); private $phids = array(); private $responsibles = array(); private $branches = array(); private $repositoryPHIDs; private $updatedEpochMin; private $updatedEpochMax; const ORDER_MODIFIED = 'order-modified'; const ORDER_CREATED = 'order-created'; private $needRelationships = false; private $needActiveDiffs = false; private $needDiffIDs = false; private $needCommitPHIDs = false; private $needHashes = false; private $needReviewerStatus = false; private $needReviewerAuthority; private $needDrafts; private $needFlags; private $buildingGlobalOrder; /* -( Query Configuration )------------------------------------------------ */ /** * Filter results to revisions which affect a Diffusion path ID in a given * repository. You can call this multiple times to select revisions for * several paths. * * @param int Diffusion repository ID. * @param int Diffusion path ID. * @return this * @task config */ public function withPath($repository_id, $path_id) { $this->pathIDs[] = array( 'repositoryID' => $repository_id, 'pathID' => $path_id, ); return $this; } /** * Filter results to revisions authored by one of the given PHIDs. Calling * this function will clear anything set by previous calls to * @{method:withAuthors}. * * @param array List of PHIDs of authors * @return this * @task config */ public function withAuthors(array $author_phids) { $this->authors = $author_phids; return $this; } - /** - * Filter results to revisions with comments authored by the given PHIDs. - * - * @param array List of PHIDs of authors - * @return this - * @task config - */ - public function withDraftRepliesByAuthors(array $author_phids) { - $this->draftAuthors = $author_phids; - return $this; - } - /** * Filter results to revisions which CC one of the listed people. Calling this * function will clear anything set by previous calls to @{method:withCCs}. * * @param array List of PHIDs of subscribers. * @return this * @task config */ public function withCCs(array $cc_phids) { $this->ccs = $cc_phids; return $this; } /** * Filter results to revisions that have one of the provided PHIDs as * reviewers. Calling this function will clear anything set by previous calls * to @{method:withReviewers}. * * @param array List of PHIDs of reviewers * @return this * @task config */ public function withReviewers(array $reviewer_phids) { $this->reviewers = $reviewer_phids; return $this; } /** * Filter results to revisions that have one of the provided commit hashes. * Calling this function will clear anything set by previous calls to * @{method:withCommitHashes}. * * @param array List of pairs * @return this * @task config */ public function withCommitHashes(array $commit_hashes) { $this->commitHashes = $commit_hashes; return $this; } /** * Filter results to revisions that have one of the provided PHIDs as * commits. Calling this function will clear anything set by previous calls * to @{method:withCommitPHIDs}. * * @param array List of PHIDs of commits * @return this * @task config */ public function withCommitPHIDs(array $commit_phids) { $this->commitPHIDs = $commit_phids; return $this; } /** * Filter results to revisions with a given status. Provide a class constant, * such as `DifferentialRevisionQuery::STATUS_OPEN`. * * @param const Class STATUS constant, like STATUS_OPEN. * @return this * @task config */ public function withStatus($status_constant) { $this->status = $status_constant; return $this; } /** * Filter results to revisions on given branches. * * @param list List of branch names. * @return this * @task config */ public function withBranches(array $branches) { $this->branches = $branches; return $this; } /** * Filter results to only return revisions whose ids are in the given set. * * @param array List of revision ids * @return this * @task config */ public function withIDs(array $ids) { $this->revIDs = $ids; return $this; } /** * Filter results to only return revisions whose PHIDs are in the given set. * * @param array List of revision PHIDs * @return this * @task config */ public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } /** * Given a set of users, filter results to return only revisions they are * responsible for (i.e., they are either authors or reviewers). * * @param array List of user PHIDs. * @return this * @task config */ public function withResponsibleUsers(array $responsible_phids) { $this->responsibles = $responsible_phids; return $this; } public function withRepositoryPHIDs(array $repository_phids) { $this->repositoryPHIDs = $repository_phids; return $this; } public function withUpdatedEpochBetween($min, $max) { $this->updatedEpochMin = $min; $this->updatedEpochMax = $max; return $this; } - /** - * Set result ordering. Provide a class constant, such as - * `DifferentialRevisionQuery::ORDER_CREATED`. - * - * @task config - */ - public function setOrder($order_constant) { - switch ($order_constant) { - case self::ORDER_CREATED: - $this->setOrderVector(array('id')); - break; - case self::ORDER_MODIFIED: - $this->setOrderVector(array('updated', 'id')); - break; - default: - throw new Exception(pht('Unknown order "%s".', $order_constant)); - } - - return $this; - } - /** * Set whether or not the query will load and attach relationships. * * @param bool True to load and attach relationships. * @return this * @task config */ public function needRelationships($need_relationships) { $this->needRelationships = $need_relationships; return $this; } /** * Set whether or not the query should load the active diff for each * revision. * * @param bool True to load and attach diffs. * @return this * @task config */ public function needActiveDiffs($need_active_diffs) { $this->needActiveDiffs = $need_active_diffs; return $this; } /** * Set whether or not the query should load the associated commit PHIDs for * each revision. * * @param bool True to load and attach diffs. * @return this * @task config */ public function needCommitPHIDs($need_commit_phids) { $this->needCommitPHIDs = $need_commit_phids; return $this; } /** * Set whether or not the query should load associated diff IDs for each * revision. * * @param bool True to load and attach diff IDs. * @return this * @task config */ public function needDiffIDs($need_diff_ids) { $this->needDiffIDs = $need_diff_ids; return $this; } /** * Set whether or not the query should load associated commit hashes for each * revision. * * @param bool True to load and attach commit hashes. * @return this * @task config */ public function needHashes($need_hashes) { $this->needHashes = $need_hashes; return $this; } /** * Set whether or not the query should load associated reviewer status. * * @param bool True to load and attach reviewers. * @return this * @task config */ public function needReviewerStatus($need_reviewer_status) { $this->needReviewerStatus = $need_reviewer_status; return $this; } /** * Request information about the viewer's authority to act on behalf of each * reviewer. In particular, they have authority to act on behalf of projects * they are a member of. * * @param bool True to load and attach authority. * @return this * @task config */ public function needReviewerAuthority($need_reviewer_authority) { $this->needReviewerAuthority = $need_reviewer_authority; return $this; } public function needFlags($need_flags) { $this->needFlags = $need_flags; return $this; } public function needDrafts($need_drafts) { $this->needDrafts = $need_drafts; return $this; } /* -( Query Execution )---------------------------------------------------- */ + public function newResultObject() { + return new DifferentialRevision(); + } + + /** * Execute the query as configured, returning matching * @{class:DifferentialRevision} objects. * * @return list List of matching DifferentialRevision objects. * @task exec */ protected function loadPage() { - $table = new DifferentialRevision(); - $conn_r = $table->establishConnection('r'); - $data = $this->loadData(); + $table = $this->newResultObject(); return $table->loadAllFromArray($data); } protected function willFilterPage(array $revisions) { $viewer = $this->getViewer(); $repository_phids = mpull($revisions, 'getRepositoryPHID'); $repository_phids = array_filter($repository_phids); $repositories = array(); if ($repository_phids) { $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withPHIDs($repository_phids) ->execute(); $repositories = mpull($repositories, null, 'getPHID'); } // If a revision is associated with a repository: // // - the viewer must be able to see the repository; or // - the viewer must have an automatic view capability. // // In the latter case, we'll load the revision but not load the repository. $can_view = PhabricatorPolicyCapability::CAN_VIEW; foreach ($revisions as $key => $revision) { $repo_phid = $revision->getRepositoryPHID(); if (!$repo_phid) { // The revision has no associated repository. Attach `null` and move on. $revision->attachRepository(null); continue; } $repository = idx($repositories, $repo_phid); if ($repository) { // The revision has an associated repository, and the viewer can see // it. Attach it and move on. $revision->attachRepository($repository); continue; } if ($revision->hasAutomaticCapability($can_view, $viewer)) { // The revision has an associated repository which the viewer can not // see, but the viewer has an automatic capability on this revision. // Load the revision without attaching a repository. $revision->attachRepository(null); continue; } if ($this->getViewer()->isOmnipotent()) { // The viewer is omnipotent. Allow the revision to load even without // a repository. $revision->attachRepository(null); continue; } // The revision has an associated repository, and the viewer can't see // it, and the viewer has no special capabilities. Filter out this // revision. $this->didRejectResult($revision); unset($revisions[$key]); } if (!$revisions) { return array(); } $table = new DifferentialRevision(); $conn_r = $table->establishConnection('r'); if ($this->needRelationships) { $this->loadRelationships($conn_r, $revisions); } if ($this->needCommitPHIDs) { $this->loadCommitPHIDs($conn_r, $revisions); } $need_active = $this->needActiveDiffs; $need_ids = $need_active || $this->needDiffIDs; if ($need_ids) { $this->loadDiffIDs($conn_r, $revisions); } if ($need_active) { $this->loadActiveDiffs($conn_r, $revisions); } if ($this->needHashes) { $this->loadHashes($conn_r, $revisions); } if ($this->needReviewerStatus || $this->needReviewerAuthority) { $this->loadReviewers($conn_r, $revisions); } return $revisions; } protected function didFilterPage(array $revisions) { $viewer = $this->getViewer(); if ($this->needFlags) { $flags = id(new PhabricatorFlagQuery()) ->setViewer($viewer) ->withOwnerPHIDs(array($viewer->getPHID())) ->withObjectPHIDs(mpull($revisions, 'getPHID')) ->execute(); $flags = mpull($flags, null, 'getObjectPHID'); foreach ($revisions as $revision) { $revision->attachFlag( $viewer, idx($flags, $revision->getPHID())); } } if ($this->needDrafts) { $drafts = id(new DifferentialDraft())->loadAllWhere( 'authorPHID = %s AND objectPHID IN (%Ls)', $viewer->getPHID(), mpull($revisions, 'getPHID')); $drafts = mgroup($drafts, 'getObjectPHID'); foreach ($revisions as $revision) { $revision->attachDrafts( $viewer, idx($drafts, $revision->getPHID(), array())); } } return $revisions; } private function loadData() { - $table = new DifferentialRevision(); + $table = $this->newResultObject(); $conn_r = $table->establishConnection('r'); $selects = array(); // NOTE: If the query includes "responsiblePHIDs", we execute it as a // UNION of revisions they own and revisions they're reviewing. This has // much better performance than doing it with JOIN/WHERE. if ($this->responsibles) { $basic_authors = $this->authors; $basic_reviewers = $this->reviewers; $authority_phids = $this->responsibles; $authority_projects = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withMemberPHIDs($this->responsibles) ->execute(); foreach ($authority_projects as $project) { $authority_phids[] = $project->getPHID(); } // NOTE: We're querying by explicit owners to make this a little faster, // since we've already expanded project membership so we don't need to // have the PackageQuery do it again. $authority_packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($this->getViewer()) ->withOwnerPHIDs($authority_phids) ->execute(); foreach ($authority_packages as $package) { $authority_phids[] = $package->getPHID(); } try { // Build the query where the responsible users are authors. $this->authors = array_merge($basic_authors, $this->responsibles); $this->reviewers = $basic_reviewers; $selects[] = $this->buildSelectStatement($conn_r); // Build the query where the responsible users are reviewers, or // projects they are members of are reviewers. $this->authors = $basic_authors; $this->reviewers = array_merge( $basic_reviewers, $authority_phids); $selects[] = $this->buildSelectStatement($conn_r); // Put everything back like it was. $this->authors = $basic_authors; $this->reviewers = $basic_reviewers; } catch (Exception $ex) { $this->authors = $basic_authors; $this->reviewers = $basic_reviewers; throw $ex; } } else { $selects[] = $this->buildSelectStatement($conn_r); } if (count($selects) > 1) { $this->buildingGlobalOrder = true; $query = qsprintf( $conn_r, '%Q %Q %Q', implode(' UNION DISTINCT ', $selects), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); } else { $query = head($selects); } return queryfx_all($conn_r, '%Q', $query); } private function buildSelectStatement(AphrontDatabaseConnection $conn_r) { $table = new DifferentialRevision(); $select = $this->buildSelectClause($conn_r); $from = qsprintf( $conn_r, 'FROM %T r', $table->getTableName()); $joins = $this->buildJoinsClause($conn_r); $where = $this->buildWhereClause($conn_r); - $group_by = $this->buildGroupByClause($conn_r); + $group_by = $this->buildGroupClause($conn_r); $having = $this->buildHavingClause($conn_r); $this->buildingGlobalOrder = false; $order_by = $this->buildOrderClause($conn_r); $limit = $this->buildLimitClause($conn_r); return qsprintf( $conn_r, '(%Q %Q %Q %Q %Q %Q %Q %Q)', $select, $from, $joins, $where, $group_by, $having, $order_by, $limit); } /* -( Internals )---------------------------------------------------------- */ /** * @task internal */ private function buildJoinsClause($conn_r) { $joins = array(); if ($this->pathIDs) { $path_table = new DifferentialAffectedPath(); $joins[] = qsprintf( $conn_r, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName()); } if ($this->commitHashes) { $joins[] = qsprintf( $conn_r, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME); } if ($this->ccs) { $joins[] = qsprintf( $conn_r, 'JOIN %T e_ccs ON e_ccs.src = r.phid '. 'AND e_ccs.type = %s '. 'AND e_ccs.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorObjectHasSubscriberEdgeType::EDGECONST, $this->ccs); } if ($this->reviewers) { $joins[] = qsprintf( $conn_r, 'JOIN %T e_reviewers ON e_reviewers.src = r.phid '. 'AND e_reviewers.type = %s '. 'AND e_reviewers.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, DifferentialRevisionHasReviewerEdgeType::EDGECONST, $this->reviewers); } if ($this->draftAuthors) { $differential_draft = new DifferentialDraft(); $joins[] = qsprintf( $conn_r, 'JOIN %T has_draft ON has_draft.objectPHID = r.phid '. 'AND has_draft.authorPHID IN (%Ls)', $differential_draft->getTableName(), $this->draftAuthors); } if ($this->commitPHIDs) { $joins[] = qsprintf( $conn_r, 'JOIN %T commits ON commits.revisionID = r.id', DifferentialRevision::TABLE_COMMIT); } $joins[] = $this->buildJoinClauseParts($conn_r); return $this->formatJoinClause($joins); } /** * @task internal */ protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->pathIDs) { $path_clauses = array(); $repo_info = igroup($this->pathIDs, 'repositoryID'); foreach ($repo_info as $repository_id => $paths) { $path_clauses[] = qsprintf( $conn_r, '(p.repositoryID = %d AND p.pathID IN (%Ld))', $repository_id, ipull($paths, 'pathID')); } $path_clauses = '('.implode(' OR ', $path_clauses).')'; $where[] = $path_clauses; } if ($this->authors) { $where[] = qsprintf( $conn_r, 'r.authorPHID IN (%Ls)', $this->authors); } if ($this->revIDs) { $where[] = qsprintf( $conn_r, 'r.id IN (%Ld)', $this->revIDs); } if ($this->repositoryPHIDs) { $where[] = qsprintf( $conn_r, 'r.repositoryPHID IN (%Ls)', $this->repositoryPHIDs); } if ($this->commitHashes) { $hash_clauses = array(); foreach ($this->commitHashes as $info) { list($type, $hash) = $info; $hash_clauses[] = qsprintf( $conn_r, '(hash_rel.type = %s AND hash_rel.hash = %s)', $type, $hash); } $hash_clauses = '('.implode(' OR ', $hash_clauses).')'; $where[] = $hash_clauses; } if ($this->commitPHIDs) { $where[] = qsprintf( $conn_r, 'commits.commitPHID IN (%Ls)', $this->commitPHIDs); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'r.phid IN (%Ls)', $this->phids); } if ($this->branches) { $where[] = qsprintf( $conn_r, 'r.branchName in (%Ls)', $this->branches); } if ($this->updatedEpochMin !== null) { $where[] = qsprintf( $conn_r, 'r.dateModified >= %d', $this->updatedEpochMin); } if ($this->updatedEpochMax !== null) { $where[] = qsprintf( $conn_r, 'r.dateModified <= %d', $this->updatedEpochMax); } // NOTE: Although the status constants are integers in PHP, the column is a // string column in MySQL, and MySQL won't use keys on string columns if // you put integers in the query. switch ($this->status) { case self::STATUS_ANY: break; case self::STATUS_OPEN: $where[] = qsprintf( $conn_r, 'r.status IN (%Ls)', DifferentialRevisionStatus::getOpenStatuses()); break; case self::STATUS_NEEDS_REVIEW: $where[] = qsprintf( $conn_r, 'r.status IN (%Ls)', array( ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, )); break; case self::STATUS_NEEDS_REVISION: $where[] = qsprintf( $conn_r, 'r.status IN (%Ls)', array( ArcanistDifferentialRevisionStatus::NEEDS_REVISION, )); break; case self::STATUS_ACCEPTED: $where[] = qsprintf( $conn_r, 'r.status IN (%Ls)', array( ArcanistDifferentialRevisionStatus::ACCEPTED, )); break; case self::STATUS_CLOSED: $where[] = qsprintf( $conn_r, 'r.status IN (%Ls)', DifferentialRevisionStatus::getClosedStatuses()); break; case self::STATUS_ABANDONED: $where[] = qsprintf( $conn_r, 'r.status IN (%Ls)', array( ArcanistDifferentialRevisionStatus::ABANDONED, )); break; default: throw new Exception( pht("Unknown revision status filter constant '%s'!", $this->status)); } $where[] = $this->buildWhereClauseParts($conn_r); return $this->formatWhereClause($where); } /** * @task internal */ - private function buildGroupByClause($conn_r) { + protected function shouldGroupQueryResultRows() { + $join_triggers = array_merge( $this->pathIDs, $this->ccs, $this->reviewers); - $needs_distinct = (count($join_triggers) > 1); - - if ($needs_distinct) { - return 'GROUP BY r.id'; - } else { - return ''; + if (count($join_triggers) > 1) { + return true; } + + return parent::shouldGroupQueryResultRows(); + } + + public function getBuiltinOrders() { + $orders = parent::getBuiltinOrders() + array( + 'updated' => array( + 'vector' => array('updated', 'id'), + 'name' => pht('Date Updated (Latest First)'), + 'aliases' => array(self::ORDER_MODIFIED), + ), + 'outdated' => array( + 'vector' => array('-updated', '-id'), + 'name' => pht('Date Updated (Oldest First)'), + ), + ); + + // Alias the "newest" builtin to the historical key for it. + $orders['newest']['aliases'][] = self::ORDER_CREATED; + + return $orders; } protected function getDefaultOrderVector() { return array('updated', 'id'); } public function getOrderableColumns() { $primary = ($this->buildingGlobalOrder ? null : 'r'); return array( 'id' => array( 'table' => $primary, 'column' => 'id', 'type' => 'int', 'unique' => true, ), 'updated' => array( 'table' => $primary, 'column' => 'dateModified', 'type' => 'int', ), ); } protected function getPagingValueMap($cursor, array $keys) { $revision = $this->loadCursorObject($cursor); return array( 'id' => $revision->getID(), 'updated' => $revision->getDateModified(), ); } private function loadRelationships($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $type_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; $type_subscriber = PhabricatorObjectHasSubscriberEdgeType::EDGECONST; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($revisions, 'getPHID')) ->withEdgeTypes(array($type_reviewer, $type_subscriber)) ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) ->execute(); $type_map = array( DifferentialRevision::RELATION_REVIEWER => $type_reviewer, DifferentialRevision::RELATION_SUBSCRIBED => $type_subscriber, ); foreach ($revisions as $revision) { $data = array(); foreach ($type_map as $rel_type => $edge_type) { $revision_edges = $edges[$revision->getPHID()][$edge_type]; foreach ($revision_edges as $dst_phid => $edge_data) { $data[] = array( 'relation' => $rel_type, 'objectPHID' => $dst_phid, 'reasonPHID' => null, ); } } $revision->attachRelationships($data); } } private function loadCommitPHIDs($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $commit_phids = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE revisionID IN (%Ld)', DifferentialRevision::TABLE_COMMIT, mpull($revisions, 'getID')); $commit_phids = igroup($commit_phids, 'revisionID'); foreach ($revisions as $revision) { $phids = idx($commit_phids, $revision->getID(), array()); $phids = ipull($phids, 'commitPHID'); $revision->attachCommitPHIDs($phids); } } private function loadDiffIDs($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $diff_table = new DifferentialDiff(); $diff_ids = queryfx_all( $conn_r, 'SELECT revisionID, id FROM %T WHERE revisionID IN (%Ld) ORDER BY id DESC', $diff_table->getTableName(), mpull($revisions, 'getID')); $diff_ids = igroup($diff_ids, 'revisionID'); foreach ($revisions as $revision) { $ids = idx($diff_ids, $revision->getID(), array()); $ids = ipull($ids, 'id'); $revision->attachDiffIDs($ids); } } private function loadActiveDiffs($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $diff_table = new DifferentialDiff(); $load_ids = array(); foreach ($revisions as $revision) { $diffs = $revision->getDiffIDs(); if ($diffs) { $load_ids[] = max($diffs); } } $active_diffs = array(); if ($load_ids) { $active_diffs = $diff_table->loadAllWhere( 'id IN (%Ld)', $load_ids); } $active_diffs = mpull($active_diffs, null, 'getRevisionID'); foreach ($revisions as $revision) { $revision->attachActiveDiff(idx($active_diffs, $revision->getID())); } } private function loadHashes( AphrontDatabaseConnection $conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE revisionID IN (%Ld)', 'differential_revisionhash', mpull($revisions, 'getID')); $data = igroup($data, 'revisionID'); foreach ($revisions as $revision) { $hashes = idx($data, $revision->getID(), array()); $list = array(); foreach ($hashes as $hash) { $list[] = array($hash['type'], $hash['hash']); } $revision->attachHashes($list); } } private function loadReviewers( AphrontDatabaseConnection $conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($revisions, 'getPHID')) ->withEdgeTypes(array($edge_type)) ->needEdgeData(true) ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) ->execute(); $viewer = $this->getViewer(); $viewer_phid = $viewer->getPHID(); $allow_key = 'differential.allow-self-accept'; $allow_self = PhabricatorEnv::getEnvConfig($allow_key); // Figure out which of these reviewers the viewer has authority to act as. if ($this->needReviewerAuthority && $viewer_phid) { $authority = $this->loadReviewerAuthority( $revisions, $edges, $allow_self); } foreach ($revisions as $revision) { $revision_edges = $edges[$revision->getPHID()][$edge_type]; $reviewers = array(); foreach ($revision_edges as $reviewer_phid => $edge) { $reviewer = new DifferentialReviewer($reviewer_phid, $edge['data']); if ($this->needReviewerAuthority) { if (!$viewer_phid) { // Logged-out users never have authority. $has_authority = false; } else if ((!$allow_self) && ($revision->getAuthorPHID() == $viewer_phid)) { // The author can never have authority unless we allow self-accept. $has_authority = false; } else { // Otherwise, look up whether the viewer has authority. $has_authority = isset($authority[$reviewer_phid]); } $reviewer->attachAuthority($viewer, $has_authority); } $reviewers[$reviewer_phid] = $reviewer; } $revision->attachReviewerStatus($reviewers); } } public static function splitResponsible(array $revisions, array $user_phids) { $blocking = array(); $active = array(); $waiting = array(); $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; // Bucket revisions into $blocking (revisions where you are blocking // others), $active (revisions you need to do something about) and $waiting // (revisions you're waiting on someone else to do something about). foreach ($revisions as $revision) { $needs_review = ($revision->getStatus() == $status_review); $filter_is_author = in_array($revision->getAuthorPHID(), $user_phids); if (!$revision->getReviewers()) { $needs_review = false; $author_is_reviewer = false; } else { $author_is_reviewer = in_array( $revision->getAuthorPHID(), $revision->getReviewers()); } // If exactly one of "needs review" and "the user is the author" is // true, the user needs to act on it. Otherwise, they're waiting on // it. if ($needs_review ^ $filter_is_author) { if ($needs_review) { array_unshift($blocking, $revision); } else { $active[] = $revision; } // User is author **and** reviewer. An exotic but configurable workflow. // User needs to act on it double. } else if ($needs_review && $author_is_reviewer) { array_unshift($blocking, $revision); $active[] = $revision; } else { $waiting[] = $revision; } } return array($blocking, $active, $waiting); } private function loadReviewerAuthority( array $revisions, array $edges, $allow_self) { $revision_map = mpull($revisions, null, 'getPHID'); $viewer_phid = $this->getViewer()->getPHID(); // Find all the project/package reviewers which the user may have authority // over. $project_phids = array(); $package_phids = array(); $project_type = PhabricatorProjectProjectPHIDType::TYPECONST; $package_type = PhabricatorOwnersPackagePHIDType::TYPECONST; $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; foreach ($edges as $src => $types) { if (!$allow_self) { if ($revision_map[$src]->getAuthorPHID() == $viewer_phid) { // If self-review isn't permitted, the user will never have // authority over projects on revisions they authored because you // can't accept your own revisions, so we don't need to load any // data about these reviewers. continue; } } $edge_data = idx($types, $edge_type, array()); foreach ($edge_data as $dst => $data) { $phid_type = phid_get_type($dst); if ($phid_type == $project_type) { $project_phids[] = $dst; } if ($phid_type == $package_type) { $package_phids[] = $dst; } } } // The viewer has authority over themselves. $user_authority = array_fuse(array($viewer_phid)); // And over any projects they are a member of. $project_authority = array(); if ($project_phids) { $project_authority = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($project_phids) ->withMemberPHIDs(array($viewer_phid)) ->execute(); $project_authority = mpull($project_authority, 'getPHID'); $project_authority = array_fuse($project_authority); } // And over any packages they own. $package_authority = array(); if ($package_phids) { $package_authority = id(new PhabricatorOwnersPackageQuery()) ->setViewer($this->getViewer()) ->withPHIDs($package_phids) ->withAuthorityPHIDs(array($viewer_phid)) ->execute(); $package_authority = mpull($package_authority, 'getPHID'); $package_authority = array_fuse($package_authority); } return $user_authority + $project_authority + $package_authority; } public function getQueryApplicationClass() { return 'PhabricatorDifferentialApplication'; } protected function getPrimaryTableAlias() { return 'r'; } } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index 5e4b76a326..5ff1ad1f57 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -1,364 +1,243 @@ needFlags(true) ->needDrafts(true) ->needRelationships(true); } public function getPageSize(PhabricatorSavedQuery $saved) { if ($saved->getQueryKey() == 'active') { return 0xFFFF; } return parent::getPageSize($saved); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'responsiblePHIDs', - $this->readUsersFromRequest($request, 'responsibles')); - - $saved->setParameter( - 'authorPHIDs', - $this->readUsersFromRequest($request, 'authors')); - - $saved->setParameter( - 'reviewerPHIDs', - $this->readUsersFromRequest( - $request, - 'reviewers', - array( - PhabricatorProjectProjectPHIDType::TYPECONST, - ))); - - $saved->setParameter( - 'subscriberPHIDs', - $this->readSubscribersFromRequest($request, 'subscribers')); - - $saved->setParameter( - 'repositoryPHIDs', - $request->getArr('repositories')); - - $saved->setParameter( - 'projects', - $this->readProjectsFromRequest($request, 'projects')); - - $saved->setParameter( - 'draft', - $request->getBool('draft')); - - $saved->setParameter( - 'order', - $request->getStr('order')); - - $saved->setParameter( - 'status', - $request->getStr('status')); - - return $saved; - } - - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new DifferentialRevisionQuery()) - ->needFlags(true) - ->needDrafts(true) - ->needRelationships(true); - $user_datasource = id(new PhabricatorPeopleUserFunctionDatasource()) - ->setViewer($this->requireViewer()); - - $responsible_phids = $saved->getParameter('responsiblePHIDs', array()); - $responsible_phids = $user_datasource->evaluateTokens($responsible_phids); - if ($responsible_phids) { - $query->withResponsibleUsers($responsible_phids); - } - - $this->setQueryProjects($query, $saved); - - $author_phids = $saved->getParameter('authorPHIDs', array()); - $author_phids = $user_datasource->evaluateTokens($author_phids); - if ($author_phids) { - $query->withAuthors($author_phids); - } + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $reviewer_phids = $saved->getParameter('reviewerPHIDs', array()); - if ($reviewer_phids) { - $query->withReviewers($reviewer_phids); + if ($map['responsiblePHIDs']) { + $query->withResponsibleUsers($map['responsiblePHIDs']); } - $sub_datasource = id(new PhabricatorMetaMTAMailableFunctionDatasource()) - ->setViewer($this->requireViewer()); - $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); - $subscriber_phids = $sub_datasource->evaluateTokens($subscriber_phids); - if ($subscriber_phids) { - $query->withCCs($subscriber_phids); + if ($map['authorPHIDs']) { + $query->withAuthors($map['authorPHIDs']); } - $repository_phids = $saved->getParameter('repositoryPHIDs', array()); - if ($repository_phids) { - $query->withRepositoryPHIDs($repository_phids); + if ($map['reviewerPHIDs']) { + $query->withReviewers($map['reviewerPHIDs']); } - $draft = $saved->getParameter('draft', false); - if ($draft && $this->requireViewer()->isLoggedIn()) { - $query->withDraftRepliesByAuthors( - array($this->requireViewer()->getPHID())); + if ($map['repositoryPHIDs']) { + $query->withRepositoryPHIDs($map['repositoryPHIDs']); } - $status = $saved->getParameter('status'); - if (idx($this->getStatusOptions(), $status)) { - $query->withStatus($status); - } - - $order = $saved->getParameter('order'); - if (idx($this->getOrderOptions(), $order)) { - $query->setOrder($order); - } else { - $query->setOrder(DifferentialRevisionQuery::ORDER_CREATED); + if ($map['status']) { + $query->withStatus($map['status']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $responsible_phids = $saved->getParameter('responsiblePHIDs', array()); - $author_phids = $saved->getParameter('authorPHIDs', array()); - $reviewer_phids = $saved->getParameter('reviewerPHIDs', array()); - $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); - $repository_phids = $saved->getParameter('repositoryPHIDs', array()); - $only_draft = $saved->getParameter('draft', false); - $projects = $saved->getParameter('projects', array()); - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Responsible Users')) - ->setName('responsibles') - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) - ->setValue($responsible_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Authors')) - ->setName('authors') - ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()) - ->setValue($author_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Reviewers')) - ->setName('reviewers') - ->setDatasource(new PhabricatorProjectOrUserDatasource()) - ->setValue($reviewer_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Subscribers')) - ->setName('subscribers') - ->setDatasource(new PhabricatorMetaMTAMailableFunctionDatasource()) - ->setValue($subscriber_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Repositories')) - ->setName('repositories') - ->setDatasource(new DiffusionRepositoryDatasource()) - ->setValue($repository_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setDatasource(new PhabricatorProjectLogicalDatasource()) - ->setValue($projects)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setOptions($this->getStatusOptions()) - ->setValue($saved->getParameter('status'))); - - if ($this->requireViewer()->isLoggedIn()) { - $form - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'draft', - 1, - pht('Show only revisions with a draft comment.'), - $only_draft)); - } - - $form - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Order')) - ->setName('order') - ->setOptions($this->getOrderOptions()) - ->setValue($saved->getParameter('order'))); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Responsible Users')) + ->setKey('responsiblePHIDs') + ->setAliases(array('responsiblePHID', 'responsibles', 'responsible')) + ->setDescription( + pht('Find revisions that a given user is responsible for.')), + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Authors')) + ->setKey('authorPHIDs') + ->setAliases(array('author', 'authors', 'authorPHID')) + ->setDescription( + pht('Find revisions with specific authors.')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Reviewers')) + ->setKey('reviewerPHIDs') + ->setAliases(array('reviewer', 'reviewers', 'reviewerPHID')) + ->setDatasource(new DiffusionAuditorDatasource()) + ->setDescription( + pht('Find revisions with specific reviewers.')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Repositories')) + ->setKey('repositoryPHIDs') + ->setAliases(array('repository', 'repositories', 'repositoryPHID')) + ->setDatasource(new DiffusionRepositoryDatasource()) + ->setDescription( + pht('Find revisions from specific repositories.')), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Status')) + ->setKey('status') + ->setOptions($this->getStatusOptions()) + ->setDescription( + pht('Find revisions with particular statuses.')), + ); } protected function getURI($path) { return '/differential/'.$path; } protected function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['active'] = pht('Active Revisions'); $names['authored'] = pht('Authored'); } $names['all'] = pht('All Revisions'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer = $this->requireViewer(); switch ($query_key) { case 'active': return $query ->setParameter('responsiblePHIDs', array($viewer->getPHID())) ->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN); case 'authored': return $query ->setParameter('authorPHIDs', array($viewer->getPHID())); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( DifferentialRevisionQuery::STATUS_ANY => pht('All'), DifferentialRevisionQuery::STATUS_OPEN => pht('Open'), DifferentialRevisionQuery::STATUS_ACCEPTED => pht('Accepted'), DifferentialRevisionQuery::STATUS_NEEDS_REVIEW => pht('Needs Review'), DifferentialRevisionQuery::STATUS_NEEDS_REVISION => pht('Needs Revision'), DifferentialRevisionQuery::STATUS_CLOSED => pht('Closed'), DifferentialRevisionQuery::STATUS_ABANDONED => pht('Abandoned'), ); } private function getOrderOptions() { return array( DifferentialRevisionQuery::ORDER_CREATED => pht('Created'), DifferentialRevisionQuery::ORDER_MODIFIED => pht('Updated'), ); } protected function renderResultList( array $revisions, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($revisions, 'DifferentialRevision'); $viewer = $this->requireViewer(); $template = id(new DifferentialRevisionListView()) ->setUser($viewer) ->setNoBox($this->isPanelContext()); $views = array(); if ($query->getQueryKey() == 'active') { $split = DifferentialRevisionQuery::splitResponsible( $revisions, $query->getParameter('responsiblePHIDs')); list($blocking, $active, $waiting) = $split; $views[] = id(clone $template) ->setHeader(pht('Blocking Others')) ->setNoDataString( pht('No revisions are blocked on your action.')) ->setHighlightAge(true) ->setRevisions($blocking) ->setHandles(array()); $views[] = id(clone $template) ->setHeader(pht('Action Required')) ->setNoDataString( pht('No revisions require your action.')) ->setHighlightAge(true) ->setRevisions($active) ->setHandles(array()); $views[] = id(clone $template) ->setHeader(pht('Waiting on Others')) ->setNoDataString( pht('You have no revisions waiting on others.')) ->setRevisions($waiting) ->setHandles(array()); } else { $views[] = id(clone $template) ->setRevisions($revisions) ->setHandles(array()); } $phids = array_mergev(mpull($views, 'getRequiredHandlePHIDs')); if ($phids) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs($phids) ->execute(); } else { $handles = array(); } foreach ($views as $view) { $view->setHandles($handles); } if (count($views) == 1) { // Reduce this to a PHUIObjectItemListView so we can get the free // support from ApplicationSearch. $list = head($views)->render(); } else { $list = $views; } $result = new PhabricatorApplicationSearchResultView(); $result->setContent($list); return $result; } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Diff')) ->setHref('/differential/diff/create/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Pre-commit code review. Revisions that are waiting on your input '. 'will appear here.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 4c92f9d642..0287966fa3 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -1,408 +1,407 @@ isBoardView = $is_board_view; return $this; } public function getIsBoardView() { return $this->isBoardView; } public function setBaseURI($base_uri) { $this->baseURI = $base_uri; return $this; } public function getBaseURI() { return $this->baseURI; } public function setShowBatchControls($show_batch_controls) { $this->showBatchControls = $show_batch_controls; return $this; } public function getResultTypeDescription() { return pht('Tasks'); } public function getApplicationClassName() { return 'PhabricatorManiphestApplication'; } public function newQuery() { return id(new ManiphestTaskQuery()) ->needProjectPHIDs(true); } protected function buildCustomSearchFields() { return array( id(new PhabricatorOwnersSearchField()) ->setLabel(pht('Assigned To')) ->setKey('assignedPHIDs') ->setConduitKey('assigned') ->setAliases(array('assigned')) ->setDescription( pht('Search for tasks owned by a user from a list.')), id(new PhabricatorUsersSearchField()) ->setLabel(pht('Authors')) ->setKey('authorPHIDs') ->setAliases(array('author', 'authors')) ->setDescription( pht('Search for tasks with given authors.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Statuses')) ->setKey('statuses') ->setAliases(array('status')) ->setDescription( pht('Search for tasks with given statuses.')) ->setDatasource(new ManiphestTaskStatusFunctionDatasource()), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Priorities')) ->setKey('priorities') ->setAliases(array('priority')) ->setDescription( pht('Search for tasks with given priorities.')) ->setConduitParameterType(new ConduitIntListParameterType()) ->setDatasource(new ManiphestTaskPriorityDatasource()), id(new PhabricatorSearchTextField()) ->setLabel(pht('Contains Words')) ->setKey('fulltext'), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Blocking')) ->setKey('blocking') ->setOptions( pht('(Show All)'), pht('Show Only Tasks Blocking Other Tasks'), pht('Hide Tasks Blocking Other Tasks')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Blocked')) ->setKey('blocked') ->setOptions( pht('(Show All)'), pht('Show Only Task Blocked By Other Tasks'), pht('Hide Tasks Blocked By Other Tasks')), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Group By')) ->setKey('group') ->setOptions($this->getGroupOptions()), id(new PhabricatorSearchDateField()) ->setLabel(pht('Created After')) ->setKey('createdStart'), id(new PhabricatorSearchDateField()) ->setLabel(pht('Created Before')) ->setKey('createdEnd'), id(new PhabricatorSearchDateField()) ->setLabel(pht('Updated After')) ->setKey('modifiedStart'), id(new PhabricatorSearchDateField()) ->setLabel(pht('Updated Before')) ->setKey('modifiedEnd'), id(new PhabricatorSearchTextField()) ->setLabel(pht('Page Size')) ->setKey('limit'), ); } protected function getDefaultFieldOrder() { return array( 'assignedPHIDs', 'projectPHIDs', 'authorPHIDs', 'subscriberPHIDs', 'statuses', 'priorities', 'fulltext', 'blocking', 'blocked', 'group', 'order', 'ids', '...', 'createdStart', 'createdEnd', 'modifiedStart', 'modifiedEnd', 'limit', ); } protected function getHiddenFields() { $keys = array(); if ($this->getIsBoardView()) { $keys[] = 'group'; $keys[] = 'order'; $keys[] = 'limit'; } return $keys; } protected function buildQueryFromParameters(array $map) { - $query = id(new ManiphestTaskQuery()) - ->needProjectPHIDs(true); + $query = $this->newQuery(); if ($map['assignedPHIDs']) { $query->withOwners($map['assignedPHIDs']); } if ($map['authorPHIDs']) { $query->withAuthors($map['authorPHIDs']); } if ($map['statuses']) { $query->withStatuses($map['statuses']); } if ($map['priorities']) { $query->withPriorities($map['priorities']); } if ($map['createdStart']) { $query->withDateCreatedAfter($map['createdStart']); } if ($map['createdEnd']) { $query->withDateCreatedBefore($map['createdEnd']); } if ($map['modifiedStart']) { $query->withDateModifiedAfter($map['modifiedStart']); } if ($map['modifiedEnd']) { $query->withDateModifiedBefore($map['modifiedEnd']); } if ($map['blocking'] !== null) { $query->withBlockingTasks($map['blocking']); } if ($map['blocked'] !== null) { $query->withBlockedTasks($map['blocked']); } if (strlen($map['fulltext'])) { $query->withFullTextSearch($map['fulltext']); } $group = idx($map, 'group'); $group = idx($this->getGroupValues(), $group); if ($group) { $query->setGroupBy($group); } else { $query->setGroupBy(head($this->getGroupValues())); } if ($map['ids']) { $ids = $map['ids']; foreach ($ids as $key => $id) { $id = trim($id, ' Tt'); if (!$id || !is_numeric($id)) { unset($ids[$key]); } else { $ids[$key] = $id; } } if ($ids) { $query->withIDs($ids); } } return $query; } protected function getURI($path) { if ($this->baseURI) { return $this->baseURI.$path; } return '/maniphest/'.$path; } protected function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['assigned'] = pht('Assigned'); $names['authored'] = pht('Authored'); $names['subscribed'] = pht('Subscribed'); } $names['open'] = pht('Open Tasks'); $names['all'] = pht('All Tasks'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); switch ($query_key) { case 'all': return $query; case 'assigned': return $query ->setParameter('assignedPHIDs', array($viewer_phid)) ->setParameter( 'statuses', ManiphestTaskStatus::getOpenStatusConstants()); case 'subscribed': return $query ->setParameter('subscriberPHIDs', array($viewer_phid)) ->setParameter( 'statuses', ManiphestTaskStatus::getOpenStatusConstants()); case 'open': return $query ->setParameter( 'statuses', ManiphestTaskStatus::getOpenStatusConstants()); case 'authored': return $query ->setParameter('authorPHIDs', array($viewer_phid)) ->setParameter('order', 'created') ->setParameter('group', 'none'); } return parent::buildSavedQueryFromBuiltin($query_key); } private function getGroupOptions() { return array( 'priority' => pht('Priority'), 'assigned' => pht('Assigned'), 'status' => pht('Status'), 'project' => pht('Project'), 'none' => pht('None'), ); } private function getGroupValues() { return array( 'priority' => ManiphestTaskQuery::GROUP_PRIORITY, 'assigned' => ManiphestTaskQuery::GROUP_OWNER, 'status' => ManiphestTaskQuery::GROUP_STATUS, 'project' => ManiphestTaskQuery::GROUP_PROJECT, 'none' => ManiphestTaskQuery::GROUP_NONE, ); } protected function renderResultList( array $tasks, PhabricatorSavedQuery $saved, array $handles) { $viewer = $this->requireViewer(); if ($this->isPanelContext()) { $can_edit_priority = false; $can_bulk_edit = false; } else { $can_edit_priority = PhabricatorPolicyFilter::hasCapability( $viewer, $this->getApplication(), ManiphestEditPriorityCapability::CAPABILITY); $can_bulk_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $this->getApplication(), ManiphestBulkEditCapability::CAPABILITY); } $list = id(new ManiphestTaskResultListView()) ->setUser($viewer) ->setTasks($tasks) ->setSavedQuery($saved) ->setCanEditPriority($can_edit_priority) ->setCanBatchEdit($can_bulk_edit) ->setShowBatchControls($this->showBatchControls); $result = new PhabricatorApplicationSearchResultView(); $result->setContent($list); return $result; } protected function willUseSavedQuery(PhabricatorSavedQuery $saved) { // The 'withUnassigned' parameter may be present in old saved queries from // before parameterized typeaheads, and is retained for compatibility. We // could remove it by migrating old saved queries. $assigned_phids = $saved->getParameter('assignedPHIDs', array()); if ($saved->getParameter('withUnassigned')) { $assigned_phids[] = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN; } $saved->setParameter('assignedPHIDs', $assigned_phids); // The 'projects' and other parameters may be present in old saved queries // from before parameterized typeaheads. $project_phids = $saved->getParameter('projectPHIDs', array()); $old = $saved->getParameter('projects', array()); foreach ($old as $phid) { $project_phids[] = $phid; } $all = $saved->getParameter('allProjectPHIDs', array()); foreach ($all as $phid) { $project_phids[] = $phid; } $any = $saved->getParameter('anyProjectPHIDs', array()); foreach ($any as $phid) { $project_phids[] = 'any('.$phid.')'; } $not = $saved->getParameter('excludeProjectPHIDs', array()); foreach ($not as $phid) { $project_phids[] = 'not('.$phid.')'; } $users = $saved->getParameter('userProjectPHIDs', array()); foreach ($users as $phid) { $project_phids[] = 'projects('.$phid.')'; } $no = $saved->getParameter('withNoProject'); if ($no) { $project_phids[] = 'null()'; } $saved->setParameter('projectPHIDs', $project_phids); } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Task')) ->setHref('/maniphest/task/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Use Maniphest to track bugs, features, todos, or anything else '. 'you need to get done. Tasks assigned to you will appear here.')) ->addAction($create_button); return $view; } }