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

Hard code a "close task" action on every column Trigger

Summary: Depends on D20287. Ref T5474. This hard-codes a storage value for every trigger, with a "Change status to <default closed status>" rule and two bogus rules. Rules may now apply transactions when cards are dropped.

Test Plan: Dragged cards to a column with a trigger, saw them close.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T5474

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

+407 -2
+12
src/__phutil_library_map__.php
··· 4174 4174 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', 4175 4175 'PhabricatorProjectTrigger' => 'applications/project/storage/PhabricatorProjectTrigger.php', 4176 4176 'PhabricatorProjectTriggerController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerController.php', 4177 + 'PhabricatorProjectTriggerCorruptionException' => 'applications/project/exception/PhabricatorProjectTriggerCorruptionException.php', 4177 4178 'PhabricatorProjectTriggerEditController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php', 4178 4179 'PhabricatorProjectTriggerEditor' => 'applications/project/editor/PhabricatorProjectTriggerEditor.php', 4180 + 'PhabricatorProjectTriggerInvalidRule' => 'applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php', 4179 4181 'PhabricatorProjectTriggerListController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerListController.php', 4182 + 'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php', 4180 4183 'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php', 4181 4184 'PhabricatorProjectTriggerPHIDType' => 'applications/project/phid/PhabricatorProjectTriggerPHIDType.php', 4182 4185 'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php', 4186 + 'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php', 4187 + 'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php', 4183 4188 'PhabricatorProjectTriggerSearchEngine' => 'applications/project/query/PhabricatorProjectTriggerSearchEngine.php', 4184 4189 'PhabricatorProjectTriggerTransaction' => 'applications/project/storage/PhabricatorProjectTriggerTransaction.php', 4185 4190 'PhabricatorProjectTriggerTransactionQuery' => 'applications/project/query/PhabricatorProjectTriggerTransactionQuery.php', 4186 4191 'PhabricatorProjectTriggerTransactionType' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerTransactionType.php', 4192 + 'PhabricatorProjectTriggerUnknownRule' => 'applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php', 4187 4193 'PhabricatorProjectTriggerViewController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php', 4188 4194 'PhabricatorProjectTypeTransaction' => 'applications/project/xaction/PhabricatorProjectTypeTransaction.php', 4189 4195 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', ··· 10300 10306 'PhabricatorDestructibleInterface', 10301 10307 ), 10302 10308 'PhabricatorProjectTriggerController' => 'PhabricatorProjectController', 10309 + 'PhabricatorProjectTriggerCorruptionException' => 'Exception', 10303 10310 'PhabricatorProjectTriggerEditController' => 'PhabricatorProjectTriggerController', 10304 10311 'PhabricatorProjectTriggerEditor' => 'PhabricatorApplicationTransactionEditor', 10312 + 'PhabricatorProjectTriggerInvalidRule' => 'PhabricatorProjectTriggerRule', 10305 10313 'PhabricatorProjectTriggerListController' => 'PhabricatorProjectTriggerController', 10314 + 'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule', 10306 10315 'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType', 10307 10316 'PhabricatorProjectTriggerPHIDType' => 'PhabricatorPHIDType', 10308 10317 'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 10318 + 'PhabricatorProjectTriggerRule' => 'Phobject', 10319 + 'PhabricatorProjectTriggerRuleRecord' => 'Phobject', 10309 10320 'PhabricatorProjectTriggerSearchEngine' => 'PhabricatorApplicationSearchEngine', 10310 10321 'PhabricatorProjectTriggerTransaction' => 'PhabricatorModularTransaction', 10311 10322 'PhabricatorProjectTriggerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 10312 10323 'PhabricatorProjectTriggerTransactionType' => 'PhabricatorModularTransactionType', 10324 + 'PhabricatorProjectTriggerUnknownRule' => 'PhabricatorProjectTriggerRule', 10313 10325 'PhabricatorProjectTriggerViewController' => 'PhabricatorProjectTriggerController', 10314 10326 'PhabricatorProjectTypeTransaction' => 'PhabricatorProjectTransactionType', 10315 10327 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
+1
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 1270 1270 array( 1271 1271 'items' => hsprintf('%s', $trigger_menu), 1272 1272 'tip' => $trigger_tip, 1273 + 'size' => 300, 1273 1274 )); 1274 1275 1275 1276 return $trigger_button;
+14
src/applications/project/controller/PhabricatorProjectMoveController.php
··· 70 70 $columns = id(new PhabricatorProjectColumnQuery()) 71 71 ->setViewer($viewer) 72 72 ->withProjectPHIDs(array($project->getPHID())) 73 + ->needTriggers(true) 73 74 ->execute(); 74 75 75 76 $columns = mpull($columns, null, 'getPHID'); ··· 108 109 $edit_header); 109 110 foreach ($header_xactions as $header_xaction) { 110 111 $xactions[] = $header_xaction; 112 + } 113 + 114 + if ($column->canHaveTrigger()) { 115 + $trigger = $column->getTrigger(); 116 + if ($trigger) { 117 + $trigger_xactions = $trigger->newDropTransactions( 118 + $viewer, 119 + $column, 120 + $object); 121 + foreach ($trigger_xactions as $trigger_xaction) { 122 + $xactions[] = $trigger_xaction; 123 + } 124 + } 111 125 } 112 126 113 127 $editor = id(new ManiphestTransactionEditor())
+4
src/applications/project/exception/PhabricatorProjectTriggerCorruptionException.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectTriggerCorruptionException 4 + extends Exception {}
+175 -2
src/applications/project/storage/PhabricatorProjectTrigger.php
··· 11 11 protected $ruleset = array(); 12 12 protected $editPolicy; 13 13 14 + private $triggerRules; 15 + 14 16 public static function initializeNewTrigger() { 15 17 $default_edit = PhabricatorPolicies::POLICY_USER; 16 18 ··· 60 62 return pht('Trigger %d', $this->getID()); 61 63 } 62 64 65 + public function getTriggerRules() { 66 + if ($this->triggerRules === null) { 67 + 68 + // TODO: Temporary hard-coded rule specification. 69 + $rule_specifications = array( 70 + array( 71 + 'type' => 'status', 72 + 'value' => ManiphestTaskStatus::getDefaultClosedStatus(), 73 + ), 74 + // This is an intentionally unknown rule. 75 + array( 76 + 'type' => 'quack', 77 + 'value' => 'aaa', 78 + ), 79 + // This is an intentionally invalid rule. 80 + array( 81 + 'type' => 'status', 82 + 'value' => 'quack', 83 + ), 84 + ); 85 + 86 + // NOTE: We're trying to preserve the database state in the rule 87 + // structure, even if it includes rule types we don't have implementations 88 + // for, or rules with invalid rule values. 89 + 90 + // If an administrator adds or removes extensions which add rules, or 91 + // an upgrade affects rule validity, existing rules may become invalid. 92 + // When they do, we still want the UI to reflect the ruleset state 93 + // accurately and "Edit" + "Save" shouldn't destroy data unless the 94 + // user explicitly modifies the ruleset. 95 + 96 + // When we run into rules which are structured correctly but which have 97 + // types we don't know about, we replace them with "Unknown Rules". If 98 + // we know about the type of a rule but the value doesn't validate, we 99 + // replace it with "Invalid Rules". These two rule types don't take any 100 + // actions when a card is dropped into the column, but they show the user 101 + // what's wrong with the ruleset and can be saved without causing any 102 + // collateral damage. 103 + 104 + $rule_map = PhabricatorProjectTriggerRule::getAllTriggerRules(); 105 + 106 + // If the stored rule data isn't a list of rules (or we encounter other 107 + // fundamental structural problems, below), there isn't much we can do 108 + // to try to represent the state. 109 + if (!is_array($rule_specifications)) { 110 + throw new PhabricatorProjectTriggerCorruptionException( 111 + pht( 112 + 'Trigger ("%s") has a corrupt ruleset: expected a list of '. 113 + 'rule specifications, found "%s".', 114 + $this->getPHID(), 115 + phutil_describe_type($rule_specifications))); 116 + } 117 + 118 + $trigger_rules = array(); 119 + foreach ($rule_specifications as $key => $rule) { 120 + if (!is_array($rule)) { 121 + throw new PhabricatorProjectTriggerCorruptionException( 122 + pht( 123 + 'Trigger ("%s") has a corrupt ruleset: rule (with key "%s") '. 124 + 'should be a rule specification, but is actually "%s".', 125 + $this->getPHID(), 126 + $key, 127 + phutil_describe_type($rule))); 128 + } 129 + 130 + try { 131 + PhutilTypeSpec::checkMap( 132 + $rule, 133 + array( 134 + 'type' => 'string', 135 + 'value' => 'wild', 136 + )); 137 + } catch (PhutilTypeCheckException $ex) { 138 + throw new PhabricatorProjectTriggerCorruptionException( 139 + pht( 140 + 'Trigger ("%s") has a corrupt ruleset: rule (with key "%s") '. 141 + 'is not a valid rule specification: %s', 142 + $this->getPHID(), 143 + $key, 144 + $ex->getMessage())); 145 + } 146 + 147 + $record = id(new PhabricatorProjectTriggerRuleRecord()) 148 + ->setType(idx($rule, 'type')) 149 + ->setValue(idx($rule, 'value')); 150 + 151 + if (!isset($rule_map[$record->getType()])) { 152 + $rule = new PhabricatorProjectTriggerUnknownRule(); 153 + } else { 154 + $rule = clone $rule_map[$record->getType()]; 155 + } 156 + 157 + try { 158 + $rule->setRecord($record); 159 + } catch (Exception $ex) { 160 + $rule = id(new PhabricatorProjectTriggerInvalidRule()) 161 + ->setRecord($record); 162 + } 163 + 164 + $trigger_rules[] = $rule; 165 + } 166 + 167 + $this->triggerRules = $trigger_rules; 168 + } 169 + 170 + return $this->triggerRules; 171 + } 172 + 63 173 public function getRulesDescription() { 64 - // TODO: Summarize the trigger rules in human-readable text. 65 - return pht('Does things.'); 174 + $rules = $this->getTriggerRules(); 175 + if (!$rules) { 176 + return pht('Does nothing.'); 177 + } 178 + 179 + $things = array(); 180 + 181 + $count = count($rules); 182 + $limit = 3; 183 + 184 + if ($count > $limit) { 185 + $show_rules = array_slice($rules, 0, ($limit - 1)); 186 + } else { 187 + $show_rules = $rules; 188 + } 189 + 190 + foreach ($show_rules as $rule) { 191 + $things[] = $rule->getDescription(); 192 + } 193 + 194 + if ($count > $limit) { 195 + $things[] = pht( 196 + '(Applies %s more actions.)', 197 + new PhutilNumber($count - $limit)); 198 + } 199 + 200 + return implode("\n", $things); 66 201 } 202 + 203 + public function newDropTransactions( 204 + PhabricatorUser $viewer, 205 + PhabricatorProjectColumn $column, 206 + $object) { 207 + 208 + $trigger_xactions = array(); 209 + foreach ($this->getTriggerRules() as $rule) { 210 + $rule 211 + ->setViewer($viewer) 212 + ->setTrigger($this) 213 + ->setColumn($column) 214 + ->setObject($object); 215 + 216 + $xactions = $rule->getDropTransactions( 217 + $object, 218 + $rule->getRecord()->getValue()); 219 + 220 + if (!is_array($xactions)) { 221 + throw new Exception( 222 + pht( 223 + 'Expected trigger rule (of class "%s") to return a list of '. 224 + 'transactions from "newDropTransactions()", but got "%s".', 225 + get_class($rule), 226 + phutil_describe_type($xactions))); 227 + } 228 + 229 + $expect_type = get_class($object->getApplicationTransactionTemplate()); 230 + assert_instances_of($xactions, $expect_type); 231 + 232 + foreach ($xactions as $xaction) { 233 + $trigger_xactions[] = $xaction; 234 + } 235 + } 236 + 237 + return $trigger_xactions; 238 + } 239 + 67 240 68 241 69 242 /* -( PhabricatorApplicationTransactionInterface )------------------------- */
+22
src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectTriggerInvalidRule 4 + extends PhabricatorProjectTriggerRule { 5 + 6 + const TRIGGERTYPE = 'invalid'; 7 + 8 + public function getDescription() { 9 + return pht( 10 + 'Invalid rule (of type "%s").', 11 + $this->getRecord()->getType()); 12 + } 13 + 14 + protected function assertValidRuleValue($value) { 15 + return; 16 + } 17 + 18 + protected function newDropTransactions($object, $value) { 19 + return array(); 20 + } 21 + 22 + }
+41
src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectTriggerManiphestStatusRule 4 + extends PhabricatorProjectTriggerRule { 5 + 6 + const TRIGGERTYPE = 'task.status'; 7 + 8 + public function getDescription() { 9 + $value = $this->getValue(); 10 + 11 + return pht( 12 + 'Changes status to "%s".', 13 + ManiphestTaskStatus::getTaskStatusName($value)); 14 + } 15 + 16 + protected function assertValidRuleValue($value) { 17 + if (!is_string($value)) { 18 + throw new Exception( 19 + pht( 20 + 'Status rule value should be a string, but is not (value is "%s").', 21 + phutil_describe_type($value))); 22 + } 23 + 24 + $map = ManiphestTaskStatus::getTaskStatusMap(); 25 + if (!isset($map[$value])) { 26 + throw new Exception( 27 + pht( 28 + 'Rule value ("%s") is not a valid task status.', 29 + $value)); 30 + } 31 + } 32 + 33 + protected function newDropTransactions($object, $value) { 34 + return array( 35 + $this->newTransaction() 36 + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) 37 + ->setNewValue($value), 38 + ); 39 + } 40 + 41 + }
+89
src/applications/project/trigger/PhabricatorProjectTriggerRule.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorProjectTriggerRule 4 + extends Phobject { 5 + 6 + private $record; 7 + private $viewer; 8 + private $column; 9 + private $trigger; 10 + private $object; 11 + 12 + final public function getTriggerType() { 13 + return $this->getPhobjectClassConstant('TRIGGERTYPE', 64); 14 + } 15 + 16 + final public static function getAllTriggerRules() { 17 + return id(new PhutilClassMapQuery()) 18 + ->setAncestorClass(__CLASS__) 19 + ->setUniqueMethod('getTriggerType') 20 + ->execute(); 21 + } 22 + 23 + final public function setRecord(PhabricatorProjectTriggerRuleRecord $record) { 24 + $value = $record->getValue(); 25 + 26 + $this->assertValidRuleValue($value); 27 + 28 + $this->record = $record; 29 + return $this; 30 + } 31 + 32 + final public function getRecord() { 33 + return $this->record; 34 + } 35 + 36 + final protected function getValue() { 37 + return $this->getRecord()->getValue(); 38 + } 39 + 40 + abstract public function getDescription(); 41 + abstract protected function assertValidRuleValue($value); 42 + abstract protected function newDropTransactions($object, $value); 43 + 44 + final public function getDropTransactions($object, $value) { 45 + return $this->newDropTransactions($object, $value); 46 + } 47 + 48 + final public function setViewer(PhabricatorUser $viewer) { 49 + $this->viewer = $viewer; 50 + return $this; 51 + } 52 + 53 + final public function getViewer() { 54 + return $this->viewer; 55 + } 56 + 57 + final public function setColumn(PhabricatorProjectColumn $column) { 58 + $this->column = $column; 59 + return $this; 60 + } 61 + 62 + final public function getColumn() { 63 + return $this->column; 64 + } 65 + 66 + final public function setTrigger(PhabricatorProjectTrigger $trigger) { 67 + $this->trigger = $trigger; 68 + return $this; 69 + } 70 + 71 + final public function getTrigger() { 72 + return $this->trigger; 73 + } 74 + 75 + final public function setObject( 76 + PhabricatorApplicationTransactionInterface $object) { 77 + $this->object = $object; 78 + return $this; 79 + } 80 + 81 + final public function getObject() { 82 + return $this->object; 83 + } 84 + 85 + final protected function newTransaction() { 86 + return $this->getObject()->getApplicationTransactionTemplate(); 87 + } 88 + 89 + }
+27
src/applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectTriggerRuleRecord 4 + extends Phobject { 5 + 6 + private $type; 7 + private $value; 8 + 9 + public function setType($type) { 10 + $this->type = $type; 11 + return $this; 12 + } 13 + 14 + public function getType() { 15 + return $this->type; 16 + } 17 + 18 + public function setValue($value) { 19 + $this->value = $value; 20 + return $this; 21 + } 22 + 23 + public function getValue() { 24 + return $this->value; 25 + } 26 + 27 + }
+22
src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectTriggerUnknownRule 4 + extends PhabricatorProjectTriggerRule { 5 + 6 + const TRIGGERTYPE = 'unknown'; 7 + 8 + public function getDescription() { 9 + return pht( 10 + 'Unknown rule (of type "%s").', 11 + $this->getRecord()->getType()); 12 + } 13 + 14 + protected function assertValidRuleValue($value) { 15 + return; 16 + } 17 + 18 + protected function newDropTransactions($object, $value) { 19 + return array(); 20 + } 21 + 22 + }