1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 05:20:56 +01:00

Change Differential revision buckets to focus on "next required action"

Summary:
Ref T10939. Ref T4144. This splits the existing buckets ("Blocking Others", "Action Required", "Waiting on Others") into 6-7 buckets with a stronger focus on what the next action you need to take is.

See T10939#175423 for some discussion.

Overall, I think some of the root problems here are caused by reviewer laziness and shotgun review workflows (where a ton of people get automatically added to everything, probably unnecessarily), but these buckets haven't been updated since the introduction of blocking reviewers or project/package reviewers and I think splitting the 3 buckets into 6 buckets isn't unreasonable, even though it's kind of a lot of buckets and the root problem here is approximately "I want to ignore a bunch of stuff on my dashboard".

I didn't remove the old bucketing code yet since it's still in use on the default homepage.

This also isn't quite right until I fix the tokenizer to work properly, since it won't bucket project/package reviewers accurately.

Test Plan: {F1395972}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4144, T10939

Differential Revision: https://secure.phabricator.com/D15924
This commit is contained in:
epriestley 2016-05-16 08:09:11 -07:00
parent eade206625
commit 42d49be47b
7 changed files with 339 additions and 27 deletions

View file

@ -3320,6 +3320,7 @@ phutil_register_library_map(array(
'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php',
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php', 'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php',
'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php',
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php', 'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php', 'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php',
'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php', 'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
@ -8015,6 +8016,7 @@ phutil_register_library_map(array(
'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorSearchRelationship' => 'Phobject', 'PhabricatorSearchRelationship' => 'Phobject',
'PhabricatorSearchResultBucket' => 'Phobject', 'PhabricatorSearchResultBucket' => 'Phobject',
'PhabricatorSearchResultBucketGroup' => 'Phobject',
'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchResultView' => 'AphrontView',
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',

View file

@ -5,8 +5,196 @@ final class DifferentialRevisionRequiredActionResultBucket
const BUCKETKEY = 'action'; const BUCKETKEY = 'action';
private $objects;
public function getResultBucketName() { public function getResultBucketName() {
return pht('Bucket by Required Action'); return pht('Bucket by Required Action');
} }
protected function buildResultGroups(
PhabricatorSavedQuery $query,
array $objects) {
$this->objects = $objects;
$phids = $query->getParameter('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('Must Review'))
->setNoDataString(pht('No revisions are blocked on your review.'))
->setObjects($this->filterMustReview($phids));
$groups[] = $this->newGroup()
->setName(pht('Ready to Review'))
->setNoDataString(pht('No revisions are waiting on you to review them.'))
->setObjects($this->filterShouldReview($phids));
$groups[] = $this->newGroup()
->setName(pht('Ready to Land'))
->setNoDataString(pht('No revisions are ready to land.'))
->setObjects($this->filterShouldLand($phids));
$groups[] = $this->newGroup()
->setName(pht('Ready to Update'))
->setNoDataString(pht('No revisions are waiting for updates.'))
->setObjects($this->filterShouldUpdate($phids));
$groups[] = $this->newGroup()
->setName(pht('Waiting on Review'))
->setNoDataString(pht('None of your revisions are waiting on review.'))
->setObjects($this->filterWaitingForReview($phids));
$groups[] = $this->newGroup()
->setName(pht('Waiting on Authors'))
->setNoDataString(pht('No revisions are waiting on author action.'))
->setObjects($this->filterWaitingOnAuthors($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 Revisions'))
->setObjects($this->objects);
}
return $groups;
}
private function filterMustReview(array $phids) {
$blocking = array(
DifferentialReviewerStatus::STATUS_BLOCKING,
DifferentialReviewerStatus::STATUS_REJECTED,
DifferentialReviewerStatus::STATUS_REJECTED_OLDER,
);
$blocking = array_fuse($blocking);
$objects = $this->getRevisionsUnderReview($this->objects, $phids);
$results = array();
foreach ($objects as $key => $object) {
if (!$this->hasReviewersWithStatus($object, $phids, $blocking)) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
private function filterShouldReview(array $phids) {
$reviewing = array(
DifferentialReviewerStatus::STATUS_ADDED,
);
$reviewing = array_fuse($reviewing);
$objects = $this->getRevisionsUnderReview($this->objects, $phids);
$results = array();
foreach ($objects as $key => $object) {
if (!$this->hasReviewersWithStatus($object, $phids, $reviewing)) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
private function filterShouldLand(array $phids) {
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
$objects = $this->getRevisionsAuthored($this->objects, $phids);
$results = array();
foreach ($objects as $key => $object) {
if ($object->getStatus() != $status_accepted) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
private function filterShouldUpdate(array $phids) {
$statuses = array(
ArcanistDifferentialRevisionStatus::NEEDS_REVISION,
ArcanistDifferentialRevisionStatus::CHANGES_PLANNED,
ArcanistDifferentialRevisionStatus::IN_PREPARATION,
);
$statuses = array_fuse($statuses);
$objects = $this->getRevisionsAuthored($this->objects, $phids);
$results = array();
foreach ($objects as $key => $object) {
if (empty($statuses[$object->getStatus()])) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
private function filterWaitingForReview(array $phids) {
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
$objects = $this->getRevisionsAuthored($this->objects, $phids);
$results = array();
foreach ($objects as $key => $object) {
if ($object->getStatus() != $status_review) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
private function filterWaitingOnAuthors(array $phids) {
$statuses = array(
ArcanistDifferentialRevisionStatus::ACCEPTED,
ArcanistDifferentialRevisionStatus::NEEDS_REVISION,
ArcanistDifferentialRevisionStatus::CHANGES_PLANNED,
ArcanistDifferentialRevisionStatus::IN_PREPARATION,
);
$statuses = array_fuse($statuses);
$objects = $this->getRevisionsNotAuthored($this->objects, $phids);
$results = array();
foreach ($objects as $key => $object) {
if (empty($statuses[$object->getStatus()])) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
} }

View file

@ -10,4 +10,68 @@ abstract class DifferentialRevisionResultBucket
->execute(); ->execute();
} }
protected function getRevisionsUnderReview(array $objects, array $phids) {
$results = array();
$objects = $this->getRevisionsNotAuthored($objects, $phids);
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
foreach ($objects as $key => $object) {
if ($object->getStatus() !== $status_review) {
continue;
}
$results[$key] = $object;
}
return $results;
}
protected function getRevisionsAuthored(array $objects, array $phids) {
$results = array();
foreach ($objects as $key => $object) {
if (isset($phids[$object->getAuthorPHID()])) {
$results[$key] = $object;
}
}
return $results;
}
protected function getRevisionsNotAuthored(array $objects, array $phids) {
$results = array();
foreach ($objects as $key => $object) {
if (empty($phids[$object->getAuthorPHID()])) {
$results[$key] = $object;
}
}
return $results;
}
protected function hasReviewersWithStatus(
DifferentialRevision $revision,
array $phids,
array $statuses) {
foreach ($revision->getReviewerStatus() as $reviewer) {
$reviewer_phid = $reviewer->getReviewerPHID();
if (empty($phids[$reviewer_phid])) {
continue;
}
$status = $reviewer->getStatus();
if (empty($statuses[$status])) {
continue;
}
return true;
}
return false;
}
} }

View file

@ -19,7 +19,8 @@ final class DifferentialRevisionSearchEngine
return id(new DifferentialRevisionQuery()) return id(new DifferentialRevisionQuery())
->needFlags(true) ->needFlags(true)
->needDrafts(true) ->needDrafts(true)
->needRelationships(true); ->needRelationships(true)
->needReviewerStatus(true);
} }
protected function buildQueryFromParameters(array $map) { protected function buildQueryFromParameters(array $map) {
@ -153,33 +154,20 @@ final class DifferentialRevisionSearchEngine
$views = array(); $views = array();
if ($bucket) { if ($bucket) {
$split = DifferentialRevisionQuery::splitResponsible( $bucket->setViewer($viewer);
$revisions,
$query->getParameter('responsiblePHIDs'));
list($blocking, $active, $waiting) = $split;
$views[] = id(clone $template) try {
->setHeader(pht('Blocking Others')) $groups = $bucket->newResultGroups($query, $revisions);
->setNoDataString(
pht('No revisions are blocked on your action.'))
->setHighlightAge(true)
->setRevisions($blocking)
->setHandles(array());
$views[] = id(clone $template) foreach ($groups as $group) {
->setHeader(pht('Action Required')) $views[] = id(clone $template)
->setNoDataString( ->setHeader($group->getName())
pht('No revisions require your action.')) ->setNoDataString($group->getNoDataString())
->setHighlightAge(true) ->setRevisions($group->getObjects());
->setRevisions($active) }
->setHandles(array()); } catch (Exception $ex) {
$this->addError($ex->getMessage());
$views[] = id(clone $template) }
->setHeader(pht('Waiting on Others'))
->setNoDataString(
pht('You have no revisions waiting on others.'))
->setRevisions($waiting)
->setHandles(array());
} else { } else {
$views[] = id(clone $template) $views[] = id(clone $template)
->setRevisions($revisions) ->setRevisions($revisions)

View file

@ -3,6 +3,7 @@
abstract class PhabricatorSearchResultBucket abstract class PhabricatorSearchResultBucket
extends Phobject { extends Phobject {
private $viewer;
private $pageSize; private $pageSize;
final public function setPageSize($page_size) { final public function setPageSize($page_size) {
@ -18,14 +19,36 @@ abstract class PhabricatorSearchResultBucket
return $this->pageSize; return $this->pageSize;
} }
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
protected function getDefaultPageSize() { protected function getDefaultPageSize() {
return 1000; return 1000;
} }
abstract public function getResultBucketName(); abstract public function getResultBucketName();
abstract protected function buildResultGroups(
PhabricatorSavedQuery $query,
array $objects);
final public function newResultGroups(
PhabricatorSavedQuery $query,
array $objects) {
return $this->buildResultGroups($query, $objects);
}
final public function getResultBucketKey() { final public function getResultBucketKey() {
return $this->getPhobjectClassConstant('BUCKETKEY'); return $this->getPhobjectClassConstant('BUCKETKEY');
} }
final protected function newGroup() {
return new PhabricatorSearchResultBucketGroup();
}
} }

View file

@ -0,0 +1,37 @@
<?php
final class PhabricatorSearchResultBucketGroup
extends Phobject {
private $name;
private $noDataString;
private $objects;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function getNoDataString() {
return $this->noDataString;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setObjects(array $objects) {
$this->objects = $objects;
return $this;
}
public function getObjects() {
return $this->objects;
}
}

View file

@ -213,6 +213,8 @@ final class PhabricatorApplicationSearchController
if ($run_query) { if ($run_query) {
$exec_errors = array();
$box->setAnchor( $box->setAnchor(
id(new PhabricatorAnchorView()) id(new PhabricatorAnchorView())
->setAnchorName('R')); ->setAnchorName('R'));
@ -280,10 +282,18 @@ final class PhabricatorApplicationSearchController
} }
} }
} catch (PhabricatorTypeaheadInvalidTokenException $ex) { } catch (PhabricatorTypeaheadInvalidTokenException $ex) {
$errors[] = pht( $exec_errors[] = pht(
'This query specifies an invalid parameter. Review the '. 'This query specifies an invalid parameter. Review the '.
'query parameters and correct errors.'); 'query parameters and correct errors.');
} }
// The engine may have encountered additional errors during rendering;
// merge them in and show everything.
foreach ($engine->getErrors() as $error) {
$exec_errors[] = $error;
}
$errors = $exec_errors;
} }
if ($errors) { if ($errors) {