Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13987418
D17050.id41015.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
D17050.id41015.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -549,6 +549,7 @@
'DifferentialRevisionRequiredActionResultBucket' => 'applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php',
'DifferentialRevisionResultBucket' => 'applications/differential/query/DifferentialRevisionResultBucket.php',
'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
+ 'DifferentialRevisionReviewersTransaction' => 'applications/differential/xaction/DifferentialRevisionReviewersTransaction.php',
'DifferentialRevisionSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionSearchConduitAPIMethod.php',
'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
@@ -5202,6 +5203,7 @@
'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket',
'DifferentialRevisionResultBucket' => 'PhabricatorSearchResultBucket',
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
+ 'DifferentialRevisionReviewersTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DifferentialRevisionStatus' => 'Phobject',
diff --git a/src/applications/differential/editor/DifferentialRevisionEditEngine.php b/src/applications/differential/editor/DifferentialRevisionEditEngine.php
--- a/src/applications/differential/editor/DifferentialRevisionEditEngine.php
+++ b/src/applications/differential/editor/DifferentialRevisionEditEngine.php
@@ -32,7 +32,8 @@
}
protected function newObjectQuery() {
- return new DifferentialRevisionQuery();
+ return id(new DifferentialRevisionQuery())
+ ->needReviewerStatus(true);
}
protected function getObjectCreateTitleText($object) {
@@ -104,6 +105,18 @@
}
$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())
->setKey('repositoryPHID')
->setLabel(pht('Repository'))
->setDatasource(new DiffusionRepositoryDatasource())
diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php
--- a/src/applications/differential/storage/DifferentialRevision.php
+++ b/src/applications/differential/storage/DifferentialRevision.php
@@ -409,6 +409,24 @@
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() {
return $this->assertAttached($this->repository);
}
diff --git a/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php
--- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php
+++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php
@@ -277,7 +277,7 @@
return !strlen($value);
}
- private function isTextMode() {
+ protected function isTextMode() {
$target = $this->getStorage()->getRenderingTarget();
return ($target == PhabricatorApplicationTransaction::TARGET_TEXT);
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Oct 22, 9:23 AM (3 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6729193
Default Alt Text
D17050.id41015.diff (15 KB)
Attached To
Mode
D17050: Add EditEngine + Modular Transactions for reviewers
Attached
Detach File
Event Timeline
Log In to Comment