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

Allow triggers to be attached to and removed from workboard columns

Summary:
Depends on D20286. Ref T5474. Attaches triggers to columns and makes "Remove Trigger" work.

(There's no "pick an existing named trigger from a list" UI yet, but I plan to add that at some point.)

Test Plan: Attached and removed triggers, saw column UI update appropriately.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T5474

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

+257 -40
+4
src/__phutil_library_map__.php
··· 4070 4070 'PhabricatorProjectColumnPositionQuery' => 'applications/project/query/PhabricatorProjectColumnPositionQuery.php', 4071 4071 'PhabricatorProjectColumnPriorityOrder' => 'applications/project/order/PhabricatorProjectColumnPriorityOrder.php', 4072 4072 'PhabricatorProjectColumnQuery' => 'applications/project/query/PhabricatorProjectColumnQuery.php', 4073 + 'PhabricatorProjectColumnRemoveTriggerController' => 'applications/project/controller/PhabricatorProjectColumnRemoveTriggerController.php', 4073 4074 'PhabricatorProjectColumnSearchEngine' => 'applications/project/query/PhabricatorProjectColumnSearchEngine.php', 4074 4075 'PhabricatorProjectColumnStatusOrder' => 'applications/project/order/PhabricatorProjectColumnStatusOrder.php', 4075 4076 'PhabricatorProjectColumnStatusTransaction' => 'applications/project/xaction/column/PhabricatorProjectColumnStatusTransaction.php', ··· 4078 4079 'PhabricatorProjectColumnTransactionEditor' => 'applications/project/editor/PhabricatorProjectColumnTransactionEditor.php', 4079 4080 'PhabricatorProjectColumnTransactionQuery' => 'applications/project/query/PhabricatorProjectColumnTransactionQuery.php', 4080 4081 'PhabricatorProjectColumnTransactionType' => 'applications/project/xaction/column/PhabricatorProjectColumnTransactionType.php', 4082 + 'PhabricatorProjectColumnTriggerTransaction' => 'applications/project/xaction/column/PhabricatorProjectColumnTriggerTransaction.php', 4081 4083 'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php', 4082 4084 'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php', 4083 4085 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', ··· 10184 10186 'PhabricatorProjectColumnPositionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 10185 10187 'PhabricatorProjectColumnPriorityOrder' => 'PhabricatorProjectColumnOrder', 10186 10188 'PhabricatorProjectColumnQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 10189 + 'PhabricatorProjectColumnRemoveTriggerController' => 'PhabricatorProjectBoardController', 10187 10190 'PhabricatorProjectColumnSearchEngine' => 'PhabricatorApplicationSearchEngine', 10188 10191 'PhabricatorProjectColumnStatusOrder' => 'PhabricatorProjectColumnOrder', 10189 10192 'PhabricatorProjectColumnStatusTransaction' => 'PhabricatorProjectColumnTransactionType', ··· 10192 10195 'PhabricatorProjectColumnTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 10193 10196 'PhabricatorProjectColumnTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 10194 10197 'PhabricatorProjectColumnTransactionType' => 'PhabricatorModularTransactionType', 10198 + 'PhabricatorProjectColumnTriggerTransaction' => 'PhabricatorProjectColumnTransactionType', 10195 10199 'PhabricatorProjectConfigOptions' => 'PhabricatorApplicationConfigOptions', 10196 10200 'PhabricatorProjectConfiguredCustomField' => array( 10197 10201 'PhabricatorProjectStandardCustomField',
+4
src/applications/project/application/PhabricatorProjectApplication.php
··· 89 89 'background/' 90 90 => 'PhabricatorProjectBoardBackgroundController', 91 91 ), 92 + 'column/' => array( 93 + 'remove/(?P<id>\d+)/' => 94 + 'PhabricatorProjectColumnRemoveTriggerController', 95 + ), 92 96 'trigger/' => array( 93 97 $this->getQueryRoutePattern() => 94 98 'PhabricatorProjectTriggerListController',
+85 -35
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 574 574 $column_menu = $this->buildColumnMenu($project, $column); 575 575 $panel->addHeaderAction($column_menu); 576 576 577 + if ($column->canHaveTrigger()) { 578 + $trigger_menu = $this->buildTriggerMenu($column); 579 + $panel->addHeaderAction($trigger_menu); 580 + } 581 + 577 582 $count_tag = id(new PHUITagView()) 578 583 ->setType(PHUITagView::TYPE_SHADE) 579 584 ->setColor(PHUITagView::COLOR_BLUE) ··· 1172 1177 ->setWorkflow(true); 1173 1178 } 1174 1179 1175 - if ($column->canHaveTrigger()) { 1176 - $column_items[] = id(new PhabricatorActionView()) 1177 - ->setType(PhabricatorActionView::TYPE_DIVIDER); 1178 - 1179 - $trigger = $column->getTrigger(); 1180 - if (!$trigger) { 1181 - $set_uri = $this->getApplicationURI( 1182 - new PhutilURI( 1183 - 'trigger/edit/', 1184 - array( 1185 - 'columnPHID' => $column->getPHID(), 1186 - ))); 1187 - 1188 - $column_items[] = id(new PhabricatorActionView()) 1189 - ->setIcon('fa-cogs') 1190 - ->setName(pht('New Trigger...')) 1191 - ->setHref($set_uri) 1192 - ->setDisabled(!$can_edit); 1193 - } else { 1194 - $column_items[] = id(new PhabricatorActionView()) 1195 - ->setIcon('fa-cogs') 1196 - ->setName(pht('View Trigger')) 1197 - ->setHref($trigger->getURI()) 1198 - ->setDisabled(!$can_edit); 1199 - } 1200 - 1201 - $column_items[] = id(new PhabricatorActionView()) 1202 - ->setIcon('fa-times') 1203 - ->setName(pht('Remove Trigger')) 1204 - ->setHref('#') 1205 - ->setWorkflow(true) 1206 - ->setDisabled(!$can_edit || !$trigger); 1207 - } 1208 - 1209 1180 $column_menu = id(new PhabricatorActionListView()) 1210 1181 ->setUser($viewer); 1211 1182 foreach ($column_items as $item) { ··· 1213 1184 } 1214 1185 1215 1186 $column_button = id(new PHUIIconView()) 1216 - ->setIcon('fa-caret-down') 1187 + ->setIcon('fa-pencil') 1217 1188 ->setHref('#') 1218 1189 ->addSigil('boards-dropdown-menu') 1219 1190 ->setMetadata( ··· 1224 1195 return $column_button; 1225 1196 } 1226 1197 1198 + private function buildTriggerMenu(PhabricatorProjectColumn $column) { 1199 + $viewer = $this->getViewer(); 1200 + $trigger = $column->getTrigger(); 1201 + 1202 + $can_edit = PhabricatorPolicyFilter::hasCapability( 1203 + $viewer, 1204 + $column, 1205 + PhabricatorPolicyCapability::CAN_EDIT); 1206 + 1207 + $trigger_items = array(); 1208 + if (!$trigger) { 1209 + $set_uri = $this->getApplicationURI( 1210 + new PhutilURI( 1211 + 'trigger/edit/', 1212 + array( 1213 + 'columnPHID' => $column->getPHID(), 1214 + ))); 1215 + 1216 + $trigger_items[] = id(new PhabricatorActionView()) 1217 + ->setIcon('fa-cogs') 1218 + ->setName(pht('New Trigger...')) 1219 + ->setHref($set_uri) 1220 + ->setDisabled(!$can_edit); 1221 + } else { 1222 + $trigger_items[] = id(new PhabricatorActionView()) 1223 + ->setIcon('fa-cogs') 1224 + ->setName(pht('View Trigger')) 1225 + ->setHref($trigger->getURI()) 1226 + ->setDisabled(!$can_edit); 1227 + } 1228 + 1229 + $remove_uri = $this->getApplicationURI( 1230 + new PhutilURI( 1231 + urisprintf( 1232 + 'column/remove/%d/', 1233 + $column->getID()))); 1234 + 1235 + $trigger_items[] = id(new PhabricatorActionView()) 1236 + ->setIcon('fa-times') 1237 + ->setName(pht('Remove Trigger')) 1238 + ->setHref($remove_uri) 1239 + ->setWorkflow(true) 1240 + ->setDisabled(!$can_edit || !$trigger); 1241 + 1242 + $trigger_menu = id(new PhabricatorActionListView()) 1243 + ->setUser($viewer); 1244 + foreach ($trigger_items as $item) { 1245 + $trigger_menu->addAction($item); 1246 + } 1247 + 1248 + if ($trigger) { 1249 + $trigger_icon = 'fa-cogs'; 1250 + } else { 1251 + $trigger_icon = 'fa-cogs grey'; 1252 + } 1253 + 1254 + if ($trigger) { 1255 + $trigger_tip = array( 1256 + pht('%s: %s', $trigger->getObjectName(), $trigger->getDisplayName()), 1257 + $trigger->getRulesDescription(), 1258 + ); 1259 + $trigger_tip = implode("\n", $trigger_tip); 1260 + } else { 1261 + $trigger_tip = pht('No column trigger.'); 1262 + } 1263 + 1264 + $trigger_button = id(new PHUIIconView()) 1265 + ->setIcon($trigger_icon) 1266 + ->setHref('#') 1267 + ->addSigil('boards-dropdown-menu') 1268 + ->addSigil('has-tooltip') 1269 + ->setMetadata( 1270 + array( 1271 + 'items' => hsprintf('%s', $trigger_menu), 1272 + 'tip' => $trigger_tip, 1273 + )); 1274 + 1275 + return $trigger_button; 1276 + } 1227 1277 1228 1278 /** 1229 1279 * Add current state parameters (like order and the visibility of hidden
+60
src/applications/project/controller/PhabricatorProjectColumnRemoveTriggerController.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectColumnRemoveTriggerController 4 + extends PhabricatorProjectBoardController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + $id = $request->getURIData('id'); 9 + 10 + $column = id(new PhabricatorProjectColumnQuery()) 11 + ->setViewer($viewer) 12 + ->withIDs(array($id)) 13 + ->requireCapabilities( 14 + array( 15 + PhabricatorPolicyCapability::CAN_VIEW, 16 + PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$column) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $done_uri = $column->getBoardURI(); 24 + 25 + if (!$column->getTriggerPHID()) { 26 + return $this->newDialog() 27 + ->setTitle(pht('No Trigger')) 28 + ->appendParagraph( 29 + pht('This column does not have a trigger.')) 30 + ->addCancelButton($done_uri); 31 + } 32 + 33 + if ($request->isFormPost()) { 34 + $column_xactions = array(); 35 + 36 + $column_xactions[] = $column->getApplicationTransactionTemplate() 37 + ->setTransactionType( 38 + PhabricatorProjectColumnTriggerTransaction::TRANSACTIONTYPE) 39 + ->setNewValue(null); 40 + 41 + $column_editor = $column->getApplicationTransactionEditor() 42 + ->setActor($viewer) 43 + ->setContentSourceFromRequest($request) 44 + ->setContinueOnNoEffect(true) 45 + ->setContinueOnMissingFields(true); 46 + 47 + $column_editor->applyTransactions($column, $column_xactions); 48 + 49 + return id(new AphrontRedirectResponse())->setURI($done_uri); 50 + } 51 + 52 + $body = pht('Really remove the trigger from this column?'); 53 + 54 + return $this->newDialog() 55 + ->setTitle(pht('Remove Trigger')) 56 + ->appendParagraph($body) 57 + ->addSubmitButton(pht('Remove Trigger')) 58 + ->addCancelButton($done_uri); 59 + } 60 + }
+6 -3
src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php
··· 93 93 if ($column) { 94 94 $column_xactions = array(); 95 95 96 - // TODO: Modularize column transactions so we can change the column 97 - // trigger here. For now, this does nothing. 96 + $column_xactions[] = $column->getApplicationTransactionTemplate() 97 + ->setTransactionType( 98 + PhabricatorProjectColumnTriggerTransaction::TRANSACTIONTYPE) 99 + ->setNewValue($trigger->getPHID()); 98 100 99 101 $column_editor = $column->getApplicationTransactionEditor() 100 102 ->setActor($viewer) 101 103 ->setContentSourceFromRequest($request) 102 - ->setContinueOnNoEffect(true); 104 + ->setContinueOnNoEffect(true) 105 + ->setContinueOnMissingFields(true); 103 106 104 107 $column_editor->applyTransactions($column, $column_xactions); 105 108
+1 -1
src/applications/project/query/PhabricatorProjectColumnQuery.php
··· 148 148 $triggers = id(new PhabricatorProjectTriggerQuery()) 149 149 ->setViewer($this->getViewer()) 150 150 ->setParentQuery($this) 151 - ->withPHIDs(array($this->getPHID())) 151 + ->withPHIDs($trigger_phids) 152 152 ->execute(); 153 153 $triggers = mpull($triggers, null, 'getPHID'); 154 154 } else {
+19 -1
src/applications/project/storage/PhabricatorProjectTrigger.php
··· 60 60 return pht('Trigger %d', $this->getID()); 61 61 } 62 62 63 + public function getRulesDescription() { 64 + // TODO: Summarize the trigger rules in human-readable text. 65 + return pht('Does things.'); 66 + } 67 + 63 68 64 69 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 65 70 ··· 102 107 103 108 public function destroyObjectPermanently( 104 109 PhabricatorDestructionEngine $engine) { 105 - $this->delete(); 110 + 111 + $this->openTransaction(); 112 + $conn = $this->establishConnection('w'); 113 + 114 + // Remove the reference to this trigger from any columns which use it. 115 + queryfx( 116 + $conn, 117 + 'UPDATE %R SET triggerPHID = null WHERE triggerPHID = %s', 118 + new PhabricatorProjectColumn(), 119 + $this->getPHID()); 120 + 121 + $this->delete(); 122 + 123 + $this->saveTransaction(); 106 124 } 107 125 108 126 }
+78
src/applications/project/xaction/column/PhabricatorProjectColumnTriggerTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectColumnTriggerTransaction 4 + extends PhabricatorProjectColumnTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'trigger'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getTriggerPHID(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setTriggerPHID($value); 14 + } 15 + 16 + public function getTitle() { 17 + $old = $this->getOldValue(); 18 + $new = $this->getNewValue(); 19 + 20 + if (!$old) { 21 + return pht( 22 + '%s set the column trigger to %s.', 23 + $this->renderAuthor(), 24 + $this->renderNewHandle()); 25 + } else if (!$new) { 26 + return pht( 27 + '%s removed the trigger for this column (was %s).', 28 + $this->renderAuthor(), 29 + $this->renderOldHandle()); 30 + } else { 31 + return pht( 32 + '%s changed the trigger for this column from %s to %s.', 33 + $this->renderAuthor(), 34 + $this->renderOldHandle(), 35 + $this->renderNewHandle()); 36 + } 37 + } 38 + 39 + public function validateTransactions($object, array $xactions) { 40 + $actor = $this->getActor(); 41 + $errors = array(); 42 + 43 + foreach ($xactions as $xaction) { 44 + $trigger_phid = $xaction->getNewValue(); 45 + 46 + // You can always remove a trigger. 47 + if (!$trigger_phid) { 48 + continue; 49 + } 50 + 51 + // You can't put a trigger on a column that can't have triggers, like 52 + // a backlog column or a proxy column. 53 + if (!$object->canHaveTrigger()) { 54 + $errors[] = $this->newInvalidError( 55 + pht('This column can not have a trigger.'), 56 + $xaction); 57 + continue; 58 + } 59 + 60 + $trigger = id(new PhabricatorProjectTriggerQuery()) 61 + ->setViewer($actor) 62 + ->withPHIDs(array($trigger_phid)) 63 + ->execute(); 64 + if (!$trigger) { 65 + $errors[] = $this->newInvalidError( 66 + pht( 67 + 'Trigger "%s" is not a valid trigger, or you do not have '. 68 + 'permission to view it.', 69 + $trigger_phid), 70 + $xaction); 71 + continue; 72 + } 73 + } 74 + 75 + return $errors; 76 + } 77 + 78 + }