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

Write workboard trigger rules to the database

Summary: Ref T5474. Read and write trigger rules so users can actually edit them.

Test Plan: Added, modified, and removed trigger rules. Saved changes, used "Show Details" to review edits.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T5474

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

+178 -85
+2
src/__phutil_library_map__.php
··· 4186 4186 'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php', 4187 4187 'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php', 4188 4188 'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php', 4189 + 'PhabricatorProjectTriggerRulesetTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php', 4189 4190 'PhabricatorProjectTriggerSearchEngine' => 'applications/project/query/PhabricatorProjectTriggerSearchEngine.php', 4190 4191 'PhabricatorProjectTriggerTransaction' => 'applications/project/storage/PhabricatorProjectTriggerTransaction.php', 4191 4192 'PhabricatorProjectTriggerTransactionQuery' => 'applications/project/query/PhabricatorProjectTriggerTransactionQuery.php', ··· 10319 10320 'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 10320 10321 'PhabricatorProjectTriggerRule' => 'Phobject', 10321 10322 'PhabricatorProjectTriggerRuleRecord' => 'Phobject', 10323 + 'PhabricatorProjectTriggerRulesetTransaction' => 'PhabricatorProjectTriggerTransactionType', 10322 10324 'PhabricatorProjectTriggerSearchEngine' => 'PhabricatorApplicationSearchEngine', 10323 10325 'PhabricatorProjectTriggerTransaction' => 'PhabricatorModularTransaction', 10324 10326 'PhabricatorProjectTriggerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+16 -4
src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php
··· 55 55 56 56 $v_name = $trigger->getName(); 57 57 $v_edit = $trigger->getEditPolicy(); 58 + $v_rules = $trigger->getTriggerRules(); 58 59 59 60 $e_name = null; 60 61 $e_edit = null; ··· 65 66 $v_name = $request->getStr('name'); 66 67 $v_edit = $request->getStr('editPolicy'); 67 68 68 - $v_rules = $request->getStr('rules'); 69 - $v_rules = phutil_json_decode($v_rules); 69 + // Read the JSON rules from the request and convert them back into 70 + // "TriggerRule" objects so we can render the correct form state 71 + // if the user is modifying the rules 72 + $raw_rules = $request->getStr('rules'); 73 + $raw_rules = phutil_json_decode($raw_rules); 74 + 75 + $copy = clone $trigger; 76 + $copy->setRuleset($raw_rules); 77 + $v_rules = $copy->getTriggerRules(); 70 78 71 79 $xactions = array(); 72 80 if (!$trigger->getID()) { ··· 84 92 ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) 85 93 ->setNewValue($v_edit); 86 94 87 - // TODO: Actually write the new rules to the database. 95 + $xactions[] = $trigger->getApplicationTransactionTemplate() 96 + ->setTransactionType( 97 + PhabricatorProjectTriggerRulesetTransaction::TRANSACTIONTYPE) 98 + ->setNewValue($raw_rules); 88 99 89 100 $editor = $trigger->getApplicationTransactionEditor() 90 101 ->setActor($viewer) ··· 207 218 208 219 $this->setupEditorBehavior( 209 220 $trigger, 221 + $v_rules, 210 222 $form_id, 211 223 $table_id, 212 224 $create_id, ··· 250 262 251 263 private function setupEditorBehavior( 252 264 PhabricatorProjectTrigger $trigger, 265 + array $rule_list, 253 266 $form_id, 254 267 $table_id, 255 268 $create_id, 256 269 $input_id) { 257 270 258 - $rule_list = $trigger->getTriggerRules(); 259 271 $rule_list = mpull($rule_list, 'toDictionary'); 260 272 $rule_list = array_values($rule_list); 261 273
+95 -81
src/applications/project/storage/PhabricatorProjectTrigger.php
··· 62 62 return pht('Trigger %d', $this->getID()); 63 63 } 64 64 65 + public function setRuleset(array $ruleset) { 66 + // Clear any cached trigger rules, since we're changing the ruleset 67 + // for the trigger. 68 + $this->triggerRules = null; 69 + 70 + parent::setRuleset($ruleset); 71 + } 72 + 65 73 public function getTriggerRules() { 66 74 if ($this->triggerRules === null) { 75 + $trigger_rules = self::newTriggerRulesFromRuleSpecifications( 76 + $this->getRuleset(), 77 + $allow_invalid = true); 67 78 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 - ); 79 + $this->triggerRules = $trigger_rules; 80 + } 85 81 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. 82 + return $this->triggerRules; 83 + } 89 84 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. 85 + public static function newTriggerRulesFromRuleSpecifications( 86 + array $list, 87 + $allow_invalid) { 95 88 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. 89 + // NOTE: With "$allow_invalid" set, we're trying to preserve the database 90 + // state in the rule structure, even if it includes rule types we don't 91 + // ha ve implementations for, or rules with invalid rule values. 103 92 104 - $rule_map = PhabricatorProjectTriggerRule::getAllTriggerRules(); 93 + // If an administrator adds or removes extensions which add rules, or 94 + // an upgrade affects rule validity, existing rules may become invalid. 95 + // When they do, we still want the UI to reflect the ruleset state 96 + // accurately and "Edit" + "Save" shouldn't destroy data unless the 97 + // user explicitly modifies the ruleset. 105 98 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)) { 99 + // In this mode, when we run into rules which are structured correctly but 100 + // which have types we don't know about, we replace them with "Unknown 101 + // Rules". If we know about the type of a rule but the value doesn't 102 + // validate, we replace it with "Invalid Rules". These two rule types don't 103 + // take any actions when a card is dropped into the column, but they show 104 + // the user what's wrong with the ruleset and can be saved without causing 105 + // any collateral damage. 106 + 107 + $rule_map = PhabricatorProjectTriggerRule::getAllTriggerRules(); 108 + 109 + // If the stored rule data isn't a list of rules (or we encounter other 110 + // fundamental structural problems, below), there isn't much we can do 111 + // to try to represent the state. 112 + if (!is_array($list)) { 113 + throw new PhabricatorProjectTriggerCorruptionException( 114 + pht( 115 + 'Trigger ruleset is corrupt: expected a list of rule '. 116 + 'specifications, found "%s".', 117 + phutil_describe_type($list))); 118 + } 119 + 120 + $trigger_rules = array(); 121 + foreach ($list as $key => $rule) { 122 + if (!is_array($rule)) { 110 123 throw new PhabricatorProjectTriggerCorruptionException( 111 124 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))); 125 + 'Trigger ruleset is corrupt: rule (with key "%s") should be a '. 126 + 'rule specification, but is actually "%s".', 127 + $key, 128 + phutil_describe_type($rule))); 116 129 } 117 130 118 - $trigger_rules = array(); 119 - foreach ($rule_specifications as $key => $rule) { 120 - if (!is_array($rule)) { 131 + try { 132 + PhutilTypeSpec::checkMap( 133 + $rule, 134 + array( 135 + 'type' => 'string', 136 + 'value' => 'wild', 137 + )); 138 + } catch (PhutilTypeCheckException $ex) { 139 + throw new PhabricatorProjectTriggerCorruptionException( 140 + pht( 141 + 'Trigger ruleset is corrupt: rule (with key "%s") is not a '. 142 + 'valid rule specification: %s', 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 + if (!$allow_invalid) { 121 153 throw new PhabricatorProjectTriggerCorruptionException( 122 154 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))); 155 + 'Trigger ruleset is corrupt: rule type "%s" is unknown.', 156 + $record->getType())); 128 157 } 129 158 130 - try { 131 - PhutilTypeSpec::checkMap( 132 - $rule, 133 - array( 134 - 'type' => 'string', 135 - 'value' => 'wild', 136 - )); 137 - } catch (PhutilTypeCheckException $ex) { 159 + $rule = new PhabricatorProjectTriggerUnknownRule(); 160 + } else { 161 + $rule = clone $rule_map[$record->getType()]; 162 + } 163 + 164 + try { 165 + $rule->setRecord($record); 166 + } catch (Exception $ex) { 167 + if (!$allow_invalid) { 138 168 throw new PhabricatorProjectTriggerCorruptionException( 139 169 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, 170 + 'Trigger ruleset is corrupt, rule (of type "%s") does not '. 171 + 'validate: %s', 172 + $record->getType(), 144 173 $ex->getMessage())); 145 174 } 146 175 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; 176 + $rule = id(new PhabricatorProjectTriggerInvalidRule()) 177 + ->setRecord($record); 165 178 } 166 179 167 - $this->triggerRules = $trigger_rules; 180 + $trigger_rules[] = $rule; 168 181 } 169 182 170 - return $this->triggerRules; 183 + return $trigger_rules; 171 184 } 185 + 172 186 173 187 public function getDropEffects() { 174 188 $effects = array();
+65
src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectTriggerRulesetTransaction 4 + extends PhabricatorProjectTriggerTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'ruleset'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getRuleset(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setRuleset($value); 14 + } 15 + 16 + public function getTitle() { 17 + return pht( 18 + '%s updated the ruleset for this trigger.', 19 + $this->renderAuthor()); 20 + } 21 + 22 + public function validateTransactions($object, array $xactions) { 23 + $errors = array(); 24 + 25 + foreach ($xactions as $xaction) { 26 + $ruleset = $xaction->getNewValue(); 27 + 28 + try { 29 + PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( 30 + $ruleset, 31 + $allow_invalid = false); 32 + } catch (PhabricatorProjectTriggerCorruptionException $ex) { 33 + $errors[] = $this->newInvalidError( 34 + pht( 35 + 'Ruleset specification is not valid. %s', 36 + $ex->getMessage()), 37 + $xaction); 38 + continue; 39 + } 40 + } 41 + 42 + return $errors; 43 + } 44 + 45 + public function hasChangeDetailView() { 46 + return true; 47 + } 48 + 49 + public function newChangeDetailView() { 50 + $viewer = $this->getViewer(); 51 + 52 + $old = $this->getOldValue(); 53 + $new = $this->getNewValue(); 54 + 55 + $json = new PhutilJSON(); 56 + $old_json = $json->encodeAsList($old); 57 + $new_json = $json->encodeAsList($new); 58 + 59 + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) 60 + ->setViewer($viewer) 61 + ->setOldText($old_json) 62 + ->setNewText($new_json); 63 + } 64 + 65 + }