@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Provide bucketing for commits in Audit

Summary:
Fixes T9430. Fixes T9362. Fixes T9544. This changes the default view of Audit to work like Differential, where commits you need to audit or respond to are shown in buckets.

This is a bit messy and probably needs some followups. This stuff has changed from a compatibility viewpoint:

- The query works differently now (but in a better, modern way), so existing saved queries will need to be updated.
- I've removed the counters from the home page instead of updating them, since they're going to get wiped out by ProfileMenu soon anyway.
- When bucketed queries return too many results (more than 1,000) we now show a warning about it. This isn't greaaaat but it seems good enough for now.

Test Plan: {F2351123}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9430, T9362, T9544

Differential Revision: https://secure.phabricator.com/D17192

+421 -293
+4
src/__phutil_library_map__.php
··· 657 657 'DiffusionCommitRemarkupRuleTestCase' => 'applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php', 658 658 'DiffusionCommitRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryHeraldField.php', 659 659 'DiffusionCommitRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryProjectsHeraldField.php', 660 + 'DiffusionCommitRequiredActionResultBucket' => 'applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php', 660 661 'DiffusionCommitResignTransaction' => 'applications/diffusion/xaction/DiffusionCommitResignTransaction.php', 662 + 'DiffusionCommitResultBucket' => 'applications/diffusion/query/DiffusionCommitResultBucket.php', 661 663 'DiffusionCommitRevertedByCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php', 662 664 'DiffusionCommitRevertsCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php', 663 665 'DiffusionCommitReviewerHeraldField' => 'applications/diffusion/herald/DiffusionCommitReviewerHeraldField.php', ··· 5361 5363 'DiffusionCommitRemarkupRuleTestCase' => 'PhabricatorTestCase', 5362 5364 'DiffusionCommitRepositoryHeraldField' => 'DiffusionCommitHeraldField', 5363 5365 'DiffusionCommitRepositoryProjectsHeraldField' => 'DiffusionCommitHeraldField', 5366 + 'DiffusionCommitRequiredActionResultBucket' => 'DiffusionCommitResultBucket', 5364 5367 'DiffusionCommitResignTransaction' => 'DiffusionCommitAuditTransaction', 5368 + 'DiffusionCommitResultBucket' => 'PhabricatorSearchResultBucket', 5365 5369 'DiffusionCommitRevertedByCommitEdgeType' => 'PhabricatorEdgeType', 5366 5370 'DiffusionCommitRevertsCommitEdgeType' => 'PhabricatorEdgeType', 5367 5371 'DiffusionCommitReviewerHeraldField' => 'DiffusionCommitHeraldField',
-53
src/applications/audit/application/PhabricatorAuditApplication.php
··· 34 34 return 0.130; 35 35 } 36 36 37 - public function loadStatus(PhabricatorUser $user) { 38 - $status = array(); 39 - $limit = self::MAX_STATUS_ITEMS; 40 - 41 - $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); 42 - 43 - $query = id(new DiffusionCommitQuery()) 44 - ->setViewer($user) 45 - ->withAuthorPHIDs(array($user->getPHID())) 46 - ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN) 47 - ->setLimit($limit); 48 - $commits = $query->execute(); 49 - 50 - $count = count($commits); 51 - if ($count >= $limit) { 52 - $count_str = pht('%s+ Problem Commits', new PhutilNumber($limit - 1)); 53 - } else { 54 - $count_str = pht('%s Problem Commit(s)', new PhutilNumber($count)); 55 - } 56 - 57 - $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; 58 - $status[] = id(new PhabricatorApplicationStatusView()) 59 - ->setType($type) 60 - ->setText($count_str) 61 - ->setCount($count); 62 - 63 - $query = id(new DiffusionCommitQuery()) 64 - ->setViewer($user) 65 - ->withNeedsAuditByPHIDs($phids) 66 - ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) 67 - ->setLimit($limit); 68 - $commits = $query->execute(); 69 - 70 - $count = count($commits); 71 - if ($count >= $limit) { 72 - $count_str = pht( 73 - '%s+ Commits Awaiting Audit', 74 - new PhutilNumber($limit - 1)); 75 - } else { 76 - $count_str = pht( 77 - '%s Commit(s) Awaiting Audit', 78 - new PhutilNumber($count)); 79 - } 80 - 81 - $type = PhabricatorApplicationStatusView::TYPE_WARNING; 82 - $status[] = id(new PhabricatorApplicationStatusView()) 83 - ->setType($type) 84 - ->setText($count_str) 85 - ->setCount($count); 86 - 87 - return $status; 88 - } 89 - 90 37 }
+41 -9
src/applications/audit/conduit/AuditQueryConduitAPIMethod.php
··· 2 2 3 3 final class AuditQueryConduitAPIMethod extends AuditConduitAPIMethod { 4 4 5 + const AUDIT_LEGACYSTATUS_ANY = 'audit-status-any'; 6 + const AUDIT_LEGACYSTATUS_OPEN = 'audit-status-open'; 7 + const AUDIT_LEGACYSTATUS_CONCERN = 'audit-status-concern'; 8 + const AUDIT_LEGACYSTATUS_ACCEPTED = 'audit-status-accepted'; 9 + const AUDIT_LEGACYSTATUS_PARTIAL = 'audit-status-partial'; 10 + 5 11 public function getAPIMethodName() { 6 12 return 'audit.query'; 7 13 } ··· 10 16 return pht('Query audit requests.'); 11 17 } 12 18 19 + public function getMethodStatus() { 20 + return self::METHOD_STATUS_FROZEN; 21 + } 22 + 23 + public function getMethodStatusDescription() { 24 + return pht( 25 + 'This method is frozen and will eventually be deprecated. New code '. 26 + 'should use "diffusion.commit.search" instead.'); 27 + } 28 + 13 29 protected function defineParamTypes() { 14 30 $statuses = array( 15 - DiffusionCommitQuery::AUDIT_STATUS_ANY, 16 - DiffusionCommitQuery::AUDIT_STATUS_OPEN, 17 - DiffusionCommitQuery::AUDIT_STATUS_CONCERN, 18 - DiffusionCommitQuery::AUDIT_STATUS_ACCEPTED, 19 - DiffusionCommitQuery::AUDIT_STATUS_PARTIAL, 31 + self::AUDIT_LEGACYSTATUS_ANY, 32 + self::AUDIT_LEGACYSTATUS_OPEN, 33 + self::AUDIT_LEGACYSTATUS_CONCERN, 34 + self::AUDIT_LEGACYSTATUS_ACCEPTED, 35 + self::AUDIT_LEGACYSTATUS_PARTIAL, 20 36 ); 21 37 $status_const = $this->formatStringConstants($statuses); 22 38 ··· 50 66 $query->withPHIDs($commit_phids); 51 67 } 52 68 53 - $status = $request->getValue( 54 - 'status', 55 - DiffusionCommitQuery::AUDIT_STATUS_ANY); 56 - $query->withAuditStatus($status); 69 + $status_map = array( 70 + self::AUDIT_LEGACYSTATUS_OPEN => array( 71 + PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT, 72 + PhabricatorAuditCommitStatusConstants::CONCERN_RAISED, 73 + ), 74 + self::AUDIT_LEGACYSTATUS_CONCERN => array( 75 + PhabricatorAuditCommitStatusConstants::CONCERN_RAISED, 76 + ), 77 + self::AUDIT_LEGACYSTATUS_ACCEPTED => array( 78 + PhabricatorAuditCommitStatusConstants::CONCERN_ACCEPTED, 79 + ), 80 + self::AUDIT_LEGACYSTATUS_PARTIAL => array( 81 + PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED, 82 + ), 83 + ); 84 + 85 + $status = $request->getValue('status'); 86 + if (isset($status_map[$status])) { 87 + $query->withStatuses($status_map[$status]); 88 + } 57 89 58 90 // NOTE: These affect the number of commits identified, which is sort of 59 91 // reasonable but means the method may return an arbitrary number of
+1
src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php
··· 28 28 return array( 29 29 self::CONCERN_RAISED, 30 30 self::NEEDS_AUDIT, 31 + self::PARTIALLY_AUDITED, 31 32 ); 32 33 } 33 34
+7
src/applications/audit/constants/PhabricatorAuditStatusConstants.php
··· 28 28 return $map; 29 29 } 30 30 31 + public static function getActionRequiredStatusConstants() { 32 + return array( 33 + self::AUDIT_REQUIRED, 34 + self::AUDIT_REQUESTED, 35 + ); 36 + } 37 + 31 38 public static function getStatusName($code) { 32 39 return idx(self::getStatusNameMap(), $code, pht('Unknown')); 33 40 }
+1 -4
src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php
··· 67 67 $ids = $this->parseList($args->getArg('ids')); 68 68 69 69 $status = $args->getArg('status'); 70 - if (!$status) { 71 - $status = DiffusionCommitQuery::AUDIT_STATUS_OPEN; 72 - } 73 70 74 71 $min_date = $this->loadDate($args->getArg('min-commit-date')); 75 72 $max_date = $this->loadDate($args->getArg('max-commit-date')); ··· 85 82 ->needAuditRequests(true); 86 83 87 84 if ($status) { 88 - $query->withAuditStatus($status); 85 + $query->withStatuses(array($status)); 89 86 } 90 87 91 88 $id_map = array();
+76 -74
src/applications/audit/query/PhabricatorCommitSearchEngine.php
··· 17 17 ->needCommitData(true); 18 18 } 19 19 20 + protected function newResultBuckets() { 21 + return DiffusionCommitResultBucket::getAllResultBuckets(); 22 + } 23 + 20 24 protected function buildQueryFromParameters(array $map) { 21 25 $query = $this->newQuery(); 22 26 23 - if ($map['needsAuditByPHIDs']) { 24 - $query->withNeedsAuditByPHIDs($map['needsAuditByPHIDs']); 27 + if ($map['responsiblePHIDs']) { 28 + $query->withResponsiblePHIDs($map['responsiblePHIDs']); 25 29 } 26 30 27 31 if ($map['auditorPHIDs']) { 28 32 $query->withAuditorPHIDs($map['auditorPHIDs']); 29 33 } 30 34 31 - if ($map['commitAuthorPHIDs']) { 32 - $query->withAuthorPHIDs($map['commitAuthorPHIDs']); 35 + if ($map['authorPHIDs']) { 36 + $query->withAuthorPHIDs($map['authorPHIDs']); 33 37 } 34 38 35 - if ($map['auditStatus']) { 36 - $query->withAuditStatus($map['auditStatus']); 39 + if ($map['statuses']) { 40 + $query->withStatuses($map['statuses']); 37 41 } 38 42 39 43 if ($map['repositoryPHIDs']) { ··· 46 50 protected function buildCustomSearchFields() { 47 51 return array( 48 52 id(new PhabricatorSearchDatasourceField()) 49 - ->setLabel(pht('Needs Audit By')) 50 - ->setKey('needsAuditByPHIDs') 51 - ->setAliases(array('needs', 'need')) 52 - ->setDatasource(new DiffusionAuditorFunctionDatasource()), 53 + ->setLabel(pht('Responsible Users')) 54 + ->setKey('responsiblePHIDs') 55 + ->setConduitKey('responsible') 56 + ->setAliases(array('responsible', 'responsibles', 'responsiblePHID')) 57 + ->setDatasource(new DifferentialResponsibleDatasource()), 58 + id(new PhabricatorUsersSearchField()) 59 + ->setLabel(pht('Authors')) 60 + ->setKey('authorPHIDs') 61 + ->setConduitKey('authors') 62 + ->setAliases(array('author', 'authors', 'authorPHID')), 53 63 id(new PhabricatorSearchDatasourceField()) 54 64 ->setLabel(pht('Auditors')) 55 65 ->setKey('auditorPHIDs') 56 - ->setAliases(array('auditor', 'auditors')) 66 + ->setConduitKey('auditors') 67 + ->setAliases(array('auditor', 'auditors', 'auditorPHID')) 57 68 ->setDatasource(new DiffusionAuditorFunctionDatasource()), 58 - id(new PhabricatorUsersSearchField()) 59 - ->setLabel(pht('Authors')) 60 - ->setKey('commitAuthorPHIDs') 61 - ->setAliases(array('author', 'authors')), 62 - id(new PhabricatorSearchSelectField()) 69 + id(new PhabricatorSearchCheckboxesField()) 63 70 ->setLabel(pht('Audit Status')) 64 - ->setKey('auditStatus') 71 + ->setKey('statuses') 65 72 ->setAliases(array('status')) 66 - ->setOptions($this->getAuditStatusOptions()), 73 + ->setOptions(PhabricatorAuditCommitStatusConstants::getStatusNameMap()), 67 74 id(new PhabricatorSearchDatasourceField()) 68 75 ->setLabel(pht('Repositories')) 69 76 ->setKey('repositoryPHIDs') 70 - ->setAliases(array('repository', 'repositories')) 77 + ->setConduitKey('repositories') 78 + ->setAliases(array('repository', 'repositories', 'repositoryPHID')) 71 79 ->setDatasource(new DiffusionRepositoryDatasource()), 72 80 ); 73 81 } ··· 80 88 $names = array(); 81 89 82 90 if ($this->requireViewer()->isLoggedIn()) { 83 - $names['need'] = pht('Needs Audit'); 84 - $names['problem'] = pht('Problem Commits'); 85 - } 86 - 87 - $names['open'] = pht('Open Audits'); 88 - 89 - if ($this->requireViewer()->isLoggedIn()) { 90 - $names['authored'] = pht('Authored Commits'); 91 + $names['active'] = pht('Active Audits'); 92 + $names['authored'] = pht('Authored'); 93 + $names['audited'] = pht('Audited'); 91 94 } 92 95 93 96 $names['all'] = pht('All Commits'); ··· 101 104 $viewer = $this->requireViewer(); 102 105 103 106 $viewer_phid = $viewer->getPHID(); 104 - $status_open = DiffusionCommitQuery::AUDIT_STATUS_OPEN; 105 - 106 107 switch ($query_key) { 107 108 case 'all': 108 109 return $query; 109 - case 'open': 110 - $query->setParameter('auditStatus', $status_open); 111 - return $query; 112 - case 'need': 113 - $needs_tokens = array( 114 - $viewer_phid, 115 - 'projects('.$viewer_phid.')', 116 - 'packages('.$viewer_phid.')', 117 - ); 110 + case 'active': 111 + $bucket_key = DiffusionCommitRequiredActionResultBucket::BUCKETKEY; 112 + 113 + $open = PhabricatorAuditCommitStatusConstants::getOpenStatusConstants(); 118 114 119 - $query->setParameter('needsAuditByPHIDs', $needs_tokens); 120 - $query->setParameter('auditStatus', $status_open); 115 + $query 116 + ->setParameter('responsiblePHIDs', array($viewer_phid)) 117 + ->setParameter('statuses', $open) 118 + ->setParameter('bucket', $bucket_key); 121 119 return $query; 122 120 case 'authored': 123 - $query->setParameter('commitAuthorPHIDs', array($viewer->getPHID())); 121 + $query 122 + ->setParameter('authorPHIDs', array($viewer_phid)); 124 123 return $query; 125 - case 'problem': 126 - $query->setParameter('commitAuthorPHIDs', array($viewer->getPHID())); 127 - $query->setParameter( 128 - 'auditStatus', 129 - DiffusionCommitQuery::AUDIT_STATUS_CONCERN); 124 + case 'audited': 125 + $query 126 + ->setParameter('auditorPHIDs', array($viewer_phid)); 130 127 return $query; 131 128 } 132 129 133 130 return parent::buildSavedQueryFromBuiltin($query_key); 134 131 } 135 132 136 - private function getAuditStatusOptions() { 137 - return array( 138 - DiffusionCommitQuery::AUDIT_STATUS_ANY => pht('Any'), 139 - DiffusionCommitQuery::AUDIT_STATUS_OPEN => pht('Open'), 140 - DiffusionCommitQuery::AUDIT_STATUS_CONCERN => pht('Concern Raised'), 141 - DiffusionCommitQuery::AUDIT_STATUS_ACCEPTED => pht('Accepted'), 142 - DiffusionCommitQuery::AUDIT_STATUS_PARTIAL => pht('Partially Audited'), 143 - ); 144 - } 145 - 146 133 protected function renderResultList( 147 134 array $commits, 148 135 PhabricatorSavedQuery $query, 149 136 array $handles) { 137 + assert_instances_of($commits, 'PhabricatorRepositoryCommit'); 138 + $viewer = $this->requireViewer(); 150 139 151 - assert_instances_of($commits, 'PhabricatorRepositoryCommit'); 140 + $bucket = $this->getResultBucket($query); 141 + 142 + $authority_phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser( 143 + $viewer); 144 + 145 + $template = id(new PhabricatorAuditListView()) 146 + ->setViewer($viewer) 147 + ->setAuthorityPHIDs($authority_phids); 148 + 149 + $views = array(); 150 + if ($bucket) { 151 + $bucket->setViewer($viewer); 152 152 153 - $viewer = $this->requireViewer(); 154 - $nodata = pht('No matching audits.'); 155 - $view = id(new PhabricatorAuditListView()) 156 - ->setUser($viewer) 157 - ->setCommits($commits) 158 - ->setAuthorityPHIDs( 159 - PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer)) 160 - ->setNoDataString($nodata); 153 + try { 154 + $groups = $bucket->newResultGroups($query, $commits); 161 155 162 - $phids = $view->getRequiredHandlePHIDs(); 163 - if ($phids) { 164 - $handles = id(new PhabricatorHandleQuery()) 165 - ->setViewer($viewer) 166 - ->withPHIDs($phids) 167 - ->execute(); 156 + foreach ($groups as $group) { 157 + $views[] = id(clone $template) 158 + ->setHeader($group->getName()) 159 + ->setNoDataString($group->getNoDataString()) 160 + ->setCommits($group->getObjects()); 161 + } 162 + } catch (Exception $ex) { 163 + $this->addError($ex->getMessage()); 164 + } 168 165 } else { 169 - $handles = array(); 166 + $views[] = id(clone $template) 167 + ->setCommits($commits) 168 + ->setNoDataString(pht('No matching commits.')); 170 169 } 171 170 172 - $view->setHandles($handles); 173 - $list = $view->buildList(); 171 + if (count($views) == 1) { 172 + $list = head($views)->buildList(); 173 + } else { 174 + $list = $views; 175 + } 174 176 175 177 $result = new PhabricatorApplicationSearchResultView(); 176 178 $result->setContent($list);
+21 -43
src/applications/audit/view/PhabricatorAuditListView.php
··· 3 3 final class PhabricatorAuditListView extends AphrontView { 4 4 5 5 private $commits; 6 - private $handles; 7 6 private $authorityPHIDs = array(); 7 + private $header; 8 8 private $noDataString; 9 - 10 9 private $highlightedAudits; 11 - 12 - public function setHandles(array $handles) { 13 - assert_instances_of($handles, 'PhabricatorObjectHandle'); 14 - $this->handles = $handles; 15 - return $this; 16 - } 17 10 18 11 public function setAuthorityPHIDs(array $phids) { 19 12 $this->authorityPHIDs = $phids; ··· 29 22 return $this->noDataString; 30 23 } 31 24 25 + public function setHeader($header) { 26 + $this->header = $header; 27 + return $this; 28 + } 29 + 30 + public function getHeader() { 31 + return $this->header; 32 + } 33 + 32 34 /** 33 35 * These commits should have both commit data and audit requests attached. 34 36 */ ··· 42 44 return $this->commits; 43 45 } 44 46 45 - public function getRequiredHandlePHIDs() { 46 - $phids = array(); 47 - $commits = $this->getCommits(); 48 - foreach ($commits as $commit) { 49 - $phids[$commit->getPHID()] = true; 50 - $phids[$commit->getAuthorPHID()] = true; 51 - $audits = $commit->getAudits(); 52 - foreach ($audits as $audit) { 53 - $phids[$audit->getAuditorPHID()] = true; 54 - } 55 - } 56 - return array_keys($phids); 57 - } 58 - 59 - private function getHandle($phid) { 60 - $handle = idx($this->handles, $phid); 61 - if (!$handle) { 62 - throw new Exception(pht("No handle for '%s'!", $phid)); 63 - } 64 - return $handle; 65 - } 66 - 67 47 private function getCommitDescription($phid) { 68 48 if ($this->commits === null) { 69 49 return pht('(Unknown Commit)'); ··· 96 76 } 97 77 98 78 public function buildList() { 99 - $user = $this->getUser(); 100 - if (!$user) { 101 - throw new Exception( 102 - pht( 103 - 'You must %s before %s!', 104 - 'setUser()', 105 - __FUNCTION__.'()')); 106 - } 79 + $viewer = $this->getViewer(); 107 80 $rowc = array(); 81 + 82 + $handles = $viewer->loadHandles(mpull($this->commits, 'getPHID')); 108 83 109 84 $list = new PHUIObjectItemListView(); 110 85 foreach ($this->commits as $commit) { 111 86 $commit_phid = $commit->getPHID(); 112 - $commit_handle = $this->getHandle($commit_phid); 87 + $commit_handle = $handles[$commit_phid]; 113 88 $committed = null; 114 89 115 90 $commit_name = $commit_handle->getName(); 116 91 $commit_link = $commit_handle->getURI(); 117 92 $commit_desc = $this->getCommitDescription($commit_phid); 118 - $committed = phabricator_datetime($commit->getEpoch(), $user); 93 + $committed = phabricator_datetime($commit->getEpoch(), $viewer); 119 94 120 95 $audits = mpull($commit->getAudits(), null, 'getAuditorPHID'); 121 96 $auditors = array(); 122 97 $reasons = array(); 123 98 foreach ($audits as $audit) { 124 99 $auditor_phid = $audit->getAuditorPHID(); 125 - $auditors[$auditor_phid] = 126 - $this->getHandle($auditor_phid)->renderLink(); 100 + $auditors[$auditor_phid] = $viewer->renderHandle($auditor_phid); 127 101 } 128 102 $auditors = phutil_implode_html(', ', $auditors); 129 103 ··· 151 125 } 152 126 $author_phid = $commit->getAuthorPHID(); 153 127 if ($author_phid) { 154 - $author_name = $this->getHandle($author_phid)->renderLink(); 128 + $author_name = $viewer->renderHandle($author_phid); 155 129 } else { 156 130 $author_name = $commit->getCommitData()->getAuthorName(); 157 131 } ··· 178 152 179 153 if ($this->noDataString) { 180 154 $list->setNoDataString($this->noDataString); 155 + } 156 + 157 + if ($this->header) { 158 + $list->setHeader($this->header); 181 159 } 182 160 183 161 return $list;
+36 -94
src/applications/diffusion/query/DiffusionCommitQuery.php
··· 11 11 private $repositoryIDs; 12 12 private $repositoryPHIDs; 13 13 private $identifierMap; 14 + private $responsiblePHIDs; 15 + private $statuses; 14 16 15 17 private $needAuditRequests; 16 18 private $auditIDs; 17 19 private $auditorPHIDs; 18 - private $needsAuditByPHIDs; 19 - private $auditStatus; 20 20 private $epochMin; 21 21 private $epochMax; 22 22 private $importing; 23 - 24 - const AUDIT_STATUS_ANY = 'audit-status-any'; 25 - const AUDIT_STATUS_OPEN = 'audit-status-open'; 26 - const AUDIT_STATUS_CONCERN = 'audit-status-concern'; 27 - const AUDIT_STATUS_ACCEPTED = 'audit-status-accepted'; 28 - const AUDIT_STATUS_PARTIAL = 'audit-status-partial'; 29 23 30 24 private $needCommitData; 31 25 ··· 119 113 return $this; 120 114 } 121 115 122 - public function withNeedsAuditByPHIDs(array $needs_phids) { 123 - $this->needsAuditByPHIDs = $needs_phids; 116 + public function withResponsiblePHIDs(array $responsible_phids) { 117 + $this->responsiblePHIDs = $responsible_phids; 118 + return $this; 119 + } 120 + 121 + public function withStatuses(array $statuses) { 122 + $this->statuses = $statuses; 124 123 return $this; 125 124 } 126 125 127 126 public function withAuditStatus($status) { 128 - $this->auditStatus = $status; 129 - return $this; 127 + // TODO: Replace callers with `withStatuses()`. 128 + return $this->withStatuses( 129 + array( 130 + $status, 131 + )); 130 132 } 131 133 132 134 public function withEpochRange($min, $max) { ··· 251 253 } 252 254 } 253 255 254 - // TODO: This should just be `needAuditRequests`, not `shouldJoinAudits()`, 255 - // but leave that for a future diff. 256 - 257 - if ($this->needAuditRequests || $this->shouldJoinAudits()) { 256 + if ($this->needAuditRequests) { 258 257 $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere( 259 258 'commitPHID IN (%Ls)', 260 259 mpull($commits, 'getPHID')); ··· 459 458 if ($this->auditIDs !== null) { 460 459 $where[] = qsprintf( 461 460 $conn, 462 - 'audit.id IN (%Ld)', 461 + 'auditor.id IN (%Ld)', 463 462 $this->auditIDs); 464 463 } 465 464 466 465 if ($this->auditorPHIDs !== null) { 467 466 $where[] = qsprintf( 468 467 $conn, 469 - 'audit.auditorPHID IN (%Ls)', 468 + 'auditor.auditorPHID IN (%Ls)', 470 469 $this->auditorPHIDs); 471 470 } 472 471 473 - if ($this->needsAuditByPHIDs !== null) { 472 + if ($this->responsiblePHIDs !== null) { 474 473 $where[] = qsprintf( 475 474 $conn, 476 - 'needs.auditorPHID IN (%Ls)', 477 - $this->needsAuditByPHIDs); 475 + '(audit.auditorPHID IN (%Ls) OR commit.authorPHID IN (%Ls))', 476 + $this->responsiblePHIDs, 477 + $this->responsiblePHIDs); 478 478 } 479 479 480 - $status = $this->auditStatus; 481 - if ($status !== null) { 482 - switch ($status) { 483 - case self::AUDIT_STATUS_PARTIAL: 484 - $where[] = qsprintf( 485 - $conn, 486 - 'commit.auditStatus = %d', 487 - PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED); 488 - break; 489 - case self::AUDIT_STATUS_ACCEPTED: 490 - $where[] = qsprintf( 491 - $conn, 492 - 'commit.auditStatus = %d', 493 - PhabricatorAuditCommitStatusConstants::FULLY_AUDITED); 494 - break; 495 - case self::AUDIT_STATUS_CONCERN: 496 - $where[] = qsprintf( 497 - $conn, 498 - 'status.auditStatus = %s', 499 - PhabricatorAuditStatusConstants::CONCERNED); 500 - break; 501 - case self::AUDIT_STATUS_OPEN: 502 - $where[] = qsprintf( 503 - $conn, 504 - 'status.auditStatus in (%Ls)', 505 - PhabricatorAuditStatusConstants::getOpenStatusConstants()); 506 - break; 507 - case self::AUDIT_STATUS_ANY: 508 - break; 509 - default: 510 - $valid = array( 511 - self::AUDIT_STATUS_ANY, 512 - self::AUDIT_STATUS_OPEN, 513 - self::AUDIT_STATUS_CONCERN, 514 - self::AUDIT_STATUS_ACCEPTED, 515 - self::AUDIT_STATUS_PARTIAL, 516 - ); 517 - throw new Exception( 518 - pht( 519 - "Unknown audit status '%s'! Valid statuses are: %s.", 520 - $status, 521 - implode(', ', $valid))); 522 - } 480 + if ($this->statuses !== null) { 481 + $where[] = qsprintf( 482 + $conn, 483 + 'commit.auditStatus IN (%Ls)', 484 + $this->statuses); 523 485 } 524 486 525 487 return $where; ··· 535 497 } 536 498 } 537 499 538 - private function shouldJoinStatus() { 539 - return $this->auditStatus; 500 + private function shouldJoinAuditor() { 501 + return ($this->auditIDs || $this->auditorPHIDs); 540 502 } 541 503 542 - private function shouldJoinAudits() { 543 - return $this->auditIDs || $this->auditorPHIDs; 544 - } 545 - 546 - private function shouldJoinNeeds() { 547 - return $this->needsAuditByPHIDs; 504 + private function shouldJoinAudit() { 505 + return (bool)$this->responsiblePHIDs; 548 506 } 549 507 550 508 protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { 551 509 $join = parent::buildJoinClauseParts($conn); 552 510 $audit_request = new PhabricatorRepositoryAuditRequest(); 553 511 554 - if ($this->shouldJoinStatus()) { 512 + if ($this->shouldJoinAuditor()) { 555 513 $join[] = qsprintf( 556 514 $conn, 557 - 'LEFT JOIN %T status ON commit.phid = status.commitPHID', 515 + 'JOIN %T auditor ON commit.phid = auditor.commitPHID', 558 516 $audit_request->getTableName()); 559 517 } 560 518 561 - if ($this->shouldJoinAudits()) { 519 + if ($this->shouldJoinAudit()) { 562 520 $join[] = qsprintf( 563 521 $conn, 564 - 'JOIN %T audit ON commit.phid = audit.commitPHID', 522 + 'LEFT JOIN %T audit ON commit.phid = audit.commitPHID', 565 523 $audit_request->getTableName()); 566 524 } 567 525 568 - if ($this->shouldJoinNeeds()) { 569 - $join[] = qsprintf( 570 - $conn, 571 - 'JOIN %T needs ON commit.phid = needs.commitPHID 572 - AND needs.auditStatus IN (%Ls)', 573 - $audit_request->getTableName(), 574 - array( 575 - PhabricatorAuditStatusConstants::AUDIT_REQUESTED, 576 - PhabricatorAuditStatusConstants::AUDIT_REQUIRED, 577 - )); 578 - } 579 - 580 526 return $join; 581 527 } 582 528 583 529 protected function shouldGroupQueryResultRows() { 584 - if ($this->shouldJoinStatus()) { 585 - return true; 586 - } 587 - 588 - if ($this->shouldJoinAudits()) { 530 + if ($this->shouldJoinAuditor()) { 589 531 return true; 590 532 } 591 533 592 - if ($this->shouldJoinNeeds()) { 534 + if ($this->shouldJoinAudit()) { 593 535 return true; 594 536 } 595 537
+151
src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php
··· 1 + <?php 2 + 3 + final class DiffusionCommitRequiredActionResultBucket 4 + extends DiffusionCommitResultBucket { 5 + 6 + const BUCKETKEY = 'action'; 7 + 8 + private $objects; 9 + 10 + public function getResultBucketName() { 11 + return pht('Bucket by Required Action'); 12 + } 13 + 14 + protected function buildResultGroups( 15 + PhabricatorSavedQuery $query, 16 + array $objects) { 17 + 18 + $this->objects = $objects; 19 + 20 + $phids = $query->getEvaluatedParameter('responsiblePHIDs', array()); 21 + if (!$phids) { 22 + throw new Exception( 23 + pht( 24 + 'You can not bucket results by required action without '. 25 + 'specifying "Responsible Users".')); 26 + } 27 + $phids = array_fuse($phids); 28 + 29 + $groups = array(); 30 + 31 + $groups[] = $this->newGroup() 32 + ->setName(pht('Needs Attention')) 33 + ->setNoDataString(pht('None of your commits have active concerns.')) 34 + ->setObjects($this->filterConcernRaised($phids)); 35 + 36 + $groups[] = $this->newGroup() 37 + ->setName(pht('Ready to Audit')) 38 + ->setNoDataString(pht('No commits are waiting for you to audit them.')) 39 + ->setObjects($this->filterShouldAudit($phids)); 40 + 41 + $groups[] = $this->newGroup() 42 + ->setName(pht('Waiting on Authors')) 43 + ->setNoDataString(pht('None of your audits are waiting on authors.')) 44 + ->setObjects($this->filterWaitingOnAuthors($phids)); 45 + 46 + $groups[] = $this->newGroup() 47 + ->setName(pht('Waiting on Auditors')) 48 + ->setNoDataString(pht('None of your commits are waiting on audit.')) 49 + ->setObjects($this->filterWaitingOnAuditors($phids)); 50 + 51 + // Because you can apply these buckets to queries which include revisions 52 + // that have been closed, add an "Other" bucket if we still have stuff 53 + // that didn't get filtered into any of the previous buckets. 54 + if ($this->objects) { 55 + $groups[] = $this->newGroup() 56 + ->setName(pht('Other Commits')) 57 + ->setObjects($this->objects); 58 + } 59 + 60 + return $groups; 61 + } 62 + 63 + private function filterConcernRaised(array $phids) { 64 + $results = array(); 65 + $objects = $this->objects; 66 + 67 + $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; 68 + 69 + foreach ($objects as $key => $object) { 70 + if (empty($phids[$object->getAuthorPHID()])) { 71 + continue; 72 + } 73 + 74 + if ($object->getAuditStatus() != $status_concern) { 75 + continue; 76 + } 77 + 78 + $results[$key] = $object; 79 + unset($this->objects[$key]); 80 + } 81 + 82 + return $results; 83 + } 84 + 85 + private function filterShouldAudit(array $phids) { 86 + $results = array(); 87 + $objects = $this->objects; 88 + 89 + $should_audit = array( 90 + PhabricatorAuditStatusConstants::AUDIT_REQUIRED, 91 + PhabricatorAuditStatusConstants::AUDIT_REQUESTED, 92 + ); 93 + $should_audit = array_fuse($should_audit); 94 + 95 + foreach ($objects as $key => $object) { 96 + if (!$this->hasAuditorsWithStatus($object, $phids, $should_audit)) { 97 + continue; 98 + } 99 + 100 + $results[$key] = $object; 101 + unset($this->objects[$key]); 102 + } 103 + 104 + return $results; 105 + } 106 + 107 + private function filterWaitingOnAuthors(array $phids) { 108 + $results = array(); 109 + $objects = $this->objects; 110 + 111 + $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; 112 + 113 + foreach ($objects as $key => $object) { 114 + if ($object->getAuditStatus() != $status_concern) { 115 + continue; 116 + } 117 + 118 + if (isset($phids[$object->getAuthorPHID()])) { 119 + continue; 120 + } 121 + 122 + $results[$key] = $object; 123 + unset($this->objects[$key]); 124 + } 125 + 126 + return $results; 127 + } 128 + 129 + private function filterWaitingOnAuditors(array $phids) { 130 + $results = array(); 131 + $objects = $this->objects; 132 + 133 + $status_waiting = array( 134 + PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT, 135 + PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED, 136 + ); 137 + $status_waiting = array_fuse($status_waiting); 138 + 139 + foreach ($objects as $key => $object) { 140 + if (empty($status_waiting[$object->getAuditStatus()])) { 141 + continue; 142 + } 143 + 144 + $results[$key] = $object; 145 + unset($this->objects[$key]); 146 + } 147 + 148 + return $results; 149 + } 150 + 151 + }
+33
src/applications/diffusion/query/DiffusionCommitResultBucket.php
··· 1 + <?php 2 + 3 + abstract class DiffusionCommitResultBucket 4 + extends PhabricatorSearchResultBucket { 5 + 6 + public static function getAllResultBuckets() { 7 + return id(new PhutilClassMapQuery()) 8 + ->setAncestorClass(__CLASS__) 9 + ->setUniqueMethod('getResultBucketKey') 10 + ->execute(); 11 + } 12 + 13 + protected function hasAuditorsWithStatus( 14 + PhabricatorRepositoryCommit $commit, 15 + array $phids, 16 + array $statuses) { 17 + 18 + foreach ($commit->getAudits() as $audit) { 19 + if (!isset($phids[$audit->getAuditorPHID()])) { 20 + continue; 21 + } 22 + 23 + if (!isset($statuses[$audit->getAuditStatus()])) { 24 + continue; 25 + } 26 + 27 + return true; 28 + } 29 + 30 + return false; 31 + } 32 + 33 + }
+8 -11
src/applications/owners/controller/PhabricatorOwnersDetailController.php
··· 71 71 'auditorPHIDs' => $package->getPHID(), 72 72 )); 73 73 74 - $status_concern = DiffusionCommitQuery::AUDIT_STATUS_CONCERN; 74 + $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; 75 75 76 76 $attention_commits = id(new DiffusionCommitQuery()) 77 77 ->setViewer($request->getUser()) 78 78 ->withAuditorPHIDs(array($package->getPHID())) 79 - ->withAuditStatus($status_concern) 79 + ->withStatuses( 80 + array( 81 + $status_concern, 82 + )) 80 83 ->needCommitData(true) 84 + ->needAuditRequests(true) 81 85 ->setLimit(10) 82 86 ->execute(); 83 87 $view = id(new PhabricatorAuditListView()) ··· 100 104 ->setViewer($request->getUser()) 101 105 ->withAuditorPHIDs(array($package->getPHID())) 102 106 ->needCommitData(true) 103 - ->setLimit(100) 107 + ->needAuditRequests(true) 108 + ->setLimit(25) 104 109 ->execute(); 105 110 106 111 $view = id(new PhabricatorAuditListView()) ··· 119 124 ->setText(pht('View All')), 120 125 ); 121 126 122 - $phids = array(); 123 - foreach ($commit_views as $commit_view) { 124 - $phids[] = $commit_view['view']->getRequiredHandlePHIDs(); 125 - } 126 - $phids = array_mergev($phids); 127 - $handles = $this->loadViewerHandles($phids); 128 - 129 127 $commit_panels = array(); 130 128 foreach ($commit_views as $commit_view) { 131 129 $commit_panel = id(new PHUIObjectBoxView()) ··· 136 134 if (isset($commit_view['button'])) { 137 135 $commit_header->addActionLink($commit_view['button']); 138 136 } 139 - $commit_view['view']->setHandles($handles); 140 137 $commit_panel->setHeader($commit_header); 141 138 $commit_panel->appendChild($commit_view['view']); 142 139
+16 -4
src/applications/policy/filter/PhabricatorPolicyFilter.php
··· 917 917 } 918 918 919 919 private function getApplicationForPHID($phid) { 920 + static $class_map = array(); 921 + 920 922 $phid_type = phid_get_type($phid); 923 + if (!isset($class_map[$phid_type])) { 924 + $type_objects = PhabricatorPHIDType::getTypes(array($phid_type)); 925 + $type_object = idx($type_objects, $phid_type); 926 + if (!$type_object) { 927 + $class = false; 928 + } else { 929 + $class = $type_object->getPHIDTypeApplicationClass(); 930 + } 921 931 922 - $type_objects = PhabricatorPHIDType::getTypes(array($phid_type)); 923 - $type_object = idx($type_objects, $phid_type); 924 - if (!$type_object) { 932 + $class_map[$phid_type] = $class; 933 + } 934 + 935 + $class = $class_map[$phid_type]; 936 + if ($class === false) { 925 937 return null; 926 938 } 927 939 928 - return $type_object->getPHIDTypeApplicationClass(); 940 + return $class; 929 941 } 930 942 931 943 }
+25
src/applications/search/controller/PhabricatorApplicationSearchController.php
··· 239 239 $nux_view = null; 240 240 } 241 241 242 + $is_overflowing = 243 + $pager->willShowPagingControls() && 244 + $engine->getResultBucket($saved_query); 245 + 242 246 $force_overheated = $request->getBool('overheated'); 243 247 $is_overheated = $query->getIsOverheated() || $force_overheated; 244 248 ··· 265 269 if ($list->getInfoView()) { 266 270 $box->setInfoView($list->getInfoView()); 267 271 } 272 + 273 + if ($is_overflowing) { 274 + $box->appendChild($this->newOverflowingView()); 275 + } 276 + 268 277 if ($list->getContent()) { 269 278 $box->appendChild($list->getContent()); 270 279 } ··· 543 552 ->setText(pht('Use Results...')) 544 553 ->setIcon('fa-road') 545 554 ->setDropdownMenu($action_list); 555 + } 556 + 557 + private function newOverflowingView() { 558 + $message = pht( 559 + 'The query matched more than one page of results. Results are '. 560 + 'paginated before bucketing, so later pages may contain additional '. 561 + 'results in any bucket.'); 562 + 563 + return id(new PHUIInfoView()) 564 + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 565 + ->setFlush(true) 566 + ->setTitle(pht('Buckets Overflowing')) 567 + ->setErrors( 568 + array( 569 + $message, 570 + )); 546 571 } 547 572 548 573 private function newOverheatedView(array $results) {
+1 -1
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 908 908 return array(); 909 909 } 910 910 911 - protected function getResultBucket(PhabricatorSavedQuery $saved) { 911 + public function getResultBucket(PhabricatorSavedQuery $saved) { 912 912 $key = $saved->getParameter('bucket'); 913 913 if ($key == self::BUCKET_NONE) { 914 914 return null;