diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index 0b7d01ad86..7f429dd596 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -1,203 +1,219 @@ needAuditRequests(true) ->needCommitData(true) ->needDrafts(true); } protected function newResultBuckets() { return DiffusionCommitResultBucket::getAllResultBuckets(); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['responsiblePHIDs']) { $query->withResponsiblePHIDs($map['responsiblePHIDs']); } if ($map['auditorPHIDs']) { $query->withAuditorPHIDs($map['auditorPHIDs']); } if ($map['authorPHIDs']) { $query->withAuthorPHIDs($map['authorPHIDs']); } if ($map['statuses']) { $query->withStatuses($map['statuses']); } if ($map['repositoryPHIDs']) { $query->withRepositoryPHIDs($map['repositoryPHIDs']); } if ($map['packagePHIDs']) { $query->withPackagePHIDs($map['packagePHIDs']); } + if ($map['unreachable'] !== null) { + $query->withUnreachable($map['unreachable']); + } + return $query; } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Responsible Users')) ->setKey('responsiblePHIDs') ->setConduitKey('responsible') ->setAliases(array('responsible', 'responsibles', 'responsiblePHID')) ->setDatasource(new DifferentialResponsibleDatasource()), id(new PhabricatorUsersSearchField()) ->setLabel(pht('Authors')) ->setKey('authorPHIDs') ->setConduitKey('authors') ->setAliases(array('author', 'authors', 'authorPHID')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Auditors')) ->setKey('auditorPHIDs') ->setConduitKey('auditors') ->setAliases(array('auditor', 'auditors', 'auditorPHID')) ->setDatasource(new DiffusionAuditorFunctionDatasource()), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Audit Status')) ->setKey('statuses') ->setAliases(array('status')) ->setOptions(PhabricatorAuditCommitStatusConstants::getStatusNameMap()), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Repositories')) ->setKey('repositoryPHIDs') ->setConduitKey('repositories') ->setAliases(array('repository', 'repositories', 'repositoryPHID')) ->setDatasource(new DiffusionRepositoryDatasource()), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Packages')) ->setKey('packagePHIDs') ->setConduitKey('packages') ->setAliases(array('package', 'packages', 'packagePHID')) ->setDatasource(new PhabricatorOwnersPackageDatasource()), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Unreachable')) + ->setKey('unreachable') + ->setOptions( + pht('(Show All)'), + pht('Show Only Unreachable Commits'), + pht('Hide Unreachable Commits')) + ->setDescription( + pht( + 'Find or exclude unreachable commits which are not ancestors of '. + 'any branch, tag, or ref.')), ); } protected function getURI($path) { return '/diffusion/commit/'.$path; } protected function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['active'] = pht('Active Audits'); $names['authored'] = pht('Authored'); $names['audited'] = pht('Audited'); } $names['all'] = pht('All Commits'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer = $this->requireViewer(); $viewer_phid = $viewer->getPHID(); switch ($query_key) { case 'all': return $query; case 'active': $bucket_key = DiffusionCommitRequiredActionResultBucket::BUCKETKEY; $open = PhabricatorAuditCommitStatusConstants::getOpenStatusConstants(); $query ->setParameter('responsiblePHIDs', array($viewer_phid)) ->setParameter('statuses', $open) - ->setParameter('bucket', $bucket_key); + ->setParameter('bucket', $bucket_key) + ->setParameter('unreachable', false); return $query; case 'authored': $query ->setParameter('authorPHIDs', array($viewer_phid)); return $query; case 'audited': $query ->setParameter('auditorPHIDs', array($viewer_phid)); return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $commits, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($commits, 'PhabricatorRepositoryCommit'); $viewer = $this->requireViewer(); $bucket = $this->getResultBucket($query); $template = id(new PhabricatorAuditListView()) ->setViewer($viewer) ->setShowDrafts(true); $views = array(); if ($bucket) { $bucket->setViewer($viewer); try { $groups = $bucket->newResultGroups($query, $commits); foreach ($groups as $group) { $views[] = id(clone $template) ->setHeader($group->getName()) ->setNoDataString($group->getNoDataString()) ->setCommits($group->getObjects()); } } catch (Exception $ex) { $this->addError($ex->getMessage()); } } else { $views[] = id(clone $template) ->setCommits($commits) ->setNoDataString(pht('No matching commits.')); } if (count($views) == 1) { $list = head($views)->buildList(); } else { $list = $views; } $result = new PhabricatorApplicationSearchResultView(); $result->setContent($list); return $result; } protected function getNewUserBody() { $view = id(new PHUIBigInfoView()) ->setIcon('fa-check-circle-o') ->setTitle(pht('Welcome to Audit')) ->setDescription( pht('Post-commit code review and auditing. Audits you are assigned '. 'to will appear here.')); return $view; } } diff --git a/src/applications/audit/view/PhabricatorAuditListView.php b/src/applications/audit/view/PhabricatorAuditListView.php index 9207230e7c..e249406724 100644 --- a/src/applications/audit/view/PhabricatorAuditListView.php +++ b/src/applications/audit/view/PhabricatorAuditListView.php @@ -1,181 +1,182 @@ noDataString = $no_data_string; return $this; } public function getNoDataString() { return $this->noDataString; } public function setHeader($header) { $this->header = $header; return $this; } public function getHeader() { return $this->header; } public function setShowDrafts($show_drafts) { $this->showDrafts = $show_drafts; return $this; } public function getShowDrafts() { return $this->showDrafts; } /** * These commits should have both commit data and audit requests attached. */ public function setCommits(array $commits) { assert_instances_of($commits, 'PhabricatorRepositoryCommit'); $this->commits = mpull($commits, null, 'getPHID'); return $this; } public function getCommits() { return $this->commits; } private function getCommitDescription($phid) { if ($this->commits === null) { return pht('(Unknown Commit)'); } $commit = idx($this->commits, $phid); if (!$commit) { return pht('(Unknown Commit)'); } $summary = $commit->getCommitData()->getSummary(); if (strlen($summary)) { return $summary; } // No summary, so either this is still impoting or just has an empty // commit message. if (!$commit->isImported()) { return pht('(Importing Commit...)'); } else { return pht('(Untitled Commit)'); } } public function render() { $list = $this->buildList(); $list->setFlush(true); return $list->render(); } public function buildList() { $viewer = $this->getViewer(); $rowc = array(); $phids = array(); foreach ($this->getCommits() as $commit) { $phids[] = $commit->getPHID(); foreach ($commit->getAudits() as $audit) { $phids[] = $audit->getAuditorPHID(); } $author_phid = $commit->getAuthorPHID(); if ($author_phid) { $phids[] = $author_phid; } } $handles = $viewer->loadHandles($phids); $show_drafts = $this->getShowDrafts(); $draft_icon = id(new PHUIIconView()) ->setIcon('fa-comment yellow') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Unsubmitted Comments'), )); $list = new PHUIObjectItemListView(); foreach ($this->commits as $commit) { $commit_phid = $commit->getPHID(); $commit_handle = $handles[$commit_phid]; $committed = null; $commit_name = $commit_handle->getName(); $commit_link = $commit_handle->getURI(); $commit_desc = $this->getCommitDescription($commit_phid); $committed = phabricator_datetime($commit->getEpoch(), $viewer); $status = $commit->getAuditStatus(); $status_text = PhabricatorAuditCommitStatusConstants::getStatusName($status); $status_color = PhabricatorAuditCommitStatusConstants::getStatusColor($status); $status_icon = PhabricatorAuditCommitStatusConstants::getStatusIcon($status); $author_phid = $commit->getAuthorPHID(); if ($author_phid) { $author_name = $handles[$author_phid]->renderLink(); } else { $author_name = $commit->getCommitData()->getAuthorName(); } $item = id(new PHUIObjectItemView()) ->setObjectName($commit_name) ->setHeader($commit_desc) ->setHref($commit_link) + ->setDisabled($commit->isUnreachable()) ->addByline(pht('Author: %s', $author_name)) ->addIcon('none', $committed); if ($show_drafts) { if ($commit->getHasDraft($viewer)) { $item->addAttribute($draft_icon); } } $audits = $commit->getAudits(); $auditor_phids = mpull($audits, 'getAuditorPHID'); if ($auditor_phids) { $auditor_list = $handles->newSublist($auditor_phids) ->renderList() ->setAsInline(true); } else { $auditor_list = phutil_tag('em', array(), pht('None')); } $item->addAttribute(pht('Auditors: %s', $auditor_list)); if ($status_color) { $item->setStatusIcon($status_icon.' '.$status_color, $status_text); } $list->addItem($item); } if ($this->noDataString) { $list->setNoDataString($this->noDataString); } if ($this->header) { $list->setHeader($this->header); } return $list; } } diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index 40fb5a64f6..1521f5770c 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -1,625 +1,646 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $phids) { $this->authorPHIDs = $phids; return $this; } /** * Load commits by partial or full identifiers, e.g. "rXab82393", "rX1234", * or "a9caf12". When an identifier matches multiple commits, they will all * be returned; callers should be prepared to deal with more results than * they queried for. */ public function withIdentifiers(array $identifiers) { // Some workflows (like blame lookups) can pass in large numbers of // duplicate identifiers. We only care about unique identifiers, so // get rid of duplicates immediately. $identifiers = array_fuse($identifiers); $this->identifiers = $identifiers; return $this; } /** * Look up commits in a specific repository. This is a shorthand for calling * @{method:withDefaultRepository} and @{method:withRepositoryIDs}. */ public function withRepository(PhabricatorRepository $repository) { $this->withDefaultRepository($repository); $this->withRepositoryIDs(array($repository->getID())); return $this; } /** * Look up commits in a specific repository. Prefer * @{method:withRepositoryIDs}; the underyling table is keyed by ID such * that this method requires a separate initial query to map PHID to ID. */ public function withRepositoryPHIDs(array $phids) { $this->repositoryPHIDs = $phids; return $this; } /** * If a default repository is provided, ambiguous commit identifiers will * be assumed to belong to the default repository. * * For example, "r123" appearing in a commit message in repository X is * likely to be unambiguously "rX123". Normally the reference would be * considered ambiguous, but if you provide a default repository it will * be correctly resolved. */ public function withDefaultRepository(PhabricatorRepository $repository) { $this->defaultRepository = $repository; return $this; } public function withRepositoryIDs(array $repository_ids) { $this->repositoryIDs = $repository_ids; return $this; } public function needCommitData($need) { $this->needCommitData = $need; return $this; } public function needDrafts($need) { $this->needDrafts = $need; return $this; } public function needAuditRequests($need) { $this->needAuditRequests = $need; return $this; } public function withAuditIDs(array $ids) { $this->auditIDs = $ids; return $this; } public function withAuditorPHIDs(array $auditor_phids) { $this->auditorPHIDs = $auditor_phids; return $this; } public function withResponsiblePHIDs(array $responsible_phids) { $this->responsiblePHIDs = $responsible_phids; return $this; } public function withPackagePHIDs(array $package_phids) { $this->packagePHIDs = $package_phids; return $this; } + public function withUnreachable($unreachable) { + $this->unreachable = $unreachable; + return $this; + } + public function withStatuses(array $statuses) { $this->statuses = $statuses; return $this; } public function withEpochRange($min, $max) { $this->epochMin = $min; $this->epochMax = $max; return $this; } public function withImporting($importing) { $this->importing = $importing; return $this; } public function getIdentifierMap() { if ($this->identifierMap === null) { throw new Exception( pht( 'You must %s the query before accessing the identifier map.', 'execute()')); } return $this->identifierMap; } protected function getPrimaryTableAlias() { return 'commit'; } protected function willExecute() { if ($this->identifierMap === null) { $this->identifierMap = array(); } } public function newResultObject() { return new PhabricatorRepositoryCommit(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $commits) { $repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID'); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withIDs($repository_ids) ->execute(); $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; $result = array(); foreach ($commits as $key => $commit) { $repo = idx($repos, $commit->getRepositoryID()); if ($repo) { $commit->attachRepository($repo); } else { $this->didRejectResult($commit); unset($commits[$key]); continue; } // Build the identifierMap if ($this->identifiers !== null) { $ids = $this->identifiers; $prefixes = array( 'r'.$commit->getRepository()->getCallsign(), 'r'.$commit->getRepository()->getCallsign().':', 'R'.$commit->getRepository()->getID().':', '', // No prefix is valid too and will only match the commitIdentifier ); $suffix = $commit->getCommitIdentifier(); if ($commit->getRepository()->isSVN()) { foreach ($prefixes as $prefix) { if (isset($ids[$prefix.$suffix])) { $result[$prefix.$suffix][] = $commit; } } } else { // This awkward construction is so we can link the commits up in O(N) // time instead of O(N^2). for ($ii = $min_qualified; $ii <= strlen($suffix); $ii++) { $part = substr($suffix, 0, $ii); foreach ($prefixes as $prefix) { if (isset($ids[$prefix.$part])) { $result[$prefix.$part][] = $commit; } } } } } } if ($result) { foreach ($result as $identifier => $matching_commits) { if (count($matching_commits) == 1) { $result[$identifier] = head($matching_commits); } else { // This reference is ambiguous (it matches more than one commit) so // don't link it. unset($result[$identifier]); } } $this->identifierMap += $result; } return $commits; } protected function didFilterPage(array $commits) { $viewer = $this->getViewer(); if ($this->needCommitData) { $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( 'commitID in (%Ld)', mpull($commits, 'getID')); $data = mpull($data, null, 'getCommitID'); foreach ($commits as $commit) { $commit_data = idx($data, $commit->getID()); if (!$commit_data) { $commit_data = new PhabricatorRepositoryCommitData(); } $commit->attachCommitData($commit_data); } } if ($this->needAuditRequests) { $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere( 'commitPHID IN (%Ls)', mpull($commits, 'getPHID')); $requests = mgroup($requests, 'getCommitPHID'); foreach ($commits as $commit) { $audit_requests = idx($requests, $commit->getPHID(), array()); $commit->attachAudits($audit_requests); foreach ($audit_requests as $audit_request) { $audit_request->attachCommit($commit); } } } if ($this->needDrafts) { PhabricatorDraftEngine::attachDrafts( $viewer, $commits); } return $commits; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->repositoryPHIDs !== null) { $map_repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withPHIDs($this->repositoryPHIDs) ->execute(); if (!$map_repositories) { throw new PhabricatorEmptyQueryException(); } $repository_ids = mpull($map_repositories, 'getID'); if ($this->repositoryIDs !== null) { $repository_ids = array_merge($repository_ids, $this->repositoryIDs); } $this->withRepositoryIDs($repository_ids); } if ($this->ids !== null) { $where[] = qsprintf( $conn, 'commit.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'commit.phid IN (%Ls)', $this->phids); } if ($this->repositoryIDs !== null) { $where[] = qsprintf( $conn, 'commit.repositoryID IN (%Ld)', $this->repositoryIDs); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, 'commit.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->epochMin !== null) { $where[] = qsprintf( $conn, 'commit.epoch >= %d', $this->epochMin); } if ($this->epochMax !== null) { $where[] = qsprintf( $conn, 'commit.epoch <= %d', $this->epochMax); } if ($this->importing !== null) { if ($this->importing) { $where[] = qsprintf( $conn, '(commit.importStatus & %d) != %d', PhabricatorRepositoryCommit::IMPORTED_ALL, PhabricatorRepositoryCommit::IMPORTED_ALL); } else { $where[] = qsprintf( $conn, '(commit.importStatus & %d) = %d', PhabricatorRepositoryCommit::IMPORTED_ALL, PhabricatorRepositoryCommit::IMPORTED_ALL); } } if ($this->identifiers !== null) { $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH; $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; $refs = array(); $bare = array(); foreach ($this->identifiers as $identifier) { $matches = null; preg_match('/^(?:[rR]([A-Z]+:?|[0-9]+:))?(.*)$/', $identifier, $matches); $repo = nonempty(rtrim($matches[1], ':'), null); $commit_identifier = nonempty($matches[2], null); if ($repo === null) { if ($this->defaultRepository) { $repo = $this->defaultRepository->getPHID(); } } if ($repo === null) { if (strlen($commit_identifier) < $min_unqualified) { continue; } $bare[] = $commit_identifier; } else { $refs[] = array( 'repository' => $repo, 'identifier' => $commit_identifier, ); } } $sql = array(); foreach ($bare as $identifier) { $sql[] = qsprintf( $conn, '(commit.commitIdentifier LIKE %> AND '. 'LENGTH(commit.commitIdentifier) = 40)', $identifier); } if ($refs) { $repositories = ipull($refs, 'repository'); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withIdentifiers($repositories); $repos->execute(); $repos = $repos->getIdentifierMap(); foreach ($refs as $key => $ref) { $repo = idx($repos, $ref['repository']); if (!$repo) { continue; } if ($repo->isSVN()) { if (!ctype_digit((string)$ref['identifier'])) { continue; } $sql[] = qsprintf( $conn, '(commit.repositoryID = %d AND commit.commitIdentifier = %s)', $repo->getID(), // NOTE: Because the 'commitIdentifier' column is a string, MySQL // ignores the index if we hand it an integer. Hand it a string. // See T3377. (int)$ref['identifier']); } else { if (strlen($ref['identifier']) < $min_qualified) { continue; } $identifier = $ref['identifier']; if (strlen($identifier) == 40) { // MySQL seems to do slightly better with this version if the // clause, so issue it if we have a full commit hash. $sql[] = qsprintf( $conn, '(commit.repositoryID = %d AND commit.commitIdentifier = %s)', $repo->getID(), $identifier); } else { $sql[] = qsprintf( $conn, '(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', $repo->getID(), $identifier); } } } } if (!$sql) { // If we discarded all possible identifiers (e.g., they all referenced // bogus repositories or were all too short), make sure the query finds // nothing. throw new PhabricatorEmptyQueryException( pht('No commit identifiers.')); } $where[] = '('.implode(' OR ', $sql).')'; } if ($this->auditIDs !== null) { $where[] = qsprintf( $conn, 'auditor.id IN (%Ld)', $this->auditIDs); } if ($this->auditorPHIDs !== null) { $where[] = qsprintf( $conn, 'auditor.auditorPHID IN (%Ls)', $this->auditorPHIDs); } if ($this->responsiblePHIDs !== null) { $where[] = qsprintf( $conn, '(audit.auditorPHID IN (%Ls) OR commit.authorPHID IN (%Ls))', $this->responsiblePHIDs, $this->responsiblePHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, 'commit.auditStatus IN (%Ls)', $this->statuses); } if ($this->packagePHIDs !== null) { $where[] = qsprintf( $conn, 'package.dst IN (%Ls)', $this->packagePHIDs); } + if ($this->unreachable !== null) { + if ($this->unreachable) { + $where[] = qsprintf( + $conn, + '(commit.importStatus & %d) = %d', + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); + } else { + $where[] = qsprintf( + $conn, + '(commit.importStatus & %d) = 0', + PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); + } + } + return $where; } protected function didFilterResults(array $filtered) { if ($this->identifierMap) { foreach ($this->identifierMap as $name => $commit) { if (isset($filtered[$commit->getPHID()])) { unset($this->identifierMap[$name]); } } } } private function shouldJoinAuditor() { return ($this->auditIDs || $this->auditorPHIDs); } private function shouldJoinAudit() { return (bool)$this->responsiblePHIDs; } private function shouldJoinOwners() { return (bool)$this->packagePHIDs; } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $join = parent::buildJoinClauseParts($conn); $audit_request = new PhabricatorRepositoryAuditRequest(); if ($this->shouldJoinAuditor()) { $join[] = qsprintf( $conn, 'JOIN %T auditor ON commit.phid = auditor.commitPHID', $audit_request->getTableName()); } if ($this->shouldJoinAudit()) { $join[] = qsprintf( $conn, 'LEFT JOIN %T audit ON commit.phid = audit.commitPHID', $audit_request->getTableName()); } if ($this->shouldJoinOwners()) { $join[] = qsprintf( $conn, 'JOIN %T package ON commit.phid = package.src AND package.type = %s', PhabricatorEdgeConfig::TABLE_NAME_EDGE, DiffusionCommitHasPackageEdgeType::EDGECONST); } return $join; } protected function shouldGroupQueryResultRows() { if ($this->shouldJoinAuditor()) { return true; } if ($this->shouldJoinAudit()) { return true; } if ($this->shouldJoinOwners()) { return true; } return parent::shouldGroupQueryResultRows(); } public function getQueryApplicationClass() { return 'PhabricatorDiffusionApplication'; } public function getOrderableColumns() { return parent::getOrderableColumns() + array( 'epoch' => array( 'table' => $this->getPrimaryTableAlias(), 'column' => 'epoch', 'type' => 'int', 'reverse' => false, ), ); } protected function getPagingValueMap($cursor, array $keys) { $commit = $this->loadCursorObject($cursor); return array( 'id' => $commit->getID(), 'epoch' => $commit->getEpoch(), ); } public function getBuiltinOrders() { $parent = parent::getBuiltinOrders(); // Rename the default ID-based orders. $parent['importnew'] = array( 'name' => pht('Import Date (Newest First)'), ) + $parent['newest']; $parent['importold'] = array( 'name' => pht('Import Date (Oldest First)'), ) + $parent['oldest']; return array( 'newest' => array( 'vector' => array('epoch', 'id'), 'name' => pht('Commit Date (Newest First)'), ), 'oldest' => array( 'vector' => array('-epoch', '-id'), 'name' => pht('Commit Date (Oldest First)'), ), ) + $parent; } } diff --git a/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php b/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php index 405e1ecd27..41840bcb72 100644 --- a/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php +++ b/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php @@ -1,187 +1,191 @@ objects = $objects; $phids = $query->getEvaluatedParameter('responsiblePHIDs', array()); if (!$phids) { throw new Exception( pht( 'You can not bucket results by required action without '. 'specifying "Responsible Users".')); } $phids = array_fuse($phids); $groups = array(); $groups[] = $this->newGroup() ->setName(pht('Needs Attention')) ->setNoDataString(pht('None of your commits have active concerns.')) ->setObjects($this->filterConcernRaised($phids)); $groups[] = $this->newGroup() ->setName(pht('Needs Verification')) ->setNoDataString(pht('No commits are awaiting your verification.')) ->setObjects($this->filterNeedsVerification($phids)); $groups[] = $this->newGroup() ->setName(pht('Ready to Audit')) ->setNoDataString(pht('No commits are waiting for you to audit them.')) ->setObjects($this->filterShouldAudit($phids)); $groups[] = $this->newGroup() ->setName(pht('Waiting on Authors')) ->setNoDataString(pht('None of your audits are waiting on authors.')) ->setObjects($this->filterWaitingOnAuthors($phids)); $groups[] = $this->newGroup() ->setName(pht('Waiting on Auditors')) ->setNoDataString(pht('None of your commits are waiting on audit.')) ->setObjects($this->filterWaitingOnAuditors($phids)); // Because you can apply these buckets to queries which include revisions // that have been closed, add an "Other" bucket if we still have stuff // that didn't get filtered into any of the previous buckets. if ($this->objects) { $groups[] = $this->newGroup() ->setName(pht('Other Commits')) ->setObjects($this->objects); } return $groups; } private function filterConcernRaised(array $phids) { $results = array(); $objects = $this->objects; $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; foreach ($objects as $key => $object) { if (empty($phids[$object->getAuthorPHID()])) { continue; } if ($object->getAuditStatus() != $status_concern) { continue; } $results[$key] = $object; unset($this->objects[$key]); } return $results; } private function filterNeedsVerification(array $phids) { $results = array(); $objects = $this->objects; $status_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION; $has_concern = array( PhabricatorAuditStatusConstants::CONCERNED, ); $has_concern = array_fuse($has_concern); foreach ($objects as $key => $object) { if (isset($phids[$object->getAuthorPHID()])) { continue; } if ($object->getAuditStatus() != $status_verify) { continue; } if (!$this->hasAuditorsWithStatus($object, $phids, $has_concern)) { continue; } $results[$key] = $object; unset($this->objects[$key]); } return $results; } private function filterShouldAudit(array $phids) { $results = array(); $objects = $this->objects; $should_audit = array( PhabricatorAuditStatusConstants::AUDIT_REQUIRED, PhabricatorAuditStatusConstants::AUDIT_REQUESTED, ); $should_audit = array_fuse($should_audit); foreach ($objects as $key => $object) { + if (isset($phids[$object->getAuthorPHID()])) { + continue; + } + if (!$this->hasAuditorsWithStatus($object, $phids, $should_audit)) { continue; } $results[$key] = $object; unset($this->objects[$key]); } return $results; } private function filterWaitingOnAuthors(array $phids) { $results = array(); $objects = $this->objects; $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; foreach ($objects as $key => $object) { if ($object->getAuditStatus() != $status_concern) { continue; } if (isset($phids[$object->getAuthorPHID()])) { continue; } $results[$key] = $object; unset($this->objects[$key]); } return $results; } private function filterWaitingOnAuditors(array $phids) { $results = array(); $objects = $this->objects; $status_waiting = array( PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT, PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION, PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED, ); $status_waiting = array_fuse($status_waiting); foreach ($objects as $key => $object) { if (empty($status_waiting[$object->getAuditStatus()])) { continue; } $results[$key] = $object; unset($this->objects[$key]); } return $results; } }