1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 18:22:41 +01:00

Implement "Resign" action against ApplicationTransactions

Summary:
Ref T2222. This introduces two small new concepts:

  - `expandTransactions()`: allows a transaction to expand into several transactions. For example, "resign" adds a "remove reviewers" transaction.
    - We have some other cases which could use this, but currently hard-code things outside of the `Editor`.
      - One example is that in Maniphest, closing a task implies claiming it if it is unowned.
  - `setIgnoreOnNoEffect()`: The whole Editor can be set to continue or stop if any transactions have no effect, but this allows the behavior to be refined at the individual transaction level. This is primarily to make the UX less confusing, so the user gets only a single relevant error instead of one for each expanded transaction.

Otherwise, this is pretty straightforward.

Test Plan:
Rigged comment form to use SavePro controller, enabled resign action, then tried to resign from a bunch of stuff.

{F117743}

Reviewers: btrahan

Reviewed By: btrahan

CC: chad, aran

Maniphest Tasks: T2222

Differential Revision: https://secure.phabricator.com/D8328
This commit is contained in:
epriestley 2014-02-25 12:36:02 -08:00
parent 72a062d802
commit 9292433dae
9 changed files with 151 additions and 11 deletions

View file

@ -7,7 +7,7 @@
return array( return array(
'names' => 'names' =>
array( array(
'core.pkg.css' => '8c8b76a8', 'core.pkg.css' => '76aa3fcd',
'core.pkg.js' => '8f7aa2c3', 'core.pkg.js' => '8f7aa2c3',
'darkconsole.pkg.js' => 'ca8671ce', 'darkconsole.pkg.js' => 'ca8671ce',
'differential.pkg.css' => '6aef439e', 'differential.pkg.css' => '6aef439e',
@ -21,7 +21,7 @@ return array(
'rsrc/css/aphront/aphront-notes.css' => '6acadd3f', 'rsrc/css/aphront/aphront-notes.css' => '6acadd3f',
'rsrc/css/aphront/context-bar.css' => '1c3b0529', 'rsrc/css/aphront/context-bar.css' => '1c3b0529',
'rsrc/css/aphront/dark-console.css' => '6378ef3d', 'rsrc/css/aphront/dark-console.css' => '6378ef3d',
'rsrc/css/aphront/dialog-view.css' => 'dd9db96c', 'rsrc/css/aphront/dialog-view.css' => 'c01d24b4',
'rsrc/css/aphront/error-view.css' => '16cd9949', 'rsrc/css/aphront/error-view.css' => '16cd9949',
'rsrc/css/aphront/lightbox-attachment.css' => '686f8885', 'rsrc/css/aphront/lightbox-attachment.css' => '686f8885',
'rsrc/css/aphront/list-filter-view.css' => 'ef989c67', 'rsrc/css/aphront/list-filter-view.css' => 'ef989c67',
@ -483,7 +483,7 @@ return array(
'aphront-bars' => '231ac33c', 'aphront-bars' => '231ac33c',
'aphront-contextbar-view-css' => '1c3b0529', 'aphront-contextbar-view-css' => '1c3b0529',
'aphront-dark-console-css' => '6378ef3d', 'aphront-dark-console-css' => '6378ef3d',
'aphront-dialog-view-css' => 'dd9db96c', 'aphront-dialog-view-css' => 'c01d24b4',
'aphront-error-view-css' => '16cd9949', 'aphront-error-view-css' => '16cd9949',
'aphront-list-filter-view-css' => 'ef989c67', 'aphront-list-filter-view-css' => 'ef989c67',
'aphront-multi-column-view-css' => '12f65921', 'aphront-multi-column-view-css' => '12f65921',

View file

@ -94,7 +94,7 @@ final class DifferentialCommentSaveControllerPro
->withPHIDs($inline_phids) ->withPHIDs($inline_phids)
->execute(); ->execute();
} else { } else {
$inlines = null; $inlines = array();
} }
foreach ($inlines as $inline) { foreach ($inlines as $inline) {

View file

@ -82,6 +82,14 @@ final class DifferentialTransactionEditor
return ($object->getStatus() != $status_revision); return ($object->getStatus() != $status_revision);
case DifferentialAction::ACTION_REQUEST: case DifferentialAction::ACTION_REQUEST:
return ($object->getStatus() != $status_review); return ($object->getStatus() != $status_review);
case DifferentialAction::ACTION_RESIGN:
$actor_phid = $this->getActor()->getPHID();
foreach ($object->getReviewerStatus() as $reviewer) {
if ($reviewer->getReviewerPHID() == $actor_phid) {
return true;
}
}
return false;
} }
} }
@ -113,6 +121,9 @@ final class DifferentialTransactionEditor
$status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION;
switch ($xaction->getNewValue()) { switch ($xaction->getNewValue()) {
case DifferentialAction::ACTION_RESIGN:
// TODO: Update review status?
break;
case DifferentialAction::ACTION_ABANDON: case DifferentialAction::ACTION_ABANDON:
$object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED);
break; break;
@ -150,6 +161,41 @@ final class DifferentialTransactionEditor
return parent::applyCustomInternalTransaction($object, $xaction); return parent::applyCustomInternalTransaction($object, $xaction);
} }
protected function expandTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$results = parent::expandTransaction($object, $xaction);
switch ($xaction->getTransactionType()) {
case DifferentialTransaction::TYPE_ACTION:
switch ($xaction->getNewValue()) {
case DifferentialAction::ACTION_RESIGN:
// If the user is resigning, add a separate reviewer edit
// transaction which removes them as a reviewer.
$actor_phid = $this->getActor()->getPHID();
$type_edge = PhabricatorTransactions::TYPE_EDGE;
$edge_reviewer = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER;
$results[] = id(new DifferentialTransaction())
->setTransactionType($type_edge)
->setMetadataValue('edge:type', $edge_reviewer)
->setIgnoreOnNoEffect(true)
->setNewValue(
array(
'-' => array(
$actor_phid => $actor_phid,
),
));
break;
}
break;
}
return $results;
}
protected function applyCustomExternalTransaction( protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) { PhabricatorApplicationTransaction $xaction) {
@ -221,6 +267,11 @@ final class DifferentialTransactionEditor
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED; $status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
switch ($action) { switch ($action) {
case DifferentialAction::ACTION_RESIGN:
// You can always resign from a revision if you're a reviewer. If you
// aren't, this is a no-op rather than invalid.
break;
case DifferentialAction::ACTION_ABANDON: case DifferentialAction::ACTION_ABANDON:
if (!$actor_is_author) { if (!$actor_is_author) {
return pht( return pht(

View file

@ -121,7 +121,8 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction {
switch ($this->getMetadataValue('edge:type')) { switch ($this->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER: case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER:
return pht( return pht(
'Those reviewers are already reviewing this revision.'); 'The reviewers you are trying to add are already reviewing '.
'this revision.');
} }
break; break;
case DifferentialTransaction::TYPE_ACTION: case DifferentialTransaction::TYPE_ACTION:
@ -142,6 +143,10 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction {
return pht('This revision already requires changes.'); return pht('This revision already requires changes.');
case DifferentialAction::ACTION_REQUEST: case DifferentialAction::ACTION_REQUEST:
return pht('Review is already requested for this revision.'); return pht('Review is already requested for this revision.');
case DifferentialAction::ACTION_RESIGN:
return pht(
'You can not resign from this revision because you are not '.
'a reviewer.');
} }
break; break;
} }

View file

@ -417,6 +417,7 @@ abstract class PhabricatorApplicationTransactionEditor
$xactions[] = $mention_xaction; $xactions[] = $mention_xaction;
} }
$xactions = $this->expandTransactions($object, $xactions);
$xactions = $this->combineTransactions($xactions); $xactions = $this->combineTransactions($xactions);
foreach ($xactions as $xaction) { foreach ($xactions as $xaction) {
@ -853,6 +854,30 @@ abstract class PhabricatorApplicationTransactionEditor
return null; return null;
} }
/**
* Optionally expand transactions which imply other effects. For example,
* resigning from a revision in Differential implies removing yourself as
* a reviewer.
*/
private function expandTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$results = array();
foreach ($xactions as $xaction) {
foreach ($this->expandTransaction($object, $xaction) as $expanded) {
$results[] = $expanded;
}
}
return $results;
}
protected function expandTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return array($xaction);
}
/** /**
* Attempt to combine similar transactions into a smaller number of total * Attempt to combine similar transactions into a smaller number of total
@ -912,6 +937,12 @@ abstract class PhabricatorApplicationTransactionEditor
} }
$u->setNewValue($result); $u->setNewValue($result);
// When combining an "ignore" transaction with a normal transaction, make
// sure we don't propagate the "ignore" flag.
if (!$v->getIgnoreOnNoEffect()) {
$u->setIgnoreOnNoEffect(false);
}
return $u; return $u;
} }
@ -1128,6 +1159,8 @@ abstract class PhabricatorApplicationTransactionEditor
if ($xaction->getTransactionType() != $type_comment) { if ($xaction->getTransactionType() != $type_comment) {
$any_effect = true; $any_effect = true;
} }
} else if ($xaction->getIgnoreOnNoEffect()) {
unset($xactions[$key]);
} else { } else {
$no_effect[$key] = $xaction; $no_effect[$key] = $xaction;
} }

View file

@ -32,22 +32,28 @@ final class PhabricatorApplicationTransactionNoEffectResponse
$only_empty_comment = (count($xactions) == 1) && $only_empty_comment = (count($xactions) == 1) &&
(head($xactions)->getTransactionType() == $type_comment); (head($xactions)->getTransactionType() == $type_comment);
$count = new PhutilNumber(count($xactions));
if ($ex->hasAnyEffect()) { if ($ex->hasAnyEffect()) {
$title = pht('%d Action(s) With No Effect', count($xactions)); $title = pht('%d Action(s) With No Effect', $count);
$head = pht('Some of your %d action(s) have no effect:', $count);
$tail = pht('Apply remaining actions?'); $tail = pht('Apply remaining actions?');
$continue = pht('Apply Other Actions'); $continue = pht('Apply Remaining Actions');
} else if ($ex->hasComment()) { } else if ($ex->hasComment()) {
$title = pht('Post as Comment'); $title = pht('Post as Comment');
$head = pht('The %d action(s) you are taking have no effect:', $count);
$tail = pht('Do you want to post your comment anyway?'); $tail = pht('Do you want to post your comment anyway?');
$continue = pht('Post Comment'); $continue = pht('Post Comment');
} else if ($only_empty_comment) { } else if ($only_empty_comment) {
// Special case this since it's common and we can give the user a nicer // Special case this since it's common and we can give the user a nicer
// dialog than "Action Has No Effect". // dialog than "Action Has No Effect".
$title = pht('Empty Comment'); $title = pht('Empty Comment');
$head = null;
$tail = null; $tail = null;
$continue = null; $continue = null;
} else { } else {
$title = pht('%d Action(s) Have No Effect', count($xactions)); $title = pht('%d Action(s) Have No Effect', $count);
$head = pht('The %d action(s) you are taking have no effect:', $count);
$tail = null; $tail = null;
$continue = null; $continue = null;
} }
@ -56,10 +62,17 @@ final class PhabricatorApplicationTransactionNoEffectResponse
->setUser($request->getUser()) ->setUser($request->getUser())
->setTitle($title); ->setTitle($title);
$dialog->appendChild($head);
$list = array();
foreach ($xactions as $xaction) { foreach ($xactions as $xaction) {
$dialog->appendChild( $list[] = phutil_tag(
phutil_tag('p', array(), $xaction->getNoEffectDescription())); 'li',
array(),
$xaction->getNoEffectDescription());
} }
$dialog->appendChild(phutil_tag('ul', array(), $list));
$dialog->appendChild($tail); $dialog->appendChild($tail);
if ($continue) { if ($continue) {

View file

@ -31,6 +31,24 @@ abstract class PhabricatorApplicationTransaction
private $viewer = self::ATTACHABLE; private $viewer = self::ATTACHABLE;
private $object = self::ATTACHABLE; private $object = self::ATTACHABLE;
private $ignoreOnNoEffect;
/**
* Flag this transaction as a pure side-effect which should be ignored when
* applying transactions if it has no effect, even if transaction application
* would normally fail. This both provides users with better error messages
* and allows transactions to perform optional side effects.
*/
public function setIgnoreOnNoEffect($ignore) {
$this->ignoreOnNoEffect = $ignore;
return $this;
}
public function getIgnoreOnNoEffect() {
return $this->ignoreOnNoEffect;
}
abstract public function getApplicationTransactionType(); abstract public function getApplicationTransactionType();
private function getApplicationObjectTypeName() { private function getApplicationObjectTypeName() {

View file

@ -215,6 +215,26 @@ abstract class PhabricatorBaseEnglishTranslation
'Actions With No Effect', 'Actions With No Effect',
), ),
'Some of your %d action(s) have no effect:' => array(
'One of your actions has no effect:',
'Some of your actions have no effect:',
),
'Apply remaining %d action(s)?' => array(
'Apply remaining action?',
'Apply remaining actions?',
),
'Apply %d Other Action(s)' => array(
'Apply Remaining Action',
'Apply Remaining Actions',
),
'The %d action(s) you are taking have no effect:' => array(
'The action you are taking has no effect:',
'The actions you are taking have no effect:',
),
'%s edited post(s), added %d: %s; removed %d: %s.' => '%s edited post(s), added %d: %s; removed %d: %s.' =>
'%s edited posts, added: %3$s; removed: %5$s', '%s edited posts, added: %3$s; removed: %5$s',

View file

@ -112,7 +112,7 @@
width: 50%; width: 50%;
} }
.aphront-access-dialog ul { .aphront-dialog-view ul {
margin: 12px 24px; margin: 12px 24px;
list-style: circle; list-style: circle;
} }