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
This commit is contained in:
parent
2f38695768
commit
7c1f6519e0
|
@ -538,6 +538,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php',
|
'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php',
|
||||||
'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php',
|
'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php',
|
||||||
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
|
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
|
||||||
|
'DifferentialNoReviewersDatasource' => 'applications/differential/typeahead/DifferentialNoReviewersDatasource.php',
|
||||||
'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
|
'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
|
||||||
'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
|
'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
|
||||||
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
|
||||||
|
@ -561,6 +562,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
|
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
|
||||||
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
|
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
|
||||||
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
|
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
|
||||||
|
'DifferentialReviewerFunctionDatasource' => 'applications/differential/typeahead/DifferentialReviewerFunctionDatasource.php',
|
||||||
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
|
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
|
||||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
|
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
|
||||||
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php',
|
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php',
|
||||||
|
@ -6197,6 +6199,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension',
|
'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension',
|
||||||
'DifferentialMailView' => 'Phobject',
|
'DifferentialMailView' => 'Phobject',
|
||||||
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
|
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
|
||||||
|
'DifferentialNoReviewersDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
|
'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||||
'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
|
||||||
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
|
||||||
|
@ -6220,6 +6223,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialReviewer' => 'DifferentialDAO',
|
'DifferentialReviewer' => 'DifferentialDAO',
|
||||||
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
|
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
|
||||||
|
'DifferentialReviewerFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'DifferentialReviewerStatus' => 'Phobject',
|
'DifferentialReviewerStatus' => 'Phobject',
|
||||||
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
|
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
|
||||||
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
|
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
|
||||||
|
|
|
@ -26,6 +26,7 @@ final class DifferentialRevisionQuery
|
||||||
private $isOpen;
|
private $isOpen;
|
||||||
private $createdEpochMin;
|
private $createdEpochMin;
|
||||||
private $createdEpochMax;
|
private $createdEpochMax;
|
||||||
|
private $noReviewers;
|
||||||
|
|
||||||
const ORDER_MODIFIED = 'order-modified';
|
const ORDER_MODIFIED = 'order-modified';
|
||||||
const ORDER_CREATED = 'order-created';
|
const ORDER_CREATED = 'order-created';
|
||||||
|
@ -98,7 +99,31 @@ final class DifferentialRevisionQuery
|
||||||
* @task config
|
* @task config
|
||||||
*/
|
*/
|
||||||
public function withReviewers(array $reviewer_phids) {
|
public function withReviewers(array $reviewer_phids) {
|
||||||
$this->reviewers = $reviewer_phids;
|
if ($reviewer_phids === array()) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Empty "withReviewers()" constraint is invalid. Provide one or '.
|
||||||
|
'more values, or remove the constraint.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$with_none = false;
|
||||||
|
|
||||||
|
foreach ($reviewer_phids as $key => $phid) {
|
||||||
|
switch ($phid) {
|
||||||
|
case DifferentialNoReviewersDatasource::FUNCTION_TOKEN:
|
||||||
|
$with_none = true;
|
||||||
|
unset($reviewer_phids[$key]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->noReviewers = $with_none;
|
||||||
|
if ($reviewer_phids) {
|
||||||
|
$this->reviewers = array_values($reviewer_phids);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,7 +597,7 @@ final class DifferentialRevisionQuery
|
||||||
if ($this->reviewers) {
|
if ($this->reviewers) {
|
||||||
$joins[] = qsprintf(
|
$joins[] = qsprintf(
|
||||||
$conn,
|
$conn,
|
||||||
'JOIN %T reviewer ON reviewer.revisionPHID = r.phid
|
'LEFT JOIN %T reviewer ON reviewer.revisionPHID = r.phid
|
||||||
AND reviewer.reviewerStatus != %s
|
AND reviewer.reviewerStatus != %s
|
||||||
AND reviewer.reviewerPHID in (%Ls)',
|
AND reviewer.reviewerPHID in (%Ls)',
|
||||||
id(new DifferentialReviewer())->getTableName(),
|
id(new DifferentialReviewer())->getTableName(),
|
||||||
|
@ -580,6 +605,15 @@ final class DifferentialRevisionQuery
|
||||||
$this->reviewers);
|
$this->reviewers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->noReviewers) {
|
||||||
|
$joins[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'LEFT JOIN %T no_reviewer ON no_reviewer.revisionPHID = r.phid
|
||||||
|
AND no_reviewer.reviewerStatus != %s',
|
||||||
|
id(new DifferentialReviewer())->getTableName(),
|
||||||
|
DifferentialReviewerStatus::STATUS_RESIGNED);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->draftAuthors) {
|
if ($this->draftAuthors) {
|
||||||
$joins[] = qsprintf(
|
$joins[] = qsprintf(
|
||||||
$conn,
|
$conn,
|
||||||
|
@ -715,6 +749,24 @@ final class DifferentialRevisionQuery
|
||||||
$statuses);
|
$statuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$reviewer_subclauses = array();
|
||||||
|
|
||||||
|
if ($this->noReviewers) {
|
||||||
|
$reviewer_subclauses[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'no_reviewer.reviewerPHID IS NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->reviewers) {
|
||||||
|
$reviewer_subclauses[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'reviewer.reviewerPHID IS NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reviewer_subclauses) {
|
||||||
|
$where[] = qsprintf($conn, '%LO', $reviewer_subclauses);
|
||||||
|
}
|
||||||
|
|
||||||
$where[] = $this->buildWhereClauseParts($conn);
|
$where[] = $this->buildWhereClauseParts($conn);
|
||||||
|
|
||||||
return $this->formatWhereClause($conn, $where);
|
return $this->formatWhereClause($conn, $where);
|
||||||
|
@ -735,6 +787,10 @@ final class DifferentialRevisionQuery
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->noReviewers) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return parent::shouldGroupQueryResultRows();
|
return parent::shouldGroupQueryResultRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ final class DifferentialRevisionSearchEngine
|
||||||
->setLabel(pht('Reviewers'))
|
->setLabel(pht('Reviewers'))
|
||||||
->setKey('reviewerPHIDs')
|
->setKey('reviewerPHIDs')
|
||||||
->setAliases(array('reviewer', 'reviewers', 'reviewerPHID'))
|
->setAliases(array('reviewer', 'reviewers', 'reviewerPHID'))
|
||||||
->setDatasource(new DiffusionAuditorFunctionDatasource())
|
->setDatasource(new DifferentialReviewerFunctionDatasource())
|
||||||
->setDescription(
|
->setDescription(
|
||||||
pht('Find revisions with specific reviewers.')),
|
pht('Find revisions with specific reviewers.')),
|
||||||
id(new PhabricatorSearchDatasourceField())
|
id(new PhabricatorSearchDatasourceField())
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DifferentialNoReviewersDatasource
|
||||||
|
extends PhabricatorTypeaheadDatasource {
|
||||||
|
|
||||||
|
const FUNCTION_TOKEN = 'none()';
|
||||||
|
|
||||||
|
public function getBrowseTitle() {
|
||||||
|
return pht('Browse No Reviewers');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlaceholderText() {
|
||||||
|
return pht('Type "none"...');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatasourceApplicationClass() {
|
||||||
|
return 'PhabricatorDifferentialApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatasourceFunctions() {
|
||||||
|
return array(
|
||||||
|
'none' => array(
|
||||||
|
'name' => pht('No Reviewers'),
|
||||||
|
'summary' => pht('Find results which have no reviewers.'),
|
||||||
|
'description' => pht(
|
||||||
|
"This function includes results which have no reviewers. Use a ".
|
||||||
|
"query like this to find results with no reviewers:\n\n%s\n\n".
|
||||||
|
"If you combine this function with other functions, the query will ".
|
||||||
|
"return results which match the other selectors //or// have no ".
|
||||||
|
"reviewers. For example, this query will find results which have ".
|
||||||
|
"`alincoln` as a reviewer, and will also find results which have ".
|
||||||
|
"no reviewers:".
|
||||||
|
"\n\n%s",
|
||||||
|
'> none()',
|
||||||
|
'> alincoln, none()'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadResults() {
|
||||||
|
$results = array(
|
||||||
|
$this->buildNoReviewersResult(),
|
||||||
|
);
|
||||||
|
return $this->filterResultsAgainstTokens($results);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function evaluateFunction($function, array $argv_list) {
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
foreach ($argv_list as $argv) {
|
||||||
|
$results[] = self::FUNCTION_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderFunctionTokens($function, array $argv_list) {
|
||||||
|
$results = array();
|
||||||
|
foreach ($argv_list as $argv) {
|
||||||
|
$results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
|
||||||
|
$this->buildNoReviewersResult());
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildNoReviewersResult() {
|
||||||
|
$name = pht('No Reviewers');
|
||||||
|
|
||||||
|
return $this->newFunctionResult()
|
||||||
|
->setName($name.' none')
|
||||||
|
->setDisplayName($name)
|
||||||
|
->setIcon('fa-ban')
|
||||||
|
->setPHID('none()')
|
||||||
|
->setUnique(true)
|
||||||
|
->addAttribute(pht('Select results with no reviewers.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DifferentialReviewerFunctionDatasource
|
||||||
|
extends PhabricatorTypeaheadCompositeDatasource {
|
||||||
|
|
||||||
|
public function getBrowseTitle() {
|
||||||
|
return pht('Browse Reviewers');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlaceholderText() {
|
||||||
|
return pht('Type a user, project, package name or function...');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatasourceApplicationClass() {
|
||||||
|
return 'PhabricatorDifferentialApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComponentDatasources() {
|
||||||
|
return array(
|
||||||
|
new PhabricatorProjectOrUserFunctionDatasource(),
|
||||||
|
new PhabricatorOwnersPackageFunctionDatasource(),
|
||||||
|
new DifferentialNoReviewersDatasource(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -60,6 +60,16 @@ final class DifferentialRevisionListView extends AphrontView {
|
||||||
$handle_phids = array();
|
$handle_phids = array();
|
||||||
foreach ($this->revisions as $key => $revision) {
|
foreach ($this->revisions as $key => $revision) {
|
||||||
$reviewers = $revision->getReviewers();
|
$reviewers = $revision->getReviewers();
|
||||||
|
|
||||||
|
// Don't show reviewers who have resigned. The "Reviewers" constraint
|
||||||
|
// does not respect these reviewers and they largely don't count as
|
||||||
|
// reviewers.
|
||||||
|
foreach ($reviewers as $reviewer_key => $reviewer) {
|
||||||
|
if ($reviewer->isResigned()) {
|
||||||
|
unset($reviewers[$reviewer_key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (count($reviewers) > $reviewer_limit) {
|
if (count($reviewers) > $reviewer_limit) {
|
||||||
$reviewers = array_slice($reviewers, 0, $reviewer_limit);
|
$reviewers = array_slice($reviewers, 0, $reviewer_limit);
|
||||||
$reviewer_more[$key] = true;
|
$reviewer_more[$key] = true;
|
||||||
|
|
Loading…
Reference in a new issue