@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 PhabricatorApplicationTransactionInterface on ManiphestTask

Summary:
Ref T5245. A very long time ago I had this terrible idea that we'd let objects react to edges being added and insert transactions in response.

This turned out to be a clearly bad idea very quickly, for like 15 different reasons. A big issue is that it inverts the responsibilities of editors. It's also just clumsy and messy.

We now have `PhabricatorApplicationTransactionInterface` instead, which mostly provides a cleaner way to deal with this.

Implement `PhabricatorApplicationTransactionInterface`, implicitly moving all the attach actions (task/task, task/revision, task/commit, task/mock) to proper edge transactions.

The cost of this is that the inverse edges don't write transactions -- if you attach an object to another object, only the object you were acting on posts a transaction record. This is sort of buggy anyway already. I'll fix this in the next diff.

Test Plan: Attached tasks, revisions and mocks to a task, then detached them.

Reviewers: chad, btrahan, joshuaspence

Reviewed By: joshuaspence

Subscribers: epriestley

Maniphest Tasks: T5245

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

+40 -181
+1 -2
src/__phutil_library_map__.php
··· 926 926 'ManiphestCustomFieldStorage' => 'applications/maniphest/storage/ManiphestCustomFieldStorage.php', 927 927 'ManiphestCustomFieldStringIndex' => 'applications/maniphest/storage/ManiphestCustomFieldStringIndex.php', 928 928 'ManiphestDAO' => 'applications/maniphest/storage/ManiphestDAO.php', 929 - 'ManiphestEdgeEventListener' => 'applications/maniphest/event/ManiphestEdgeEventListener.php', 930 929 'ManiphestExcelDefaultFormat' => 'applications/maniphest/export/ManiphestExcelDefaultFormat.php', 931 930 'ManiphestExcelFormat' => 'applications/maniphest/export/ManiphestExcelFormat.php', 932 931 'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php', ··· 3674 3673 'ManiphestCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 3675 3674 'ManiphestCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 3676 3675 'ManiphestDAO' => 'PhabricatorLiskDAO', 3677 - 'ManiphestEdgeEventListener' => 'PhabricatorEventListener', 3678 3676 'ManiphestExcelDefaultFormat' => 'ManiphestExcelFormat', 3679 3677 'ManiphestExportController' => 'ManiphestController', 3680 3678 'ManiphestHovercardEventListener' => 'PhabricatorEventListener', ··· 3697 3695 5 => 'PhrequentTrackableInterface', 3698 3696 6 => 'PhabricatorCustomFieldInterface', 3699 3697 7 => 'PhabricatorDestructableInterface', 3698 + 8 => 'PhabricatorApplicationTransactionInterface', 3700 3699 ), 3701 3700 'ManiphestTaskDescriptionPreviewController' => 'ManiphestController', 3702 3701 'ManiphestTaskDetailController' => 'ManiphestController',
+1 -7
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 9 9 $types = parent::getTransactionTypes(); 10 10 11 11 $types[] = PhabricatorTransactions::TYPE_COMMENT; 12 + $types[] = PhabricatorTransactions::TYPE_EDGE; 12 13 $types[] = ManiphestTransaction::TYPE_PRIORITY; 13 14 $types[] = ManiphestTransaction::TYPE_STATUS; 14 15 $types[] = ManiphestTransaction::TYPE_TITLE; ··· 16 17 $types[] = ManiphestTransaction::TYPE_OWNER; 17 18 $types[] = ManiphestTransaction::TYPE_CCS; 18 19 $types[] = ManiphestTransaction::TYPE_PROJECTS; 19 - $types[] = ManiphestTransaction::TYPE_EDGE; 20 20 $types[] = ManiphestTransaction::TYPE_SUBPRIORITY; 21 21 $types[] = ManiphestTransaction::TYPE_PROJECT_COLUMN; 22 22 $types[] = ManiphestTransaction::TYPE_UNBLOCK; ··· 57 57 return array_values(array_unique($object->getCCPHIDs())); 58 58 case ManiphestTransaction::TYPE_PROJECTS: 59 59 return array_values(array_unique($object->getProjectPHIDs())); 60 - case ManiphestTransaction::TYPE_EDGE: 61 60 case ManiphestTransaction::TYPE_PROJECT_COLUMN: 62 61 // These are pre-populated. 63 62 return $xaction->getOldValue(); ··· 82 81 case ManiphestTransaction::TYPE_STATUS: 83 82 case ManiphestTransaction::TYPE_TITLE: 84 83 case ManiphestTransaction::TYPE_DESCRIPTION: 85 - case ManiphestTransaction::TYPE_EDGE: 86 84 case ManiphestTransaction::TYPE_SUBPRIORITY: 87 85 case ManiphestTransaction::TYPE_PROJECT_COLUMN: 88 86 case ManiphestTransaction::TYPE_UNBLOCK: ··· 155 153 $object->setProjectPHIDs($xaction->getNewValue()); 156 154 ManiphestTaskProject::updateTaskProjects($object); 157 155 return $object; 158 - case ManiphestTransaction::TYPE_EDGE: 159 - // These are a weird, funky mess and are already being applied by the 160 - // time we reach this. 161 - return; 162 156 case ManiphestTransaction::TYPE_SUBPRIORITY: 163 157 $data = $xaction->getNewValue(); 164 158 $new_sub = $this->getNextSubpriority(
-137
src/applications/maniphest/event/ManiphestEdgeEventListener.php
··· 1 - <?php 2 - 3 - /** 4 - * Listener for Maniphest Task edge events. When some workflow causes task 5 - * edges to be added or removed, we consider the edge edit authoritative but 6 - * duplicate the information into a @{class:ManiphestTansaction} for display. 7 - */ 8 - final class ManiphestEdgeEventListener extends PhabricatorEventListener { 9 - 10 - private $edges = array(); 11 - private $tasks = array(); 12 - 13 - public function register() { 14 - $this->listen(PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES); 15 - $this->listen(PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES); 16 - } 17 - 18 - public function handleEvent(PhutilEvent $event) { 19 - switch ($event->getType()) { 20 - case PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES: 21 - return $this->handleWillEditEvent($event); 22 - case PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES: 23 - return $this->handleDidEditEvent($event); 24 - } 25 - } 26 - 27 - private function handleWillEditEvent(PhutilEvent $event) { 28 - // NOTE: Everything is namespaced by `id` so that we aren't left in an 29 - // inconsistent state if an edit fails to complete (e.g., something throws) 30 - // or an edit happens inside another edit. 31 - 32 - $id = $event->getValue('id'); 33 - 34 - $edges = $this->loadAllEdges($event); 35 - $tasks = array(); 36 - if ($edges) { 37 - // TODO: T603 This should probably all get nuked. Until then, this isn't 38 - // realllllly a policy issue since callers are (or should be) doing 39 - // policy checks anyway. 40 - $tasks = id(new ManiphestTask())->loadAllWhere( 41 - 'phid IN (%Ls)', 42 - array_keys($edges)); 43 - $tasks = mpull($tasks, null, 'getPHID'); 44 - } 45 - 46 - $this->edges[$id] = $edges; 47 - $this->tasks[$id] = $tasks; 48 - } 49 - 50 - private function handleDidEditEvent(PhutilEvent $event) { 51 - $id = $event->getValue('id'); 52 - 53 - $old_edges = $this->edges[$id]; 54 - $tasks = $this->tasks[$id]; 55 - 56 - unset($this->edges[$id]); 57 - unset($this->tasks[$id]); 58 - 59 - $content_source = PhabricatorContentSource::newForSource( 60 - PhabricatorContentSource::SOURCE_LEGACY, 61 - array()); 62 - 63 - $new_edges = $this->loadAllEdges($event); 64 - $editor = id(new ManiphestTransactionEditor()) 65 - ->setActor($event->getUser()) 66 - ->setContentSource($content_source) 67 - ->setContinueOnNoEffect(true) 68 - ->setContinueOnMissingFields(true); 69 - 70 - foreach ($tasks as $phid => $task) { 71 - $xactions = array(); 72 - 73 - $old = $old_edges[$phid]; 74 - $new = $new_edges[$phid]; 75 - 76 - $types = array_keys($old + $new); 77 - foreach ($types as $type) { 78 - $old_type = idx($old, $type, array()); 79 - $new_type = idx($new, $type, array()); 80 - 81 - if ($old_type === $new_type) { 82 - continue; 83 - } 84 - 85 - $xactions[] = id(new ManiphestTransaction()) 86 - ->setTransactionType(ManiphestTransaction::TYPE_EDGE) 87 - ->setOldValue($old_type) 88 - ->setNewValue($new_type) 89 - ->setMetadataValue('edge:type', $type); 90 - } 91 - 92 - if ($xactions) { 93 - $editor->applyTransactions($task, $xactions); 94 - } 95 - } 96 - } 97 - 98 - private function filterEdgesBySourceType(array $edges, $type) { 99 - foreach ($edges as $key => $edge) { 100 - if ($edge['src_type'] !== $type) { 101 - unset($edges[$key]); 102 - } 103 - } 104 - return $edges; 105 - } 106 - 107 - private function loadAllEdges(PhutilEvent $event) { 108 - $add_edges = $event->getValue('add'); 109 - $rem_edges = $event->getValue('rem'); 110 - 111 - $type_task = ManiphestPHIDTypeTask::TYPECONST; 112 - 113 - $all_edges = array_merge($add_edges, $rem_edges); 114 - $all_edges = $this->filterEdgesBySourceType($all_edges, $type_task); 115 - 116 - if (!$all_edges) { 117 - return; 118 - } 119 - 120 - $all_tasks = array(); 121 - $all_types = array(); 122 - foreach ($all_edges as $edge) { 123 - $all_tasks[$edge['src']] = true; 124 - $all_types[$edge['type']] = true; 125 - } 126 - 127 - $all_tasks = array_keys($all_tasks); 128 - $all_types = array_keys($all_types); 129 - 130 - return id(new PhabricatorEdgeQuery()) 131 - ->withSourcePHIDs($all_tasks) 132 - ->withEdgeTypes($all_types) 133 - ->needEdgeData(true) 134 - ->execute(); 135 - } 136 - 137 - }
+18 -1
src/applications/maniphest/storage/ManiphestTask.php
··· 8 8 PhabricatorFlaggableInterface, 9 9 PhrequentTrackableInterface, 10 10 PhabricatorCustomFieldInterface, 11 - PhabricatorDestructableInterface { 11 + PhabricatorDestructableInterface, 12 + PhabricatorApplicationTransactionInterface { 12 13 13 14 const MARKUP_FIELD_DESCRIPTION = 'markup:desc'; 14 15 ··· 301 302 302 303 $this->delete(); 303 304 $this->saveTransaction(); 305 + } 306 + 307 + 308 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 309 + 310 + 311 + public function getApplicationTransactionEditor() { 312 + return new ManiphestTransactionEditor(); 313 + } 314 + 315 + public function getApplicationTransactionObject() { 316 + return $this; 317 + } 318 + 319 + public function getApplicationTransactionTemplate() { 320 + return new ManiphestTransaction(); 304 321 } 305 322 306 323 }
+20 -33
src/applications/search/controller/PhabricatorSearchAttachController.php
··· 57 57 $phids = array_values($phids); 58 58 59 59 if ($edge_type) { 60 - $do_txn = $object instanceof PhabricatorApplicationTransactionInterface; 60 + if (!$object instanceof PhabricatorApplicationTransactionInterface) { 61 + throw new Exception( 62 + pht( 63 + 'Expected object ("%s") to implement interface "%s".', 64 + get_class($object), 65 + 'PhabricatorApplicationTransactionInterface')); 66 + } 67 + 61 68 $old_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 62 69 $this->phid, 63 70 $edge_type); 64 71 $add_phids = $phids; 65 72 $rem_phids = array_diff($old_phids, $add_phids); 66 73 67 - if ($do_txn) { 68 - 69 - $txn_editor = $object->getApplicationTransactionEditor() 70 - ->setActor($user) 71 - ->setContentSourceFromRequest($request); 72 - $txn_template = $object->getApplicationTransactionTemplate() 73 - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 74 - ->setMetadataValue('edge:type', $edge_type) 75 - ->setNewValue(array( 76 - '+' => array_fuse($add_phids), 77 - '-' => array_fuse($rem_phids))); 78 - $txn_editor->applyTransactions( 79 - $object->getApplicationTransactionObject(), 80 - array($txn_template)); 81 - 82 - } else { 83 - 84 - $editor = id(new PhabricatorEdgeEditor()); 85 - $editor->setActor($user); 86 - foreach ($add_phids as $phid) { 87 - $editor->addEdge($this->phid, $edge_type, $phid); 88 - } 89 - foreach ($rem_phids as $phid) { 90 - $editor->removeEdge($this->phid, $edge_type, $phid); 91 - } 92 - 93 - try { 94 - $editor->save(); 95 - } catch (PhabricatorEdgeCycleException $ex) { 96 - $this->raiseGraphCycleException($ex); 97 - } 98 - } 74 + $txn_editor = $object->getApplicationTransactionEditor() 75 + ->setActor($user) 76 + ->setContentSourceFromRequest($request); 77 + $txn_template = $object->getApplicationTransactionTemplate() 78 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 79 + ->setMetadataValue('edge:type', $edge_type) 80 + ->setNewValue(array( 81 + '+' => array_fuse($add_phids), 82 + '-' => array_fuse($rem_phids))); 83 + $txn_editor->applyTransactions( 84 + $object->getApplicationTransactionObject(), 85 + array($txn_template)); 99 86 100 87 return id(new AphrontReloadResponse())->setURI($handle->getURI()); 101 88 } else {
-1
src/infrastructure/events/PhabricatorEventEngine.php
··· 25 25 26 26 // Add builtin listeners. 27 27 $listeners[] = new DarkConsoleEventPluginAPI(); 28 - $listeners[] = new ManiphestEdgeEventListener(); 29 28 30 29 // Add application listeners. 31 30 $applications = PhabricatorApplication::getAllInstalledApplications();