@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+151 -11
+3 -3
resources/celerity/map.php
··· 7 7 return array( 8 8 'names' => 9 9 array( 10 - 'core.pkg.css' => '8c8b76a8', 10 + 'core.pkg.css' => '76aa3fcd', 11 11 'core.pkg.js' => '8f7aa2c3', 12 12 'darkconsole.pkg.js' => 'ca8671ce', 13 13 'differential.pkg.css' => '6aef439e', ··· 21 21 'rsrc/css/aphront/aphront-notes.css' => '6acadd3f', 22 22 'rsrc/css/aphront/context-bar.css' => '1c3b0529', 23 23 'rsrc/css/aphront/dark-console.css' => '6378ef3d', 24 - 'rsrc/css/aphront/dialog-view.css' => 'dd9db96c', 24 + 'rsrc/css/aphront/dialog-view.css' => 'c01d24b4', 25 25 'rsrc/css/aphront/error-view.css' => '16cd9949', 26 26 'rsrc/css/aphront/lightbox-attachment.css' => '686f8885', 27 27 'rsrc/css/aphront/list-filter-view.css' => 'ef989c67', ··· 483 483 'aphront-bars' => '231ac33c', 484 484 'aphront-contextbar-view-css' => '1c3b0529', 485 485 'aphront-dark-console-css' => '6378ef3d', 486 - 'aphront-dialog-view-css' => 'dd9db96c', 486 + 'aphront-dialog-view-css' => 'c01d24b4', 487 487 'aphront-error-view-css' => '16cd9949', 488 488 'aphront-list-filter-view-css' => 'ef989c67', 489 489 'aphront-multi-column-view-css' => '12f65921',
+1 -1
src/applications/differential/controller/DifferentialCommentSaveControllerPro.php
··· 94 94 ->withPHIDs($inline_phids) 95 95 ->execute(); 96 96 } else { 97 - $inlines = null; 97 + $inlines = array(); 98 98 } 99 99 100 100 foreach ($inlines as $inline) {
+51
src/applications/differential/editor/DifferentialTransactionEditor.php
··· 82 82 return ($object->getStatus() != $status_revision); 83 83 case DifferentialAction::ACTION_REQUEST: 84 84 return ($object->getStatus() != $status_review); 85 + case DifferentialAction::ACTION_RESIGN: 86 + $actor_phid = $this->getActor()->getPHID(); 87 + foreach ($object->getReviewerStatus() as $reviewer) { 88 + if ($reviewer->getReviewerPHID() == $actor_phid) { 89 + return true; 90 + } 91 + } 92 + return false; 85 93 } 86 94 } 87 95 ··· 113 121 $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; 114 122 115 123 switch ($xaction->getNewValue()) { 124 + case DifferentialAction::ACTION_RESIGN: 125 + // TODO: Update review status? 126 + break; 116 127 case DifferentialAction::ACTION_ABANDON: 117 128 $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); 118 129 break; ··· 150 161 return parent::applyCustomInternalTransaction($object, $xaction); 151 162 } 152 163 164 + protected function expandTransaction( 165 + PhabricatorLiskDAO $object, 166 + PhabricatorApplicationTransaction $xaction) { 167 + 168 + $results = parent::expandTransaction($object, $xaction); 169 + switch ($xaction->getTransactionType()) { 170 + case DifferentialTransaction::TYPE_ACTION: 171 + switch ($xaction->getNewValue()) { 172 + case DifferentialAction::ACTION_RESIGN: 173 + // If the user is resigning, add a separate reviewer edit 174 + // transaction which removes them as a reviewer. 175 + 176 + $actor_phid = $this->getActor()->getPHID(); 177 + $type_edge = PhabricatorTransactions::TYPE_EDGE; 178 + $edge_reviewer = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER; 179 + 180 + $results[] = id(new DifferentialTransaction()) 181 + ->setTransactionType($type_edge) 182 + ->setMetadataValue('edge:type', $edge_reviewer) 183 + ->setIgnoreOnNoEffect(true) 184 + ->setNewValue( 185 + array( 186 + '-' => array( 187 + $actor_phid => $actor_phid, 188 + ), 189 + )); 190 + 191 + break; 192 + } 193 + break; 194 + } 195 + 196 + return $results; 197 + } 198 + 153 199 protected function applyCustomExternalTransaction( 154 200 PhabricatorLiskDAO $object, 155 201 PhabricatorApplicationTransaction $xaction) { ··· 221 267 $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; 222 268 223 269 switch ($action) { 270 + case DifferentialAction::ACTION_RESIGN: 271 + // You can always resign from a revision if you're a reviewer. If you 272 + // aren't, this is a no-op rather than invalid. 273 + break; 274 + 224 275 case DifferentialAction::ACTION_ABANDON: 225 276 if (!$actor_is_author) { 226 277 return pht(
+6 -1
src/applications/differential/storage/DifferentialTransaction.php
··· 121 121 switch ($this->getMetadataValue('edge:type')) { 122 122 case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER: 123 123 return pht( 124 - 'Those reviewers are already reviewing this revision.'); 124 + 'The reviewers you are trying to add are already reviewing '. 125 + 'this revision.'); 125 126 } 126 127 break; 127 128 case DifferentialTransaction::TYPE_ACTION: ··· 142 143 return pht('This revision already requires changes.'); 143 144 case DifferentialAction::ACTION_REQUEST: 144 145 return pht('Review is already requested for this revision.'); 146 + case DifferentialAction::ACTION_RESIGN: 147 + return pht( 148 + 'You can not resign from this revision because you are not '. 149 + 'a reviewer.'); 145 150 } 146 151 break; 147 152 }
+33
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 417 417 $xactions[] = $mention_xaction; 418 418 } 419 419 420 + $xactions = $this->expandTransactions($object, $xactions); 420 421 $xactions = $this->combineTransactions($xactions); 421 422 422 423 foreach ($xactions as $xaction) { ··· 853 854 return null; 854 855 } 855 856 857 + /** 858 + * Optionally expand transactions which imply other effects. For example, 859 + * resigning from a revision in Differential implies removing yourself as 860 + * a reviewer. 861 + */ 862 + private function expandTransactions( 863 + PhabricatorLiskDAO $object, 864 + array $xactions) { 865 + 866 + $results = array(); 867 + foreach ($xactions as $xaction) { 868 + foreach ($this->expandTransaction($object, $xaction) as $expanded) { 869 + $results[] = $expanded; 870 + } 871 + } 872 + 873 + return $results; 874 + } 875 + 876 + protected function expandTransaction( 877 + PhabricatorLiskDAO $object, 878 + PhabricatorApplicationTransaction $xaction) { 879 + return array($xaction); 880 + } 856 881 857 882 /** 858 883 * Attempt to combine similar transactions into a smaller number of total ··· 911 936 $result[$key] = array_merge($value, idx($result, $key, array())); 912 937 } 913 938 $u->setNewValue($result); 939 + 940 + // When combining an "ignore" transaction with a normal transaction, make 941 + // sure we don't propagate the "ignore" flag. 942 + if (!$v->getIgnoreOnNoEffect()) { 943 + $u->setIgnoreOnNoEffect(false); 944 + } 914 945 915 946 return $u; 916 947 } ··· 1128 1159 if ($xaction->getTransactionType() != $type_comment) { 1129 1160 $any_effect = true; 1130 1161 } 1162 + } else if ($xaction->getIgnoreOnNoEffect()) { 1163 + unset($xactions[$key]); 1131 1164 } else { 1132 1165 $no_effect[$key] = $xaction; 1133 1166 }
+18 -5
src/applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php
··· 32 32 $only_empty_comment = (count($xactions) == 1) && 33 33 (head($xactions)->getTransactionType() == $type_comment); 34 34 35 + $count = new PhutilNumber(count($xactions)); 36 + 35 37 if ($ex->hasAnyEffect()) { 36 - $title = pht('%d Action(s) With No Effect', count($xactions)); 38 + $title = pht('%d Action(s) With No Effect', $count); 39 + $head = pht('Some of your %d action(s) have no effect:', $count); 37 40 $tail = pht('Apply remaining actions?'); 38 - $continue = pht('Apply Other Actions'); 41 + $continue = pht('Apply Remaining Actions'); 39 42 } else if ($ex->hasComment()) { 40 43 $title = pht('Post as Comment'); 44 + $head = pht('The %d action(s) you are taking have no effect:', $count); 41 45 $tail = pht('Do you want to post your comment anyway?'); 42 46 $continue = pht('Post Comment'); 43 47 } else if ($only_empty_comment) { 44 48 // Special case this since it's common and we can give the user a nicer 45 49 // dialog than "Action Has No Effect". 46 50 $title = pht('Empty Comment'); 51 + $head = null; 47 52 $tail = null; 48 53 $continue = null; 49 54 } else { 50 - $title = pht('%d Action(s) Have No Effect', count($xactions)); 55 + $title = pht('%d Action(s) Have No Effect', $count); 56 + $head = pht('The %d action(s) you are taking have no effect:', $count); 51 57 $tail = null; 52 58 $continue = null; 53 59 } ··· 56 62 ->setUser($request->getUser()) 57 63 ->setTitle($title); 58 64 65 + $dialog->appendChild($head); 66 + 67 + $list = array(); 59 68 foreach ($xactions as $xaction) { 60 - $dialog->appendChild( 61 - phutil_tag('p', array(), $xaction->getNoEffectDescription())); 69 + $list[] = phutil_tag( 70 + 'li', 71 + array(), 72 + $xaction->getNoEffectDescription()); 62 73 } 74 + 75 + $dialog->appendChild(phutil_tag('ul', array(), $list)); 63 76 $dialog->appendChild($tail); 64 77 65 78 if ($continue) {
+18
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 31 31 private $viewer = self::ATTACHABLE; 32 32 private $object = self::ATTACHABLE; 33 33 34 + private $ignoreOnNoEffect; 35 + 36 + 37 + /** 38 + * Flag this transaction as a pure side-effect which should be ignored when 39 + * applying transactions if it has no effect, even if transaction application 40 + * would normally fail. This both provides users with better error messages 41 + * and allows transactions to perform optional side effects. 42 + */ 43 + public function setIgnoreOnNoEffect($ignore) { 44 + $this->ignoreOnNoEffect = $ignore; 45 + return $this; 46 + } 47 + 48 + public function getIgnoreOnNoEffect() { 49 + return $this->ignoreOnNoEffect; 50 + } 51 + 34 52 abstract public function getApplicationTransactionType(); 35 53 36 54 private function getApplicationObjectTypeName() {
+20
src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php
··· 215 215 'Actions With No Effect', 216 216 ), 217 217 218 + 'Some of your %d action(s) have no effect:' => array( 219 + 'One of your actions has no effect:', 220 + 'Some of your actions have no effect:', 221 + ), 222 + 223 + 'Apply remaining %d action(s)?' => array( 224 + 'Apply remaining action?', 225 + 'Apply remaining actions?', 226 + ), 227 + 228 + 'Apply %d Other Action(s)' => array( 229 + 'Apply Remaining Action', 230 + 'Apply Remaining Actions', 231 + ), 232 + 233 + 'The %d action(s) you are taking have no effect:' => array( 234 + 'The action you are taking has no effect:', 235 + 'The actions you are taking have no effect:', 236 + ), 237 + 218 238 '%s edited post(s), added %d: %s; removed %d: %s.' => 219 239 '%s edited posts, added: %3$s; removed: %5$s', 220 240
+1 -1
webroot/rsrc/css/aphront/dialog-view.css
··· 112 112 width: 50%; 113 113 } 114 114 115 - .aphront-access-dialog ul { 115 + .aphront-dialog-view ul { 116 116 margin: 12px 24px; 117 117 list-style: circle; 118 118 }