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

Make yellow "draft" bubbles more generic

Summary:
Fixes T12095. Ref T6660. The old code for this was specific to Differential, using the `DifferentialDraft` table.

Instead, make the `EditEngine` / `VersionedDraft` code create and remove a `<objectPHID, authorPHID>` edge when a particular author creates drafts.

Some applications have drafts beyond `VersionedDrafts`, notably inline comments. Before writing "yes, draft" or "no, no draft", ask the object if it has any custom draft stuff we need to know about.

This should fix all the yellow bubble bugs I created in T11114 and allow us to bring the feature to Audit fairly easily.

Test Plan: Created and deleted comments and inlines, reloading the list view after each change. Couldn't find a way to break the list view anymore.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12095, T6660

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

+234 -77
+10
src/__phutil_library_map__.php
··· 519 519 'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php', 520 520 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php', 521 521 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', 522 + 'DifferentialRevisionDraftEngine' => 'applications/differential/engine/DifferentialRevisionDraftEngine.php', 522 523 'DifferentialRevisionEditConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionEditConduitAPIMethod.php', 523 524 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 524 525 'DifferentialRevisionEditEngine' => 'applications/differential/editor/DifferentialRevisionEditEngine.php', ··· 2055 2056 'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php', 2056 2057 'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php', 2057 2058 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 2059 + 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', 2058 2060 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 2059 2061 'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php', 2060 2062 'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php', ··· 2550 2552 'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php', 2551 2553 'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php', 2552 2554 'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php', 2555 + 'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php', 2556 + 'PhabricatorDraftInterface' => 'applications/transactions/draft/PhabricatorDraftInterface.php', 2553 2557 'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php', 2554 2558 'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php', 2555 2559 'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php', ··· 3095 3099 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php', 3096 3100 'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php', 3097 3101 'PhabricatorObjectHasContributorEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasContributorEdgeType.php', 3102 + 'PhabricatorObjectHasDraftEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasDraftEdgeType.php', 3098 3103 'PhabricatorObjectHasFileEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php', 3099 3104 'PhabricatorObjectHasJiraIssueEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasJiraIssueEdgeType.php', 3100 3105 'PhabricatorObjectHasSubscriberEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasSubscriberEdgeType.php', ··· 5210 5215 'PhabricatorProjectInterface', 5211 5216 'PhabricatorFulltextInterface', 5212 5217 'PhabricatorConduitResultInterface', 5218 + 'PhabricatorDraftInterface', 5213 5219 ), 5214 5220 'DifferentialRevisionAbandonTransaction' => 'DifferentialRevisionActionTransaction', 5215 5221 'DifferentialRevisionAcceptTransaction' => 'DifferentialRevisionReviewTransaction', ··· 5226 5232 'DifferentialRevisionControlSystem' => 'Phobject', 5227 5233 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'PhabricatorEdgeType', 5228 5234 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', 5235 + 'DifferentialRevisionDraftEngine' => 'PhabricatorDraftEngine', 5229 5236 'DifferentialRevisionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 5230 5237 'DifferentialRevisionEditController' => 'DifferentialController', 5231 5238 'DifferentialRevisionEditEngine' => 'PhabricatorEditEngine', ··· 6981 6988 'PhabricatorBotUser' => 'PhabricatorBotTarget', 6982 6989 'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler', 6983 6990 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 6991 + 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', 6984 6992 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 6985 6993 'PhabricatorBulkContentSource' => 'PhabricatorContentSource', 6986 6994 'PhabricatorBusyUIExample' => 'PhabricatorUIExample', ··· 7559 7567 'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication', 7560 7568 'PhabricatorDraft' => 'PhabricatorDraftDAO', 7561 7569 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', 7570 + 'PhabricatorDraftEngine' => 'Phobject', 7562 7571 'PhabricatorDrydockApplication' => 'PhabricatorApplication', 7563 7572 'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants', 7564 7573 'PhabricatorEdgeConstants' => 'Phobject', ··· 8172 8181 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'PhabricatorEdgeType', 8173 8182 'PhabricatorObjectHasAsanaTaskEdgeType' => 'PhabricatorEdgeType', 8174 8183 'PhabricatorObjectHasContributorEdgeType' => 'PhabricatorEdgeType', 8184 + 'PhabricatorObjectHasDraftEdgeType' => 'PhabricatorEdgeType', 8175 8185 'PhabricatorObjectHasFileEdgeType' => 'PhabricatorEdgeType', 8176 8186 'PhabricatorObjectHasJiraIssueEdgeType' => 'PhabricatorEdgeType', 8177 8187 'PhabricatorObjectHasSubscriberEdgeType' => 'PhabricatorEdgeType',
+13 -16
src/applications/differential/controller/DifferentialInlineCommentEditController.php
··· 152 152 153 153 protected function deleteComment(PhabricatorInlineCommentInterface $inline) { 154 154 $inline->openTransaction(); 155 - 156 155 $inline->setIsDeleted(1)->save(); 157 - DifferentialDraft::deleteHasDraft( 158 - $inline->getAuthorPHID(), 159 - $inline->getRevisionPHID(), 160 - $inline->getPHID()); 161 - 156 + $this->syncDraft(); 162 157 $inline->saveTransaction(); 163 158 } 164 159 165 160 protected function undeleteComment( 166 161 PhabricatorInlineCommentInterface $inline) { 167 162 $inline->openTransaction(); 168 - 169 163 $inline->setIsDeleted(0)->save(); 170 - DifferentialDraft::markHasDraft( 171 - $inline->getAuthorPHID(), 172 - $inline->getRevisionPHID(), 173 - $inline->getPHID()); 174 - 164 + $this->syncDraft(); 175 165 $inline->saveTransaction(); 176 166 } 177 167 178 168 protected function saveComment(PhabricatorInlineCommentInterface $inline) { 179 169 $inline->openTransaction(); 180 170 $inline->save(); 181 - DifferentialDraft::markHasDraft( 182 - $inline->getAuthorPHID(), 183 - $inline->getRevisionPHID(), 184 - $inline->getPHID()); 171 + $this->syncDraft(); 185 172 $inline->saveTransaction(); 186 173 } 187 174 ··· 222 209 $table->getTableName(), 223 210 $viewer->getPHID(), 224 211 $ids); 212 + } 213 + 214 + private function syncDraft() { 215 + $viewer = $this->getViewer(); 216 + $revision = $this->loadRevision(); 217 + 218 + $revision->newDraftEngine() 219 + ->setObject($revision) 220 + ->setViewer($viewer) 221 + ->synchronize(); 225 222 } 226 223 227 224 }
+17
src/applications/differential/engine/DifferentialRevisionDraftEngine.php
··· 1 + <?php 2 + 3 + final class DifferentialRevisionDraftEngine 4 + extends PhabricatorDraftEngine { 5 + 6 + protected function hasCustomDraftContent() { 7 + $viewer = $this->getViewer(); 8 + $revision = $this->getObject(); 9 + 10 + $inlines = DifferentialTransactionQuery::loadUnsubmittedInlineComments( 11 + $viewer, 12 + $revision); 13 + 14 + return (bool)$inlines; 15 + } 16 + 17 + }
+32 -13
src/applications/differential/query/DifferentialRevisionQuery.php
··· 473 473 } 474 474 475 475 if ($this->needDrafts) { 476 - $drafts = id(new DifferentialDraft())->loadAllWhere( 477 - 'authorPHID = %s AND objectPHID IN (%Ls)', 478 - $viewer->getPHID(), 479 - mpull($revisions, 'getPHID')); 480 - $drafts = mgroup($drafts, 'getObjectPHID'); 481 - foreach ($revisions as $revision) { 482 - $revision->attachDrafts( 483 - $viewer, 484 - idx($drafts, $revision->getPHID(), array())); 476 + $viewer_phid = $viewer->getPHID(); 477 + $draft_type = PhabricatorObjectHasDraftEdgeType::EDGECONST; 478 + 479 + if (!$viewer_phid) { 480 + // Viewers without a valid PHID can never have drafts. 481 + foreach ($revisions as $revision) { 482 + $revision->attachHasDraft($viewer, false); 483 + } 484 + } else { 485 + $edge_query = id(new PhabricatorEdgeQuery()) 486 + ->withSourcePHIDs(mpull($revisions, 'getPHID')) 487 + ->withEdgeTypes( 488 + array( 489 + $draft_type, 490 + )) 491 + ->withDestinationPHIDs(array($viewer_phid)); 492 + 493 + $edge_query->execute(); 494 + 495 + foreach ($revisions as $revision) { 496 + $has_draft = (bool)$edge_query->getDestinationPHIDs( 497 + array( 498 + $revision->getPHID(), 499 + )); 500 + 501 + $revision->attachHasDraft($viewer, $has_draft); 502 + } 485 503 } 486 504 } 487 505 ··· 621 639 } 622 640 623 641 if ($this->draftAuthors) { 624 - $differential_draft = new DifferentialDraft(); 625 642 $joins[] = qsprintf( 626 643 $conn_r, 627 - 'JOIN %T has_draft ON has_draft.objectPHID = r.phid '. 628 - 'AND has_draft.authorPHID IN (%Ls)', 629 - $differential_draft->getTableName(), 644 + 'JOIN %T has_draft ON has_draft.srcPHID = r.phid 645 + AND has_draft.type = %s 646 + AND has_draft.dstPHID IN (%Ls)', 647 + PhabricatorEdgeConfig::TABLE_NAME_EDGE, 648 + PhabricatorObjectHasDraftEdgeType::EDGECONST, 630 649 $this->draftAuthors); 631 650 } 632 651
-42
src/applications/differential/storage/DifferentialDraft.php
··· 20 20 ) + parent::getConfiguration(); 21 21 } 22 22 23 - public static function markHasDraft( 24 - $author_phid, 25 - $object_phid, 26 - $draft_key) { 27 - try { 28 - id(new DifferentialDraft()) 29 - ->setObjectPHID($object_phid) 30 - ->setAuthorPHID($author_phid) 31 - ->setDraftKey($draft_key) 32 - ->save(); 33 - } catch (AphrontDuplicateKeyQueryException $ex) { 34 - // no worries 35 - } 36 - } 37 - 38 - public static function deleteHasDraft( 39 - $author_phid, 40 - $object_phid, 41 - $draft_key) { 42 - $draft = id(new DifferentialDraft())->loadOneWhere( 43 - 'objectPHID = %s AND authorPHID = %s AND draftKey = %s', 44 - $object_phid, 45 - $author_phid, 46 - $draft_key); 47 - if ($draft) { 48 - $draft->delete(); 49 - } 50 - } 51 - 52 - public static function deleteAllDrafts( 53 - $author_phid, 54 - $object_phid) { 55 - 56 - $drafts = id(new DifferentialDraft())->loadAllWhere( 57 - 'objectPHID = %s AND authorPHID = %s', 58 - $object_phid, 59 - $author_phid); 60 - foreach ($drafts as $draft) { 61 - $draft->delete(); 62 - } 63 - } 64 - 65 23 }
+14 -5
src/applications/differential/storage/DifferentialRevision.php
··· 15 15 PhabricatorDestructibleInterface, 16 16 PhabricatorProjectInterface, 17 17 PhabricatorFulltextInterface, 18 - PhabricatorConduitResultInterface { 18 + PhabricatorConduitResultInterface, 19 + PhabricatorDraftInterface { 19 20 20 21 protected $title = ''; 21 22 protected $originalTitle; ··· 488 489 return $this; 489 490 } 490 491 491 - public function getDrafts(PhabricatorUser $viewer) { 492 - return $this->assertAttachedKey($this->drafts, $viewer->getPHID()); 492 + public function getHasDraft(PhabricatorUser $viewer) { 493 + return $this->assertAttachedKey($this->drafts, $viewer->getCacheFragment()); 493 494 } 494 495 495 - public function attachDrafts(PhabricatorUser $viewer, array $drafts) { 496 - $this->drafts[$viewer->getPHID()] = $drafts; 496 + public function attachHasDraft(PhabricatorUser $viewer, $has_draft) { 497 + $this->drafts[$viewer->getCacheFragment()] = $has_draft; 497 498 return $this; 498 499 } 499 500 ··· 733 734 734 735 public function getConduitSearchAttachments() { 735 736 return array(); 737 + } 738 + 739 + 740 + /* -( PhabricatorDraftInterface )------------------------------------------ */ 741 + 742 + 743 + public function newDraftEngine() { 744 + return new DifferentialRevisionDraftEngine(); 736 745 } 737 746 738 747 }
+1 -1
src/applications/differential/view/DifferentialRevisionListView.php
··· 92 92 ''); 93 93 } 94 94 95 - if ($revision->getDrafts($viewer)) { 95 + if ($revision->getHasDraft($viewer)) { 96 96 $icons['draft'] = true; 97 97 } 98 98
+4
src/applications/transactions/draft/PhabricatorBuiltinDraftEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorBuiltinDraftEngine 4 + extends PhabricatorDraftEngine {}
+98
src/applications/transactions/draft/PhabricatorDraftEngine.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorDraftEngine 4 + extends Phobject { 5 + 6 + private $viewer; 7 + private $object; 8 + private $hasVersionedDraft; 9 + private $versionedDraft; 10 + 11 + final public function setViewer(PhabricatorUser $viewer) { 12 + $this->viewer = $viewer; 13 + return $this; 14 + } 15 + 16 + final public function getViewer() { 17 + return $this->viewer; 18 + } 19 + 20 + final public function setObject($object) { 21 + $this->object = $object; 22 + return $this; 23 + } 24 + 25 + final public function getObject() { 26 + return $this->object; 27 + } 28 + 29 + final public function setVersionedDraft( 30 + PhabricatorVersionedDraft $draft = null) { 31 + $this->hasVersionedDraft = true; 32 + $this->versionedDraft = $draft; 33 + return $this; 34 + } 35 + 36 + final public function getVersionedDraft() { 37 + if (!$this->hasVersionedDraft) { 38 + $draft = PhabricatorVersionedDraft::loadDraft( 39 + $this->getObject()->getPHID(), 40 + $this->getViewer()->getPHID()); 41 + $this->setVersionedDraft($draft); 42 + } 43 + 44 + return $this->versionedDraft; 45 + } 46 + 47 + protected function hasVersionedDraftContent() { 48 + $draft = $this->getVersionedDraft(); 49 + if (!$draft) { 50 + return false; 51 + } 52 + 53 + if ($draft->getProperty('comment')) { 54 + return true; 55 + } 56 + 57 + if ($draft->getProperty('actions')) { 58 + return true; 59 + } 60 + 61 + return false; 62 + } 63 + 64 + protected function hasCustomDraftContent() { 65 + return false; 66 + } 67 + 68 + final protected function hasAnyDraftContent() { 69 + if ($this->hasVersionedDraftContent()) { 70 + return true; 71 + } 72 + 73 + if ($this->hasCustomDraftContent()) { 74 + return true; 75 + } 76 + 77 + return false; 78 + } 79 + 80 + final public function synchronize() { 81 + $object_phid = $this->getObject()->getPHID(); 82 + $viewer_phid = $this->getViewer()->getPHID(); 83 + 84 + $has_draft = $this->hasAnyDraftContent(); 85 + 86 + $draft_type = PhabricatorObjectHasDraftEdgeType::EDGECONST; 87 + $editor = id(new PhabricatorEdgeEditor()); 88 + 89 + if ($has_draft) { 90 + $editor->addEdge($object_phid, $draft_type, $viewer_phid); 91 + } else { 92 + $editor->removeEdge($object_phid, $draft_type, $viewer_phid); 93 + } 94 + 95 + $editor->save(); 96 + } 97 + 98 + }
+7
src/applications/transactions/draft/PhabricatorDraftInterface.php
··· 1 + <?php 2 + 3 + interface PhabricatorDraftInterface { 4 + 5 + public function newDraftEngine(); 6 + 7 + }
+8
src/applications/transactions/edges/PhabricatorObjectHasDraftEdgeType.php
··· 1 + <?php 2 + 3 + final class PhabricatorObjectHasDraftEdgeType 4 + extends PhabricatorEdgeType { 5 + 6 + const EDGECONST = 64; 7 + 8 + }
+30
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 1746 1746 $viewer->getPHID(), 1747 1747 $current_version); 1748 1748 1749 + $is_empty = (!strlen($comment_text) && !$actions); 1750 + 1749 1751 $draft 1750 1752 ->setProperty('comment', $comment_text) 1751 1753 ->setProperty('actions', $actions) 1752 1754 ->save(); 1755 + 1756 + $draft_engine = $this->newDraftEngine($object); 1757 + if ($draft_engine) { 1758 + $draft_engine 1759 + ->setVersionedDraft($draft) 1760 + ->synchronize(); 1761 + } 1753 1762 } 1754 1763 } 1755 1764 ··· 1831 1840 $object->getPHID(), 1832 1841 $viewer->getPHID(), 1833 1842 $this->loadDraftVersion($object)); 1843 + 1844 + $draft_engine = $this->newDraftEngine($object); 1845 + if ($draft_engine) { 1846 + $draft_engine 1847 + ->setVersionedDraft(null) 1848 + ->synchronize(); 1849 + } 1834 1850 } 1835 1851 1836 1852 if ($request->isAjax() && $is_preview) { ··· 1845 1861 return id(new AphrontRedirectResponse()) 1846 1862 ->setURI($view_uri); 1847 1863 } 1864 + } 1865 + 1866 + protected function newDraftEngine($object) { 1867 + $viewer = $this->getViewer(); 1868 + 1869 + if ($object instanceof PhabricatorDraftInterface) { 1870 + $engine = $object->newDraftEngine(); 1871 + } else { 1872 + $engine = new PhabricatorBuiltinDraftEngine(); 1873 + } 1874 + 1875 + return $engine 1876 + ->setObject($object) 1877 + ->setViewer($viewer); 1848 1878 } 1849 1879 1850 1880