mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-12 08:36:13 +01:00
Add EditEngine + Modular Transactions for reviewers
Summary: Ref T11114. This one is a bit more complex, but I think I covered everything. Test Plan: - Added reviewers. - Removed reviewers. - Made reviewers blocking. - Made reviewers nonblocking. - Tried to make the author a reviewer. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11114 Differential Revision: https://secure.phabricator.com/D17050
This commit is contained in:
parent
6c9af81f7a
commit
7f99f2cde8
5 changed files with 385 additions and 2 deletions
|
@ -549,6 +549,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialRevisionRequiredActionResultBucket' => 'applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php',
|
'DifferentialRevisionRequiredActionResultBucket' => 'applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php',
|
||||||
'DifferentialRevisionResultBucket' => 'applications/differential/query/DifferentialRevisionResultBucket.php',
|
'DifferentialRevisionResultBucket' => 'applications/differential/query/DifferentialRevisionResultBucket.php',
|
||||||
'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
|
'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
|
||||||
|
'DifferentialRevisionReviewersTransaction' => 'applications/differential/xaction/DifferentialRevisionReviewersTransaction.php',
|
||||||
'DifferentialRevisionSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionSearchConduitAPIMethod.php',
|
'DifferentialRevisionSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionSearchConduitAPIMethod.php',
|
||||||
'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
|
'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
|
||||||
'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
|
'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
|
||||||
|
@ -5202,6 +5203,7 @@ phutil_register_library_map(array(
|
||||||
'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket',
|
'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket',
|
||||||
'DifferentialRevisionResultBucket' => 'PhabricatorSearchResultBucket',
|
'DifferentialRevisionResultBucket' => 'PhabricatorSearchResultBucket',
|
||||||
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
|
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
|
||||||
|
'DifferentialRevisionReviewersTransaction' => 'DifferentialRevisionTransactionType',
|
||||||
'DifferentialRevisionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
'DifferentialRevisionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||||
'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'DifferentialRevisionStatus' => 'Phobject',
|
'DifferentialRevisionStatus' => 'Phobject',
|
||||||
|
|
|
@ -32,7 +32,8 @@ final class DifferentialRevisionEditEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function newObjectQuery() {
|
protected function newObjectQuery() {
|
||||||
return new DifferentialRevisionQuery();
|
return id(new DifferentialRevisionQuery())
|
||||||
|
->needReviewerStatus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getObjectCreateTitleText($object) {
|
protected function getObjectCreateTitleText($object) {
|
||||||
|
@ -103,6 +104,18 @@ final class DifferentialRevisionEditEngine
|
||||||
->setValue($object->getTestPlan());
|
->setValue($object->getTestPlan());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$fields[] = id(new PhabricatorDatasourceEditField())
|
||||||
|
->setKey('reviewerPHIDs')
|
||||||
|
->setLabel(pht('Reviewers'))
|
||||||
|
->setDatasource(new DifferentialReviewerDatasource())
|
||||||
|
->setUseEdgeTransactions(true)
|
||||||
|
->setTransactionType(
|
||||||
|
DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE)
|
||||||
|
->setDescription(pht('Reviewers for this revision.'))
|
||||||
|
->setConduitDescription(pht('Change the reviewers for this revision.'))
|
||||||
|
->setConduitTypeDescription(pht('New reviewers.'))
|
||||||
|
->setValue($object->getReviewerPHIDsForEdit());
|
||||||
|
|
||||||
$fields[] = id(new PhabricatorDatasourceEditField())
|
$fields[] = id(new PhabricatorDatasourceEditField())
|
||||||
->setKey('repositoryPHID')
|
->setKey('repositoryPHID')
|
||||||
->setLabel(pht('Repository'))
|
->setLabel(pht('Repository'))
|
||||||
|
|
|
@ -409,6 +409,24 @@ final class DifferentialRevision extends DifferentialDAO
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getReviewerPHIDsForEdit() {
|
||||||
|
$reviewers = $this->getReviewerStatus();
|
||||||
|
|
||||||
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||||
|
|
||||||
|
$value = array();
|
||||||
|
foreach ($reviewers as $reviewer) {
|
||||||
|
$phid = $reviewer->getReviewerPHID();
|
||||||
|
if ($reviewer->getStatus() == $status_blocking) {
|
||||||
|
$value[] = 'blocking('.$phid.')';
|
||||||
|
} else {
|
||||||
|
$value[] = $phid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
public function getRepository() {
|
public function getRepository() {
|
||||||
return $this->assertAttached($this->repository);
|
return $this->assertAttached($this->repository);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,350 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DifferentialRevisionReviewersTransaction
|
||||||
|
extends DifferentialRevisionTransactionType {
|
||||||
|
|
||||||
|
const TRANSACTIONTYPE = 'differential.revision.reviewers';
|
||||||
|
|
||||||
|
public function generateOldValue($object) {
|
||||||
|
$reviewers = $object->getReviewerStatus();
|
||||||
|
$reviewers = mpull($reviewers, 'getStatus', 'getReviewerPHID');
|
||||||
|
return $reviewers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateNewValue($object, $value) {
|
||||||
|
$actor = $this->getActor();
|
||||||
|
|
||||||
|
$datasource = id(new DifferentialBlockingReviewerDatasource())
|
||||||
|
->setViewer($actor);
|
||||||
|
|
||||||
|
$reviewers = $this->generateOldValue($object);
|
||||||
|
|
||||||
|
// First, remove any reviewers we're getting rid of.
|
||||||
|
$rem = idx($value, '-', array());
|
||||||
|
$rem = $datasource->evaluateTokens($rem);
|
||||||
|
foreach ($rem as $phid) {
|
||||||
|
unset($reviewers[$phid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$add = idx($value, '+', array());
|
||||||
|
$add = $datasource->evaluateTokens($add);
|
||||||
|
$add_map = array();
|
||||||
|
foreach ($add as $spec) {
|
||||||
|
if (!is_array($spec)) {
|
||||||
|
$phid = $spec;
|
||||||
|
$status = DifferentialReviewerStatus::STATUS_ADDED;
|
||||||
|
} else {
|
||||||
|
$phid = $spec['phid'];
|
||||||
|
$status = $spec['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$add_map[$phid] = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
$set = idx($value, '=', null);
|
||||||
|
if ($set !== null) {
|
||||||
|
$set = $datasource->evaluateTokens($set);
|
||||||
|
foreach ($set as $spec) {
|
||||||
|
if (!is_array($spec)) {
|
||||||
|
$phid = $spec;
|
||||||
|
$status = DifferentialReviewerStatus::STATUS_ADDED;
|
||||||
|
} else {
|
||||||
|
$phid = $spec['phid'];
|
||||||
|
$status = $spec['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$add_map[$phid] = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We treat setting reviewers as though they were being added to an
|
||||||
|
// empty list, so we can share more code between pathways.
|
||||||
|
$reviewers = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||||
|
foreach ($add_map as $phid => $new_status) {
|
||||||
|
$old_status = idx($reviewers, $phid);
|
||||||
|
|
||||||
|
// If we have an old status and this didn't make the reviewer blocking
|
||||||
|
// or nonblocking, just retain the old status. This makes sure we don't
|
||||||
|
// throw away rejects, accepts, etc.
|
||||||
|
if ($old_status) {
|
||||||
|
$was_blocking = ($old_status == $status_blocking);
|
||||||
|
$now_blocking = ($new_status == $status_blocking);
|
||||||
|
|
||||||
|
$is_block = ($now_blocking && !$was_blocking);
|
||||||
|
$is_unblock = (!$now_blocking && $was_blocking);
|
||||||
|
|
||||||
|
if (!$is_block && !$is_unblock) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$reviewers[$phid] = $new_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $reviewers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyExternalEffects($object, $value) {
|
||||||
|
$src_phid = $object->getPHID();
|
||||||
|
|
||||||
|
$old = $this->generateOldValue($object);
|
||||||
|
$new = $value;
|
||||||
|
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
|
||||||
|
|
||||||
|
$editor = new PhabricatorEdgeEditor();
|
||||||
|
|
||||||
|
$rem = array_diff_key($old, $new);
|
||||||
|
foreach ($rem as $dst_phid => $status) {
|
||||||
|
$editor->removeEdge($src_phid, $edge_type, $dst_phid);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($new as $dst_phid => $status) {
|
||||||
|
$old_status = idx($old, $dst_phid);
|
||||||
|
if ($old_status === $status) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'data' => array(
|
||||||
|
'status' => $status,
|
||||||
|
|
||||||
|
// TODO: This seemes like it's buggy before the Modular Transactions
|
||||||
|
// changes. Figure out what's going on here? We don't have a very
|
||||||
|
// clean way to get the active diff ID right now.
|
||||||
|
'diffID' => null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$editor->addEdge($src_phid, $edge_type, $dst_phid, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$editor->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
return $this->renderReviewerEditTitle(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitleForFeed() {
|
||||||
|
return $this->renderReviewerEditTitle(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderReviewerEditTitle($is_feed) {
|
||||||
|
$old = $this->getOldValue();
|
||||||
|
$new = $this->getNewValue();
|
||||||
|
|
||||||
|
$rem = array_diff_key($old, $new);
|
||||||
|
$add = array_diff_key($new, $old);
|
||||||
|
$rem_phids = array_keys($rem);
|
||||||
|
$add_phids = array_keys($add);
|
||||||
|
$total_count = count($rem) + count($add);
|
||||||
|
|
||||||
|
$parts = array();
|
||||||
|
|
||||||
|
if ($rem && $add) {
|
||||||
|
if ($is_feed) {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s edited %s reviewer(s) for %s, added %s: %s; removed %s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
new PhutilNumber($total_count),
|
||||||
|
$this->renderObject(),
|
||||||
|
phutil_count($add_phids),
|
||||||
|
$this->renderHandleList($add_phids),
|
||||||
|
phutil_count($rem_phids),
|
||||||
|
$this->renderHandleList($rem_phids));
|
||||||
|
} else {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s edited %s reviewer(s), added %s: %s; removed %s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
new PhutilNumber($total_count),
|
||||||
|
phutil_count($add_phids),
|
||||||
|
$this->renderHandleList($add_phids),
|
||||||
|
phutil_count($rem_phids),
|
||||||
|
$this->renderHandleList($rem_phids));
|
||||||
|
}
|
||||||
|
} else if ($add) {
|
||||||
|
if ($is_feed) {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s added %s reviewer(s) for %s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($add_phids),
|
||||||
|
$this->renderObject(),
|
||||||
|
$this->renderHandleList($add_phids));
|
||||||
|
} else {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s added %s reviewer(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($add_phids),
|
||||||
|
$this->renderHandleList($add_phids));
|
||||||
|
}
|
||||||
|
} else if ($rem) {
|
||||||
|
if ($is_feed) {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s removed %s reviewer(s) for %s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($rem_phids),
|
||||||
|
$this->renderObject(),
|
||||||
|
$this->renderHandleList($rem_phids));
|
||||||
|
} else {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s removed %s reviewer(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($rem_phids),
|
||||||
|
$this->renderHandleList($rem_phids));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
||||||
|
$blocks = array();
|
||||||
|
$unblocks = array();
|
||||||
|
foreach ($new as $phid => $new_status) {
|
||||||
|
$old_status = idx($old, $phid);
|
||||||
|
if (!$old_status) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$was_blocking = ($old_status == $status_blocking);
|
||||||
|
$now_blocking = ($new_status == $status_blocking);
|
||||||
|
|
||||||
|
$is_block = ($now_blocking && !$was_blocking);
|
||||||
|
$is_unblock = (!$now_blocking && $was_blocking);
|
||||||
|
|
||||||
|
if ($is_block) {
|
||||||
|
$blocks[] = $phid;
|
||||||
|
}
|
||||||
|
if ($is_unblock) {
|
||||||
|
$unblocks[] = $phid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_count = count($blocks) + count($unblocks);
|
||||||
|
|
||||||
|
if ($blocks && $unblocks) {
|
||||||
|
if ($is_feed) {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s changed %s blocking reviewer(s) for %s, added %s: %s; removed '.
|
||||||
|
'%s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
new PhutilNumber($total_count),
|
||||||
|
$this->renderObject(),
|
||||||
|
phutil_count($blocks),
|
||||||
|
$this->renderHandleList($blocks),
|
||||||
|
phutil_count($unblocks),
|
||||||
|
$this->renderHandleList($unblocks));
|
||||||
|
} else {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s changed %s blocking reviewer(s), added %s: %s; removed %s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
new PhutilNumber($total_count),
|
||||||
|
phutil_count($blocks),
|
||||||
|
$this->renderHandleList($blocks),
|
||||||
|
phutil_count($unblocks),
|
||||||
|
$this->renderHandleList($unblocks));
|
||||||
|
}
|
||||||
|
} else if ($blocks) {
|
||||||
|
if ($is_feed) {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s added %s blocking reviewer(s) for %s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($blocks),
|
||||||
|
$this->renderObject(),
|
||||||
|
$this->renderHandleList($blocks));
|
||||||
|
} else {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s added %s blocking reviewer(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($blocks),
|
||||||
|
$this->renderHandleList($blocks));
|
||||||
|
}
|
||||||
|
} else if ($unblocks) {
|
||||||
|
if ($is_feed) {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s removed %s blocking reviewer(s) for %s: %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($unblocks),
|
||||||
|
$this->renderObject(),
|
||||||
|
$this->renderHandleList($unblocks));
|
||||||
|
} else {
|
||||||
|
$parts[] = pht(
|
||||||
|
'%s removed %s blocking reviewer(s): %s.',
|
||||||
|
$this->renderAuthor(),
|
||||||
|
phutil_count($unblocks),
|
||||||
|
$this->renderHandleList($unblocks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isTextMode()) {
|
||||||
|
return implode(' ', $parts);
|
||||||
|
} else {
|
||||||
|
return phutil_implode_html(' ', $parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateTransactions($object, array $xactions) {
|
||||||
|
$actor = $this->getActor();
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
$author_phid = $object->getAuthorPHID();
|
||||||
|
$config_self_accept_key = 'differential.allow-self-accept';
|
||||||
|
$allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key);
|
||||||
|
|
||||||
|
$old = $this->generateOldValue($object);
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$new = $this->generateNewValue($object, $xaction->getNewValue());
|
||||||
|
|
||||||
|
$add = array_diff_key($new, $old);
|
||||||
|
if (!$add) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$objects = id(new PhabricatorObjectQuery())
|
||||||
|
->setViewer($actor)
|
||||||
|
->withPHIDs(array_keys($add))
|
||||||
|
->execute();
|
||||||
|
$objects = mpull($objects, null, 'getPHID');
|
||||||
|
|
||||||
|
foreach ($add as $phid => $status) {
|
||||||
|
if (!isset($objects[$phid])) {
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht(
|
||||||
|
'Reviewer "%s" is not a valid object.',
|
||||||
|
$phid),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (phid_get_type($phid)) {
|
||||||
|
case PhabricatorPeopleUserPHIDType::TYPECONST:
|
||||||
|
case PhabricatorOwnersPackagePHIDType::TYPECONST:
|
||||||
|
case PhabricatorProjectProjectPHIDType::TYPECONST:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht(
|
||||||
|
'Reviewer "%s" must be a user, a package, or a project.',
|
||||||
|
$phid),
|
||||||
|
$xaction);
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This weird behavior around commandeering is a bit unorthodox,
|
||||||
|
// but this restriction is an unusual one.
|
||||||
|
|
||||||
|
$is_self = ($phid === $author_phid);
|
||||||
|
if ($is_self && !$allow_self_accept) {
|
||||||
|
if (!$xaction->getIsCommandeerSideEffect()) {
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht('The author of a revision can not be a reviewer.'),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -277,7 +277,7 @@ abstract class PhabricatorModularTransactionType
|
||||||
return !strlen($value);
|
return !strlen($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isTextMode() {
|
protected function isTextMode() {
|
||||||
$target = $this->getStorage()->getRenderingTarget();
|
$target = $this->getStorage()->getRenderingTarget();
|
||||||
return ($target == PhabricatorApplicationTransaction::TARGET_TEXT);
|
return ($target == PhabricatorApplicationTransaction::TARGET_TEXT);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue