@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.

Update overall revision status after reviewers change

Summary:
Ref T2222. This doesn't feel super clean, but doesn't feel too bad either.

Basically, Differential transactions can have secondary state-based effects (changing the overall revision status) when reviewers resign, are removed, accept, or reject revisions.

To deal with this in ApplicationTransactions, I did this:

- `applyFinalEffects()` can now alter the transaction set (notably, add new ones). This mostly matters for email, notifications and feed.
- In Differential, check for an overall revision state transition in `applyFinalEffects()` (e.g., your reject moving the revision to a rejected state).
- I'm only writing the transaction if the transition is implied and indirect.
- For example, if you "Plan Changes", that action changes the state on its own so there's no implicit state change transaction added.

The transactions themselves are kind of fluff, but it seems useful to keep a record of when state changes occurred in the transaction log. If people complain we can hide/remove them.

Test Plan: {F118143}

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2222

Differential Revision: https://secure.phabricator.com/D8339

+206 -13
+2
src/applications/conpherence/editor/ConpherenceEditor.php
··· 304 304 } 305 305 $participant->save(); 306 306 } 307 + 308 + return $xactions; 307 309 } 308 310 309 311 protected function mergeTransactions(
+93 -4
src/applications/differential/editor/DifferentialTransactionEditor.php
··· 13 13 14 14 $types[] = DifferentialTransaction::TYPE_ACTION; 15 15 $types[] = DifferentialTransaction::TYPE_INLINE; 16 + $types[] = DifferentialTransaction::TYPE_STATUS; 16 17 17 18 /* 18 19 ··· 145 146 case DifferentialTransaction::TYPE_INLINE: 146 147 return; 147 148 case PhabricatorTransactions::TYPE_EDGE: 148 - // TODO: Update review status. 149 149 return; 150 150 case DifferentialTransaction::TYPE_ACTION: 151 151 $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; ··· 166 166 return; 167 167 case DifferentialAction::ACTION_RECLAIM: 168 168 $object->setStatus($status_review); 169 - // TODO: Update review status? 170 169 return; 171 170 case DifferentialAction::ACTION_REOPEN: 172 171 $object->setStatus($status_review); 173 - // TODO: Update review status? 174 172 return; 175 173 case DifferentialAction::ACTION_REQUEST: 176 174 $object->setStatus($status_review); 177 - // TODO: Update review status? 178 175 return; 179 176 case DifferentialAction::ACTION_CLOSE: 180 177 $object->setStatus(ArcanistDifferentialRevisionStatus::CLOSED); ··· 314 311 315 312 return parent::applyCustomExternalTransaction($object, $xaction); 316 313 } 314 + 315 + protected function applyFinalEffects( 316 + PhabricatorLiskDAO $object, 317 + array $xactions) { 318 + 319 + $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; 320 + $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; 321 + $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; 322 + 323 + $old_status = $object->getStatus(); 324 + switch ($old_status) { 325 + case $status_accepted: 326 + case $status_revision: 327 + case $status_review: 328 + // Load the most up-to-date version of the revision and its reviewers, 329 + // so we don't need to try to deduce the state of reviewers by examining 330 + // all the changes made by the transactions. 331 + $new_revision = id(new DifferentialRevisionQuery()) 332 + ->setViewer($this->getActor()) 333 + ->needReviewerStatus(true) 334 + ->withIDs(array($object->getID())) 335 + ->executeOne(); 336 + if (!$new_revision) { 337 + throw new Exception( 338 + pht('Failed to load revision from transaction finalization.')); 339 + } 340 + 341 + // Try to move a revision to "accepted". We look for: 342 + // 343 + // - at least one accepting reviewer who is a user; and 344 + // - no rejects; and 345 + // - no blocking reviewers. 346 + 347 + $has_accepting_user = false; 348 + $has_rejecting_reviewer = false; 349 + $has_blocking_reviewer = false; 350 + foreach ($new_revision->getReviewerStatus() as $reviewer) { 351 + $reviewer_status = $reviewer->getStatus(); 352 + switch ($reviewer_status) { 353 + case DifferentialReviewerStatus::STATUS_REJECTED: 354 + $has_rejecting_reviewer = true; 355 + break; 356 + case DifferentialReviewerStatus::STATUS_BLOCKING: 357 + $has_blocking_reviewer = true; 358 + break; 359 + case DifferentialReviewerStatus::STATUS_ACCEPTED: 360 + if ($reviewer->isUser()) { 361 + $has_accepting_user = true; 362 + } 363 + break; 364 + } 365 + } 366 + 367 + $new_status = null; 368 + if ($has_accepting_user && 369 + !$has_rejecting_reviewer && 370 + !$has_blocking_reviewer) { 371 + $new_status = $status_accepted; 372 + } else if ($has_rejecting_reviewer) { 373 + // This isn't accepted, and there's at least one rejecting reviewer, 374 + // so the revision needs changes. This usually happens after a 375 + // "reject". 376 + $new_status = $status_revision; 377 + } else if ($old_status == $status_accepted) { 378 + // This revision was accepted, but it no longer satisfies the 379 + // conditions for acceptance. This usually happens after an accepting 380 + // reviewer resigns or is removed. 381 + $new_status = $status_review; 382 + } 383 + 384 + if ($new_status !== null && $new_status != $old_status) { 385 + $xaction = id(new DifferentialTransaction()) 386 + ->setTransactionType(DifferentialTransaction::TYPE_STATUS) 387 + ->setOldValue($old_status) 388 + ->setNewValue($new_status); 389 + 390 + $xaction = $this->populateTransaction($object, $xaction)->save(); 391 + 392 + $xactions[] = $xaction; 393 + 394 + $object->setStatus($new_status)->save(); 395 + } 396 + break; 397 + default: 398 + // Revisions can't transition out of other statuses (like closed or 399 + // abandoned) as a side effect of reviewer status changes. 400 + break; 401 + } 402 + 403 + return $xactions; 404 + } 405 + 317 406 318 407 protected function validateTransaction( 319 408 PhabricatorLiskDAO $object,
+76
src/applications/differential/storage/DifferentialTransaction.php
··· 5 5 const TYPE_INLINE = 'differential:inline'; 6 6 const TYPE_UPDATE = 'differential:update'; 7 7 const TYPE_ACTION = 'differential:action'; 8 + const TYPE_STATUS = 'differential:status'; 8 9 9 10 public function getApplicationName() { 10 11 return 'differential'; ··· 18 19 return new DifferentialTransactionComment(); 19 20 } 20 21 22 + public function shouldHide() { 23 + switch ($this->getTransactionType()) { 24 + case PhabricatorTransactions::TYPE_EDGE: 25 + $old = $this->getOldValue(); 26 + $new = $this->getNewValue(); 27 + $add = array_diff_key($new, $old); 28 + $rem = array_diff_key($old, $new); 29 + 30 + // Hide metadata-only edge transactions. These correspond to users 31 + // accepting or rejecting revisions, but the change is always explicit 32 + // because of the TYPE_ACTION transaction. Rendering these transactions 33 + // just creates clutter. 34 + 35 + if (!$add && !$rem) { 36 + return true; 37 + } 38 + break; 39 + } 40 + 41 + return false; 42 + } 43 + 21 44 public function getTitle() { 22 45 $author_phid = $this->getAuthorPHID(); 23 46 $author_handle = $this->renderHandleLink($author_phid); ··· 45 68 } 46 69 case self::TYPE_ACTION: 47 70 return DifferentialAction::getBasicStoryText($new, $author_handle); 71 + case self::TYPE_STATUS: 72 + switch ($this->getNewValue()) { 73 + case ArcanistDifferentialRevisionStatus::ACCEPTED: 74 + return pht( 75 + 'This revision is now accepted and ready to land.'); 76 + case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 77 + return pht( 78 + 'This revision now requires changes to proceed.'); 79 + case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 80 + return pht( 81 + 'This revision now requires review to proceed.'); 82 + } 48 83 } 49 84 50 85 return parent::getTitle(); ··· 56 91 return 'comment'; 57 92 case self::TYPE_UPDATE: 58 93 return 'refresh'; 94 + case self::TYPE_STATUS: 95 + switch ($this->getNewValue()) { 96 + case ArcanistDifferentialRevisionStatus::ACCEPTED: 97 + return 'enable'; 98 + case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 99 + return 'delete'; 100 + case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 101 + return 'refresh'; 102 + } 103 + break; 59 104 case self::TYPE_ACTION: 60 105 switch ($this->getNewValue()) { 61 106 case DifferentialAction::ACTION_CLOSE: ··· 82 127 return parent::getIcon(); 83 128 } 84 129 130 + public function shouldDisplayGroupWith(array $group) { 131 + 132 + // Never group status changes with other types of actions, they're indirect 133 + // and don't make sense when combined with direct actions. 134 + 135 + $type_status = self::TYPE_STATUS; 136 + 137 + if ($this->getTransactionType() == $type_status) { 138 + return false; 139 + } 140 + 141 + foreach ($group as $xaction) { 142 + if ($xaction->getTransactionType() == $type_status) { 143 + return false; 144 + } 145 + } 146 + 147 + return parent::shouldDisplayGroupWith($group); 148 + } 149 + 150 + 85 151 public function getColor() { 86 152 switch ($this->getTransactionType()) { 87 153 case self::TYPE_UPDATE: 88 154 return PhabricatorTransactions::COLOR_SKY; 155 + case self::TYPE_STATUS: 156 + switch ($this->getNewValue()) { 157 + case ArcanistDifferentialRevisionStatus::ACCEPTED: 158 + return PhabricatorTransactions::COLOR_GREEN; 159 + case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 160 + return PhabricatorTransactions::COLOR_RED; 161 + case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 162 + return PhabricatorTransactions::COLOR_ORANGE; 163 + } 164 + break; 89 165 case self::TYPE_ACTION: 90 166 switch ($this->getNewValue()) { 91 167 case DifferentialAction::ACTION_CLOSE:
+2
src/applications/legalpad/editor/LegalpadDocumentEditor.php
··· 104 104 105 105 $object->save(); 106 106 } 107 + 108 + return $xactions; 107 109 } 108 110 109 111 protected function mergeTransactions(
+2
src/applications/paste/editor/PhabricatorPasteEditor.php
··· 113 113 $this->getActor(), 114 114 $object->getPHID()); 115 115 } 116 + 117 + return $xactions; 116 118 } 117 119 118 120
+2
src/applications/pholio/editor/PholioMockEditor.php
··· 275 275 $image->setMockID($object->getID()); 276 276 $image->save(); 277 277 } 278 + 279 + return $xactions; 278 280 } 279 281 280 282 protected function mergeTransactions(
+29 -9
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 377 377 "implementation!"); 378 378 } 379 379 380 + /** 381 + * Fill in a transaction's common values, like author and content source. 382 + */ 383 + protected function populateTransaction( 384 + PhabricatorLiskDAO $object, 385 + PhabricatorApplicationTransaction $xaction) { 386 + 387 + $actor = $this->getActor(); 388 + 389 + // TODO: This needs to be more sophisticated once we have meta-policies. 390 + $xaction->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC); 391 + $xaction->setEditPolicy($actor->getPHID()); 392 + 393 + $xaction->setAuthorPHID($actor->getPHID()); 394 + $xaction->setContentSource($this->getContentSource()); 395 + $xaction->attachViewer($actor); 396 + $xaction->attachObject($object); 397 + 398 + if ($object->getPHID()) { 399 + $xaction->setObjectPHID($object->getPHID()); 400 + } 401 + 402 + return $xaction; 403 + } 404 + 405 + 380 406 protected function applyFinalEffects( 381 407 PhabricatorLiskDAO $object, 382 408 array $xactions) { 409 + return $xactions; 383 410 } 384 411 385 412 public function setContentSource(PhabricatorContentSource $content_source) { ··· 421 448 $xactions = $this->combineTransactions($xactions); 422 449 423 450 foreach ($xactions as $xaction) { 424 - // TODO: This needs to be more sophisticated once we have meta-policies. 425 - $xaction->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC); 426 - $xaction->setEditPolicy($actor->getPHID()); 427 - 428 - $xaction->setAuthorPHID($actor->getPHID()); 429 - $xaction->setContentSource($this->getContentSource()); 430 - $xaction->attachViewer($this->getActor()); 431 - $xaction->attachObject($object); 451 + $xaction = $this->populateTransaction($object, $xaction); 432 452 } 433 453 434 454 $is_preview = $this->getIsPreview(); ··· 557 577 $this->applyHeraldRules($object, $xactions); 558 578 } 559 579 560 - $this->applyFinalEffects($object, $xactions); 580 + $xactions = $this->applyFinalEffects($object, $xactions); 561 581 562 582 if ($read_locking) { 563 583 $object->endReadLocking();