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

Insert Maniphest transactions when edges are edited

Summary:
- See D2741.
- When EdgeEditor performs edits, emit events.
- Listen for Maniphest edge events and save the changes as transactions.
- Do all this in a reasonably generic way that won't take too much rewriting as we use edges more generally.

Test Plan: Attached and detached commits from tasks, saw reasonable-looking transactions spring into existence.

Reviewers: btrahan, davidreuss

Reviewed By: btrahan

CC: aran

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

+198 -3
+2
src/__phutil_library_map__.php
··· 500 500 'ManiphestController' => 'applications/maniphest/controller/ManiphestController.php', 501 501 'ManiphestDAO' => 'applications/maniphest/storage/ManiphestDAO.php', 502 502 'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/ManiphestDefaultTaskExtensions.php', 503 + 'ManiphestEdgeEventListener' => 'applications/maniphest/event/ManiphestEdgeEventListener.php', 503 504 'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php', 504 505 'ManiphestReplyHandler' => 'applications/maniphest/ManiphestReplyHandler.php', 505 506 'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php', ··· 1527 1528 'ManiphestController' => 'PhabricatorController', 1528 1529 'ManiphestDAO' => 'PhabricatorLiskDAO', 1529 1530 'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions', 1531 + 'ManiphestEdgeEventListener' => 'PhutilEventListener', 1530 1532 'ManiphestExportController' => 'ManiphestController', 1531 1533 'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler', 1532 1534 'ManiphestReportController' => 'ManiphestController',
+2 -1
src/applications/maniphest/constants/ManiphestTransactionType.php
··· 1 1 <?php 2 2 3 3 /* 4 - * Copyright 2011 Facebook, Inc. 4 + * Copyright 2012 Facebook, Inc. 5 5 * 6 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 7 * you may not use this file except in compliance with the License. ··· 29 29 const TYPE_PRIORITY = 'priority'; 30 30 31 31 const TYPE_ATTACH = 'attach'; 32 + const TYPE_EDGE = 'edge'; 32 33 33 34 const TYPE_TITLE = 'title'; 34 35 const TYPE_DESCRIPTION = 'description';
+7
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 69 69 case ManiphestTransactionType::TYPE_PRIORITY: 70 70 $old = $task->getPriority(); 71 71 break; 72 + case ManiphestTransactionType::TYPE_EDGE: 73 + $old = $transaction->getOldValue(); 74 + break; 72 75 case ManiphestTransactionType::TYPE_ATTACH: 73 76 $old = $task->getAttached(); 74 77 break; ··· 172 175 case ManiphestTransactionType::TYPE_AUXILIARY: 173 176 $aux_key = $transaction->getMetadataValue('aux:key'); 174 177 $task->setAuxiliaryAttribute($aux_key, $new); 178 + break; 179 + case ManiphestTransactionType::TYPE_EDGE: 180 + // Edge edits are accomplished through PhabricatorEdgeEditor, which 181 + // has authority. 175 182 break; 176 183 default: 177 184 throw new Exception('Unknown action type.');
+145
src/applications/maniphest/event/ManiphestEdgeEventListener.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + /** 20 + * Listener for Maniphest Task edge events. When some workflow causes task 21 + * edges to be added or removed, we consider the edge edit authoritative but 22 + * duplicate the information into a ManiphestTansaction for display. 23 + * 24 + * @group maniphest 25 + */ 26 + final class ManiphestEdgeEventListener extends PhutilEventListener { 27 + 28 + private $edges = array(); 29 + private $tasks = array(); 30 + 31 + public function register() { 32 + $this->listen(PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES); 33 + $this->listen(PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES); 34 + } 35 + 36 + public function handleEvent(PhutilEvent $event) { 37 + switch ($event->getType()) { 38 + case PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES: 39 + return $this->handleWillEditEvent($event); 40 + case PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES: 41 + return $this->handleDidEditEvent($event); 42 + } 43 + } 44 + 45 + private function handleWillEditEvent(PhutilEvent $event) { 46 + // NOTE: Everything is namespaced by `id` so that we aren't left in an 47 + // inconsistent state if an edit fails to complete (e.g., something throws) 48 + // or an edit happens inside another edit. 49 + 50 + $id = $event->getValue('id'); 51 + 52 + $edges = $this->loadAllEdges($event); 53 + $tasks = array(); 54 + if ($edges) { 55 + $tasks = id(new ManiphestTask())->loadAllWhere( 56 + 'phid IN (%Ls)', 57 + array_keys($edges)); 58 + $tasks = mpull($tasks, null, 'getPHID'); 59 + } 60 + 61 + $this->edges[$id] = $edges; 62 + $this->tasks[$id] = $tasks; 63 + } 64 + 65 + private function handleDidEditEvent(PhutilEvent $event) { 66 + $id = $event->getValue('id'); 67 + 68 + $old_edges = $this->edges[$id]; 69 + $tasks = $this->tasks[$id]; 70 + 71 + unset($this->edges[$id]); 72 + unset($this->tasks[$id]); 73 + 74 + $new_edges = $this->loadAllEdges($event); 75 + $editor = new ManiphestTransactionEditor(); 76 + 77 + foreach ($tasks as $phid => $task) { 78 + $xactions = array(); 79 + 80 + $old = $old_edges[$phid]; 81 + $new = $new_edges[$phid]; 82 + 83 + $types = array_keys($old + $new); 84 + foreach ($types as $type) { 85 + $old_type = idx($old, $type, array()); 86 + $new_type = idx($new, $type, array()); 87 + 88 + if ($old_type === $new_type) { 89 + continue; 90 + } 91 + 92 + $xactions[] = id(new ManiphestTransaction()) 93 + ->setTransactionType(ManiphestTransactionType::TYPE_EDGE) 94 + ->setOldValue($old_type) 95 + ->setNewValue($new_type) 96 + ->setMetadataValue('edge:type', $type) 97 + ->setAuthorPHID($event->getUser()->getPHID()); 98 + } 99 + 100 + if ($xactions) { 101 + $editor->applyTransactions($task, $xactions); 102 + } 103 + } 104 + } 105 + 106 + private function filterEdgesBySourceType(array $edges, $type) { 107 + foreach ($edges as $key => $edge) { 108 + if ($edge['src_type'] !== $type) { 109 + unset($edges[$key]); 110 + } 111 + } 112 + return $edges; 113 + } 114 + 115 + private function loadAllEdges(PhutilEvent $event) { 116 + $add_edges = $event->getValue('add'); 117 + $rem_edges = $event->getValue('rem'); 118 + 119 + $type_task = PhabricatorPHIDConstants::PHID_TYPE_TASK; 120 + 121 + $all_edges = array_merge($add_edges, $rem_edges); 122 + $all_edges = $this->filterEdgesBySourceType($all_edges, $type_task); 123 + 124 + if (!$all_edges) { 125 + return; 126 + } 127 + 128 + $all_tasks = array(); 129 + $all_types = array(); 130 + foreach ($all_edges as $edge) { 131 + $all_tasks[$edge['src']] = true; 132 + $all_types[$edge['type']] = true; 133 + } 134 + 135 + $all_tasks = array_keys($all_tasks); 136 + $all_types = array_keys($all_types); 137 + 138 + return id(new PhabricatorEdgeQuery()) 139 + ->withSourcePHIDs($all_tasks) 140 + ->withEdgeTypes($all_types) 141 + ->needEdgeData(true) 142 + ->execute(); 143 + } 144 + 145 + }
+2
src/applications/maniphest/storage/ManiphestTransaction.php
··· 58 58 $phids[] = $this->getOldValue(); 59 59 $phids[] = $this->getNewValue(); 60 60 break; 61 + case ManiphestTransactionType::TYPE_EDGE: 62 + return array_keys($this->getOldValue() + $this->getNewValue()); 61 63 case ManiphestTransactionType::TYPE_ATTACH: 62 64 $old = $this->getOldValue(); 63 65 $new = $this->getNewValue();
+1
src/applications/search/controller/PhabricatorSearchAttachController.php
··· 74 74 $add_phids = $phids; 75 75 $rem_phids = array_diff($old_phids, $add_phids); 76 76 $editor = id(new PhabricatorEdgeEditor()); 77 + $editor->setUser($user); 77 78 foreach ($add_phids as $phid) { 78 79 $editor->addEdge($this->phid, $edge_type, $phid); 79 80 }
+34 -2
src/infrastructure/edges/editor/PhabricatorEdgeEditor.php
··· 28 28 * 29 29 * id(new PhabricatorEdgeEditor()) 30 30 * ->addEdge($src, $type, $dst) 31 + * ->setUser($user) 31 32 * ->save(); 32 33 * 33 34 * @task edit Editing Edges ··· 38 39 private $addEdges = array(); 39 40 private $remEdges = array(); 40 41 private $openTransactions = array(); 42 + private $user; 43 + 44 + public function setUser(PhabricatorUser $user) { 45 + $this->user = $user; 46 + return $this; 47 + } 41 48 42 49 43 50 /* -( Editing Edges )------------------------------------------------------ */ ··· 111 118 112 119 $this->writeEdgeData(); 113 120 121 + static $id = 0; 122 + $id++; 123 + 124 + $this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES); 125 + 114 126 // NOTE: Removes first, then adds, so that "remove + add" is a useful 115 127 // operation meaning "overwrite". 116 128 117 129 $this->executeRemoves(); 118 130 $this->executeAdds(); 131 + 132 + $this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES); 133 + 119 134 120 135 $this->saveTransactions(); 121 136 } ··· 135 150 if (!empty($options['data'])) { 136 151 $data['data'] = $options['data']; 137 152 } 153 + 154 + $src_type = phid_get_type($src); 155 + $dst_type = phid_get_type($dst); 138 156 139 157 $specs = array(); 140 158 $specs[] = array( 141 159 'src' => $src, 142 - 'src_type' => phid_get_type($src), 160 + 'src_type' => $src_type, 143 161 'dst' => $dst, 162 + 'dst_type' => $dst_type, 144 163 'type' => $type, 145 164 'data' => $data, 146 165 ); ··· 156 175 157 176 $specs[] = array( 158 177 'src' => $dst, 159 - 'src_type' => phid_get_type($dst), 178 + 'src_type' => $dst_type, 160 179 'dst' => $src, 180 + 'dst_type' => $src_type, 161 181 'type' => $inverse, 162 182 'data' => $data, 163 183 ); ··· 305 325 $conn_w->saveTransaction(); 306 326 unset($this->openTransactions[$key]); 307 327 } 328 + } 329 + 330 + private function sendEvent($edit_id, $event_type) { 331 + $event = new PhabricatorEvent( 332 + $event_type, 333 + array( 334 + 'id' => $edit_id, 335 + 'add' => $this->addEdges, 336 + 'rem' => $this->remEdges, 337 + )); 338 + $event->setUser($this->user); 339 + PhutilEventEngine::dispatchEvent($event); 308 340 } 309 341 310 342 }
+2
src/infrastructure/events/PhabricatorEventEngine.php
··· 26 26 27 27 // Register the DarkConosole event logger. 28 28 id(new DarkConsoleEventPluginAPI())->register(); 29 + id(new ManiphestEdgeEventListener())->register(); 30 + 29 31 } 30 32 31 33 }
+3
src/infrastructure/events/constant/PhabricatorEventType.php
··· 23 23 const TYPE_DIFFERENTIAL_WILLSENDMAIL = 'differential.willSendMail'; 24 24 const TYPE_DIFFERENTIAL_WILLMARKGENERATED = 'differential.willMarkGenerated'; 25 25 26 + const TYPE_EDGE_WILLEDITEDGES = 'edge.willEditEdges'; 27 + const TYPE_EDGE_DIDEDITEDGES = 'edge.didEditEdges'; 28 + 26 29 }