@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

Differential - add DifferentialDraft to track whether revisions have draft feedback or not

Summary: ...do it somewhat generically, so we could fairly easily add this to other applications. Fixes T3496. I got a wee bit lazy and decided not to migrate existing drafts. My excuses aside from laziness are doing it this way will let us see if anyone complains, we can always do a migration later if people do complain, and there's likely to be a lot of garbage data for older / bigger installs, and the migration didn't seem worth itgiven it would also likely be expensive in these cases.

Test Plan: made a draft inline comment on DX and observed DX had a note icon on Differential home page. made a draft comment on DX and observed DX had a note icon on Differential home page. deleted a draft inline comment and noted icon disappeared from Differential homepage. Submitted a draft comment + inline comment and noted icon disappeared.

Reviewers: epriestley

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T3496

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

+152 -50
+9
resources/sql/autopatches/20140218.differentialdraft.sql
··· 1 + CREATE TABLE {$NAMESPACE}_differential.differential_draft( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + draftKey VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + dateCreated INT UNSIGNED NOT NULL, 7 + dateModified INT UNSIGNED NOT NULL, 8 + UNIQUE KEY `key_unique` (objectPHID, authorPHID, draftKey) 9 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+3 -1
src/__phutil_library_map__.php
··· 378 378 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 379 379 'DifferentialDiffViewPolicyFieldSpecification' => 'applications/differential/field/specification/DifferentialDiffViewPolicyFieldSpecification.php', 380 380 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', 381 + 'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php', 381 382 'DifferentialEditPolicyFieldSpecification' => 'applications/differential/field/specification/DifferentialEditPolicyFieldSpecification.php', 382 383 'DifferentialException' => 'applications/differential/exception/DifferentialException.php', 383 384 'DifferentialExceptionMail' => 'applications/differential/mail/DifferentialExceptionMail.php', ··· 2914 2915 'DifferentialDiffViewController' => 'DifferentialController', 2915 2916 'DifferentialDiffViewPolicyFieldSpecification' => 'DifferentialFieldSpecification', 2916 2917 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 2918 + 'DifferentialDraft' => 'DifferentialDAO', 2917 2919 'DifferentialEditPolicyFieldSpecification' => 'DifferentialFieldSpecification', 2918 2920 'DifferentialException' => 'Exception', 2919 2921 'DifferentialExceptionMail' => 'DifferentialMail', ··· 3957 3959 'PhabricatorCalendarEventOverlapException' => 'Exception', 3958 3960 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3959 3961 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', 3960 - 'PhabricatorCalendarEventViewController' => 'PhabricatorDashboardController', 3962 + 'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController', 3961 3963 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 3962 3964 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 3963 3965 'PhabricatorCalendarPHIDTypeEvent' => 'PhabricatorPHIDType',
+14 -2
src/applications/differential/controller/DifferentialCommentPreviewController.php
··· 119 119 $metadata['action'] = $action; 120 120 } 121 121 122 - id(new PhabricatorDraft()) 122 + $draft_key = 'differential-comment-'.$this->id; 123 + $draft = id(new PhabricatorDraft()) 123 124 ->setAuthorPHID($viewer->getPHID()) 124 - ->setDraftKey('differential-comment-'.$this->id) 125 + ->setDraftKey($draft_key) 125 126 ->setDraft($request->getStr('content')) 126 127 ->setMetadata($metadata) 127 128 ->replaceOrDelete(); 129 + if ($draft->isDeleted()) { 130 + DifferentialDraft::deleteHasDraft( 131 + $viewer->getPHID(), 132 + $revision->getPHID(), 133 + $draft_key); 134 + } else { 135 + DifferentialDraft::markHasDraft( 136 + $viewer->getPHID(), 137 + $revision->getPHID(), 138 + $draft_key); 139 + } 128 140 129 141 return id(new AphrontAjaxResponse()) 130 142 ->setContent((string)phutil_implode_html('', $view->buildEvents()));
+3 -1
src/applications/differential/controller/DifferentialCommentSaveController.php
··· 74 74 75 75 // TODO: Diff change detection? 76 76 77 + $user = $request->getUser(); 77 78 $draft = id(new PhabricatorDraft())->loadOneWhere( 78 79 'authorPHID = %s AND draftKey = %s', 79 - $request->getUser()->getPHID(), 80 + $user->getPHID(), 80 81 'differential-comment-'.$revision->getID()); 81 82 if ($draft) { 82 83 $draft->delete(); 83 84 } 85 + DifferentialDraft::deleteAllDrafts($user->getPHID(), $revision->getPHID()); 84 86 85 87 return id(new AphrontRedirectResponse()) 86 88 ->setURI('/D'.$revision->getID());
+19
src/applications/differential/controller/DifferentialInlineCommentEditController.php
··· 74 74 return true; 75 75 } 76 76 77 + protected function deleteComment(PhabricatorInlineCommentInterface $inline) { 78 + $inline->openTransaction(); 79 + DifferentialDraft::deleteHasDraft( 80 + $inline->getAuthorPHID(), 81 + $inline->getRevisionPHID(), 82 + $inline->getPHID()); 83 + $inline->delete(); 84 + $inline->saveTransaction(); 85 + } 86 + 87 + protected function saveComment(PhabricatorInlineCommentInterface $inline) { 88 + $inline->openTransaction(); 89 + $inline->save(); 90 + DifferentialDraft::markHasDraft( 91 + $inline->getAuthorPHID(), 92 + $inline->getRevisionPHID(), 93 + $inline->getPHID()); 94 + $inline->saveTransaction(); 95 + } 77 96 }
+10 -41
src/applications/differential/query/DifferentialRevisionQuery.php
··· 36 36 private $responsibles = array(); 37 37 private $branches = array(); 38 38 private $arcanistProjectPHIDs = array(); 39 - private $draftRevisions = array(); 40 39 private $repositoryPHIDs; 41 40 42 41 private $order = 'order-modified'; ··· 468 467 $table = new DifferentialRevision(); 469 468 $conn_r = $table->establishConnection('r'); 470 469 471 - if ($this->draftAuthors) { 472 - $this->draftRevisions = array(); 473 - 474 - $draft_key = 'differential-comment-'; 475 - $drafts = id(new PhabricatorDraft())->loadAllWhere( 476 - 'authorPHID IN (%Ls) AND draftKey LIKE %> AND draft != %s', 477 - $this->draftAuthors, 478 - $draft_key, 479 - ''); 480 - $len = strlen($draft_key); 481 - foreach ($drafts as $draft) { 482 - $this->draftRevisions[] = substr($draft->getDraftKey(), $len); 483 - } 484 - 485 - // TODO: Restore this after drafts are sorted out. It's now very 486 - // expensive to get revision IDs. 487 - 488 - /* 489 - 490 - $inlines = id(new DifferentialInlineCommentQuery()) 491 - ->withDraftsByAuthors($this->draftAuthors) 492 - ->execute(); 493 - foreach ($inlines as $inline) { 494 - $this->draftRevisions[] = $inline->getRevisionID(); 495 - } 496 - 497 - */ 498 - 499 - if (!$this->draftRevisions) { 500 - return array(); 501 - } 502 - } 503 - 504 470 $selects = array(); 505 471 506 472 // NOTE: If the query includes "responsiblePHIDs", we execute it as a ··· 632 598 $this->reviewers); 633 599 } 634 600 601 + if ($this->draftAuthors) { 602 + $differential_draft = new DifferentialDraft(); 603 + $joins[] = qsprintf( 604 + $conn_r, 605 + 'JOIN %T has_draft ON has_draft.objectPHID = r.phid '. 606 + 'AND has_draft.authorPHID IN (%Ls)', 607 + $differential_draft->getTableName(), 608 + $this->draftAuthors); 609 + } 610 + 635 611 $joins = implode(' ', $joins); 636 612 637 613 return $joins; ··· 663 639 $conn_r, 664 640 'r.authorPHID IN (%Ls)', 665 641 $this->authors); 666 - } 667 - 668 - if ($this->draftRevisions) { 669 - $where[] = qsprintf( 670 - $conn_r, 671 - 'r.id IN (%Ld)', 672 - $this->draftRevisions); 673 642 } 674 643 675 644 if ($this->revIDs) {
+51
src/applications/differential/storage/DifferentialDraft.php
··· 1 + <?php 2 + 3 + final class DifferentialDraft extends DifferentialDAO { 4 + 5 + protected $objectPHID; 6 + protected $authorPHID; 7 + protected $draftKey; 8 + 9 + public static function markHasDraft( 10 + $author_phid, 11 + $object_phid, 12 + $draft_key) { 13 + try { 14 + id(new DifferentialDraft()) 15 + ->setObjectPHID($object_phid) 16 + ->setAuthorPHID($author_phid) 17 + ->setDraftKey($draft_key) 18 + ->save(); 19 + } catch (AphrontQueryDuplicateKeyException $ex) { 20 + // no worries 21 + } 22 + } 23 + 24 + public static function deleteHasDraft( 25 + $author_phid, 26 + $object_phid, 27 + $draft_key) { 28 + $draft = id(new DifferentialDraft())->loadOneWhere( 29 + 'objectPHID = %s AND authorPHID = %s AND draftKey = %s', 30 + $object_phid, 31 + $author_phid, 32 + $draft_key); 33 + if ($draft) { 34 + $draft->delete(); 35 + } 36 + } 37 + 38 + public static function deleteAllDrafts( 39 + $author_phid, 40 + $object_phid) { 41 + 42 + $drafts = id(new DifferentialDraft())->loadAllWhere( 43 + 'objectPHID = %s AND authorPHID = %s', 44 + $object_phid, 45 + $author_phid); 46 + foreach ($drafts as $draft) { 47 + $draft->delete(); 48 + } 49 + } 50 + 51 + }
+15
src/applications/differential/storage/DifferentialInlineComment.php
··· 28 28 return $this->proxy; 29 29 } 30 30 31 + public function openTransaction() { 32 + $this->proxy->openTransaction(); 33 + } 34 + 35 + public function saveTransaction() { 36 + $this->proxy->saveTransaction(); 37 + } 31 38 32 39 public function save() { 33 40 $this->getTransactionCommentForSave()->save(); ··· 43 50 44 51 public function getID() { 45 52 return $this->proxy->getID(); 53 + } 54 + 55 + public function getPHID() { 56 + return $this->proxy->getPHID(); 46 57 } 47 58 48 59 public static function newFromModernComment( ··· 139 150 public function setRevision(DifferentialRevision $revision) { 140 151 $this->proxy->setRevisionPHID($revision->getPHID()); 141 152 return $this; 153 + } 154 + 155 + public function getRevisionPHID() { 156 + return $this->proxy->getRevisionPHID(); 142 157 } 143 158 144 159 // Although these are purely transitional, they're also *extra* dumb.
+8
src/applications/diffusion/controller/DiffusionInlineCommentController.php
··· 76 76 return true; 77 77 } 78 78 79 + protected function deleteComment(PhabricatorInlineCommentInterface $inline) { 80 + return $inline->delete(); 81 + } 82 + 83 + protected function saveComment(PhabricatorInlineCommentInterface $inline) { 84 + return $inline->save(); 85 + } 86 + 79 87 }
+11
src/applications/draft/storage/PhabricatorDraft.php
··· 7 7 protected $draft; 8 8 protected $metadata = array(); 9 9 10 + private $deleted = false; 11 + 10 12 public function getConfiguration() { 11 13 return array( 12 14 self::CONFIG_SERIALIZATION => array( ··· 23 25 $this->getTableName(), 24 26 $this->authorPHID, 25 27 $this->draftKey); 28 + $this->deleted = true; 26 29 return $this; 27 30 } 28 31 return parent::replace(); 32 + } 33 + 34 + protected function didDelete() { 35 + $this->deleted = true; 36 + } 37 + 38 + public function isDeleted() { 39 + return $this->deleted; 29 40 } 30 41 31 42 public static function newFromUserAndKey(PhabricatorUser $user, $key) {
+9 -5
src/infrastructure/diff/PhabricatorInlineCommentController.php
··· 6 6 abstract protected function createComment(); 7 7 abstract protected function loadComment($id); 8 8 abstract protected function loadCommentForEdit($id); 9 + abstract protected function deleteComment( 10 + PhabricatorInlineCommentInterface $inline); 11 + abstract protected function saveComment( 12 + PhabricatorInlineCommentInterface $inline); 9 13 10 14 private $changesetID; 11 15 private $isNewFile; ··· 60 64 $inline = $this->loadCommentForEdit($this->getCommentID()); 61 65 62 66 if ($request->isFormPost()) { 63 - $inline->delete(); 67 + $this->deleteComment($inline); 64 68 return $this->buildEmptyResponse(); 65 69 } 66 70 ··· 86 90 if ($request->isFormPost()) { 87 91 if (strlen($text)) { 88 92 $inline->setContent($text); 89 - $inline->save(); 93 + $this->saveComment($inline); 90 94 return $this->buildRenderedCommentResponse( 91 95 $inline, 92 96 $this->getIsOnRight()); 93 97 } else { 94 - $inline->delete(); 98 + $this->deleteComment($inline); 95 99 return $this->buildEmptyResponse(); 96 100 } 97 101 } ··· 122 126 ->setLineNumber($this->getLineNumber()) 123 127 ->setLineLength($this->getLineLength()) 124 128 ->setIsNewFile($this->getIsNewFile()) 125 - ->setContent($text) 126 - ->save(); 129 + ->setContent($text); 130 + $this->saveComment($inline); 127 131 128 132 return $this->buildRenderedCommentResponse( 129 133 $inline,