@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.

Support "none()" in Differential to find revisions with no (un-resigned) reviewers

Summary:
Ref T13289. In Maniphest, you can currently search for "Owner: none()" to find tasks with no owner, but there's no way to search for "Reviewers: none()" in Differential right now.

Add support for this, since it's consistent and reasonable and doesn't seem too weird or niche.

Test Plan: Searched for "Reviewers: none()", found revisions with no reviewers. Searched for "Reviewers: alice, none()", "Reviewers: alice", and "Reviewers: <no constraint>" and got sensible results.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13289

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

+177 -3
+4
src/__phutil_library_map__.php
··· 538 538 'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php', 539 539 'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php', 540 540 'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php', 541 + 'DifferentialNoReviewersDatasource' => 'applications/differential/typeahead/DifferentialNoReviewersDatasource.php', 541 542 'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php', 542 543 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 543 544 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', ··· 561 562 'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php', 562 563 'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php', 563 564 'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php', 565 + 'DifferentialReviewerFunctionDatasource' => 'applications/differential/typeahead/DifferentialReviewerFunctionDatasource.php', 564 566 'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php', 565 567 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php', 566 568 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php', ··· 6197 6199 'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension', 6198 6200 'DifferentialMailView' => 'Phobject', 6199 6201 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', 6202 + 'DifferentialNoReviewersDatasource' => 'PhabricatorTypeaheadDatasource', 6200 6203 'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector', 6201 6204 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 6202 6205 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', ··· 6220 6223 'DifferentialReviewer' => 'DifferentialDAO', 6221 6224 'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 6222 6225 'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType', 6226 + 'DifferentialReviewerFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 6223 6227 'DifferentialReviewerStatus' => 'Phobject', 6224 6228 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction', 6225 6229 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
+58 -2
src/applications/differential/query/DifferentialRevisionQuery.php
··· 26 26 private $isOpen; 27 27 private $createdEpochMin; 28 28 private $createdEpochMax; 29 + private $noReviewers; 29 30 30 31 const ORDER_MODIFIED = 'order-modified'; 31 32 const ORDER_CREATED = 'order-created'; ··· 98 99 * @task config 99 100 */ 100 101 public function withReviewers(array $reviewer_phids) { 101 - $this->reviewers = $reviewer_phids; 102 + if ($reviewer_phids === array()) { 103 + throw new Exception( 104 + pht( 105 + 'Empty "withReviewers()" constraint is invalid. Provide one or '. 106 + 'more values, or remove the constraint.')); 107 + } 108 + 109 + $with_none = false; 110 + 111 + foreach ($reviewer_phids as $key => $phid) { 112 + switch ($phid) { 113 + case DifferentialNoReviewersDatasource::FUNCTION_TOKEN: 114 + $with_none = true; 115 + unset($reviewer_phids[$key]); 116 + break; 117 + default: 118 + break; 119 + } 120 + } 121 + 122 + $this->noReviewers = $with_none; 123 + if ($reviewer_phids) { 124 + $this->reviewers = array_values($reviewer_phids); 125 + } 126 + 102 127 return $this; 103 128 } 104 129 ··· 572 597 if ($this->reviewers) { 573 598 $joins[] = qsprintf( 574 599 $conn, 575 - 'JOIN %T reviewer ON reviewer.revisionPHID = r.phid 600 + 'LEFT JOIN %T reviewer ON reviewer.revisionPHID = r.phid 576 601 AND reviewer.reviewerStatus != %s 577 602 AND reviewer.reviewerPHID in (%Ls)', 578 603 id(new DifferentialReviewer())->getTableName(), 579 604 DifferentialReviewerStatus::STATUS_RESIGNED, 580 605 $this->reviewers); 606 + } 607 + 608 + if ($this->noReviewers) { 609 + $joins[] = qsprintf( 610 + $conn, 611 + 'LEFT JOIN %T no_reviewer ON no_reviewer.revisionPHID = r.phid 612 + AND no_reviewer.reviewerStatus != %s', 613 + id(new DifferentialReviewer())->getTableName(), 614 + DifferentialReviewerStatus::STATUS_RESIGNED); 581 615 } 582 616 583 617 if ($this->draftAuthors) { ··· 715 749 $statuses); 716 750 } 717 751 752 + $reviewer_subclauses = array(); 753 + 754 + if ($this->noReviewers) { 755 + $reviewer_subclauses[] = qsprintf( 756 + $conn, 757 + 'no_reviewer.reviewerPHID IS NULL'); 758 + } 759 + 760 + if ($this->reviewers) { 761 + $reviewer_subclauses[] = qsprintf( 762 + $conn, 763 + 'reviewer.reviewerPHID IS NOT NULL'); 764 + } 765 + 766 + if ($reviewer_subclauses) { 767 + $where[] = qsprintf($conn, '%LO', $reviewer_subclauses); 768 + } 769 + 718 770 $where[] = $this->buildWhereClauseParts($conn); 719 771 720 772 return $this->formatWhereClause($conn, $where); ··· 732 784 $this->reviewers); 733 785 734 786 if (count($join_triggers) > 1) { 787 + return true; 788 + } 789 + 790 + if ($this->noReviewers) { 735 791 return true; 736 792 } 737 793
+1 -1
src/applications/differential/query/DifferentialRevisionSearchEngine.php
··· 73 73 ->setLabel(pht('Reviewers')) 74 74 ->setKey('reviewerPHIDs') 75 75 ->setAliases(array('reviewer', 'reviewers', 'reviewerPHID')) 76 - ->setDatasource(new DiffusionAuditorFunctionDatasource()) 76 + ->setDatasource(new DifferentialReviewerFunctionDatasource()) 77 77 ->setDescription( 78 78 pht('Find revisions with specific reviewers.')), 79 79 id(new PhabricatorSearchDatasourceField())
+78
src/applications/differential/typeahead/DifferentialNoReviewersDatasource.php
··· 1 + <?php 2 + 3 + final class DifferentialNoReviewersDatasource 4 + extends PhabricatorTypeaheadDatasource { 5 + 6 + const FUNCTION_TOKEN = 'none()'; 7 + 8 + public function getBrowseTitle() { 9 + return pht('Browse No Reviewers'); 10 + } 11 + 12 + public function getPlaceholderText() { 13 + return pht('Type "none"...'); 14 + } 15 + 16 + public function getDatasourceApplicationClass() { 17 + return 'PhabricatorDifferentialApplication'; 18 + } 19 + 20 + public function getDatasourceFunctions() { 21 + return array( 22 + 'none' => array( 23 + 'name' => pht('No Reviewers'), 24 + 'summary' => pht('Find results which have no reviewers.'), 25 + 'description' => pht( 26 + "This function includes results which have no reviewers. Use a ". 27 + "query like this to find results with no reviewers:\n\n%s\n\n". 28 + "If you combine this function with other functions, the query will ". 29 + "return results which match the other selectors //or// have no ". 30 + "reviewers. For example, this query will find results which have ". 31 + "`alincoln` as a reviewer, and will also find results which have ". 32 + "no reviewers:". 33 + "\n\n%s", 34 + '> none()', 35 + '> alincoln, none()'), 36 + ), 37 + ); 38 + } 39 + 40 + public function loadResults() { 41 + $results = array( 42 + $this->buildNoReviewersResult(), 43 + ); 44 + return $this->filterResultsAgainstTokens($results); 45 + } 46 + 47 + protected function evaluateFunction($function, array $argv_list) { 48 + $results = array(); 49 + 50 + foreach ($argv_list as $argv) { 51 + $results[] = self::FUNCTION_TOKEN; 52 + } 53 + 54 + return $results; 55 + } 56 + 57 + public function renderFunctionTokens($function, array $argv_list) { 58 + $results = array(); 59 + foreach ($argv_list as $argv) { 60 + $results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( 61 + $this->buildNoReviewersResult()); 62 + } 63 + return $results; 64 + } 65 + 66 + private function buildNoReviewersResult() { 67 + $name = pht('No Reviewers'); 68 + 69 + return $this->newFunctionResult() 70 + ->setName($name.' none') 71 + ->setDisplayName($name) 72 + ->setIcon('fa-ban') 73 + ->setPHID('none()') 74 + ->setUnique(true) 75 + ->addAttribute(pht('Select results with no reviewers.')); 76 + } 77 + 78 + }
+26
src/applications/differential/typeahead/DifferentialReviewerFunctionDatasource.php
··· 1 + <?php 2 + 3 + final class DifferentialReviewerFunctionDatasource 4 + extends PhabricatorTypeaheadCompositeDatasource { 5 + 6 + public function getBrowseTitle() { 7 + return pht('Browse Reviewers'); 8 + } 9 + 10 + public function getPlaceholderText() { 11 + return pht('Type a user, project, package name or function...'); 12 + } 13 + 14 + public function getDatasourceApplicationClass() { 15 + return 'PhabricatorDifferentialApplication'; 16 + } 17 + 18 + public function getComponentDatasources() { 19 + return array( 20 + new PhabricatorProjectOrUserFunctionDatasource(), 21 + new PhabricatorOwnersPackageFunctionDatasource(), 22 + new DifferentialNoReviewersDatasource(), 23 + ); 24 + } 25 + 26 + }
+10
src/applications/differential/view/DifferentialRevisionListView.php
··· 60 60 $handle_phids = array(); 61 61 foreach ($this->revisions as $key => $revision) { 62 62 $reviewers = $revision->getReviewers(); 63 + 64 + // Don't show reviewers who have resigned. The "Reviewers" constraint 65 + // does not respect these reviewers and they largely don't count as 66 + // reviewers. 67 + foreach ($reviewers as $reviewer_key => $reviewer) { 68 + if ($reviewer->isResigned()) { 69 + unset($reviewers[$reviewer_key]); 70 + } 71 + } 72 + 63 73 if (count($reviewers) > $reviewer_limit) { 64 74 $reviewers = array_slice($reviewers, 0, $reviewer_limit); 65 75 $reviewer_more[$key] = true;