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

Distinguish between "bad record format" and "bad record value" when validating Trigger rules

Summary:
Depends on D20416. Ref T13269. See D20329.

If you try to save an "Assign to" rule with no assignee, we currently replace the control with an "InvalidRule" control that isn't editable. We'd prefer to give you an empty field back and let you pick a different value.

Differentiate between "bad record format" (i.e., we can't really do anything with this) and "bad record value" (i.e., everything is structurally fine, you just typed the wrong thing). In the latter case, we still build a properly typed rule for the UI, we just refuse to update storage until you fix the problem.

Test Plan:
First, hit the original issue and got a nicer UI with a more consistent control width (note full-width error):

{F6374205}

Then, applied the rest of the patch and got a normal "fix the issue" form state instead of a dead-end:

{F6374211}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13269

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

+113 -35
+2 -2
resources/celerity/map.php
··· 100 100 'rsrc/css/application/policy/policy.css' => 'ceb56a08', 101 101 'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a', 102 102 'rsrc/css/application/project/project-card-view.css' => '4e7371cd', 103 - 'rsrc/css/application/project/project-triggers.css' => 'cb866c2d', 103 + 'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9', 104 104 'rsrc/css/application/project/project-view.css' => '567858b3', 105 105 'rsrc/css/application/releeph/releeph-core.css' => 'f81ff2db', 106 106 'rsrc/css/application/releeph/releeph-preview-branch.css' => '22db5c07', ··· 882 882 'policy-transaction-detail-css' => 'c02b8384', 883 883 'ponder-view-css' => '05a09d0a', 884 884 'project-card-view-css' => '4e7371cd', 885 - 'project-triggers-css' => 'cb866c2d', 885 + 'project-triggers-css' => 'cd9c8bb9', 886 886 'project-view-css' => '567858b3', 887 887 'releeph-core' => 'f81ff2db', 888 888 'releeph-preview-branch' => '22db5c07',
+2 -2
src/applications/project/storage/PhabricatorProjectTrigger.php
··· 104 104 $allow_invalid, 105 105 PhabricatorUser $viewer) { 106 106 107 - // NOTE: With "$allow_invalid" set, we're trying to preserve the database 107 + // NOTE: With "$allow_invalid" set, we're trying to preserve the database 108 108 // state in the rule structure, even if it includes rule types we don't 109 - // ha ve implementations for, or rules with invalid rule values. 109 + // have implementations for, or rules with invalid rule values. 110 110 111 111 // If an administrator adds or removes extensions which add rules, or 112 112 // an upgrade affects rule validity, existing rules may become invalid.
+13 -5
src/applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php
··· 5 5 6 6 const TRIGGERTYPE = 'task.projects.add'; 7 7 8 - public function getSelectControLname() { 9 - return pht('Add projects'); 8 + public function getSelectControlName() { 9 + return pht('Add project tags'); 10 10 } 11 11 12 12 protected function getValueForEditorField() { 13 13 return $this->getDatasource()->getWireTokens($this->getValue()); 14 14 } 15 15 16 - protected function assertValidRuleValue($value) { 16 + protected function assertValidRuleRecordFormat($value) { 17 17 if (!is_array($value)) { 18 18 throw new Exception( 19 19 pht( 20 20 'Add project rule value should be a list, but is not '. 21 21 '(value is "%s").', 22 22 phutil_describe_type($value))); 23 + } 24 + } 25 + 26 + protected function assertValidRuleRecordValue($value) { 27 + if (!$value) { 28 + throw new Exception( 29 + pht( 30 + 'You must select at least one project tag to add.')); 23 31 } 24 32 } 25 33 ··· 78 86 } 79 87 80 88 public function getRuleViewLabel() { 81 - return pht('Add Projects'); 89 + return pht('Add Project Tags'); 82 90 } 83 91 84 92 public function getRuleViewDescription($value) { 85 93 return pht( 86 - 'Add projects: %s.', 94 + 'Add project tags: %s.', 87 95 phutil_tag( 88 96 'strong', 89 97 array(),
+1 -1
src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php
··· 24 24 return false; 25 25 } 26 26 27 - protected function assertValidRuleValue($value) { 27 + protected function assertValidRuleRecordFormat($value) { 28 28 return; 29 29 } 30 30
+26 -4
src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php
··· 21 21 return $value; 22 22 } 23 23 24 - protected function assertValidRuleValue($value) { 24 + protected function assertValidRuleRecordFormat($value) { 25 25 if (!is_array($value)) { 26 26 throw new Exception( 27 27 pht( 28 28 'Owner rule value should be a list, but is not (value is "%s").', 29 29 phutil_describe_type($value))); 30 30 } 31 + } 31 32 32 - if (count($value) != 1) { 33 + protected function assertValidRuleRecordValue($value) { 34 + if (!$value) { 35 + throw new Exception( 36 + pht( 37 + 'Owner rule value is required. Specify a user to assign tasks '. 38 + 'to, or the token "none()" to unassign tasks.')); 39 + } 40 + 41 + if (count($value) > 1) { 33 42 throw new Exception( 34 43 pht( 35 - 'Owner rule value should be a list of exactly one user PHID, or the '. 36 - 'token "none()" (value is "%s").', 44 + 'Owner rule value must have only one elmement (value is "%s").', 37 45 implode(', ', $value))); 46 + } 47 + 48 + $owner_phid = $this->convertTokenizerValueToOwner($value); 49 + if ($owner_phid !== null) { 50 + $user = id(new PhabricatorPeopleQuery()) 51 + ->setViewer($this->getViewer()) 52 + ->withPHIDs(array($owner_phid)) 53 + ->executeOne(); 54 + if (!$user) { 55 + throw new Exception( 56 + pht( 57 + 'User PHID ("%s") is not a valid user.', 58 + $owner_phid)); 59 + } 38 60 } 39 61 } 40 62
+7 -3
src/applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php
··· 9 9 return pht('Change priority to'); 10 10 } 11 11 12 - protected function assertValidRuleValue($value) { 12 + protected function assertValidRuleRecordFormat($value) { 13 13 if (!is_string($value)) { 14 14 throw new Exception( 15 15 pht( 16 16 'Priority rule value should be a string, but is not (value is "%s").', 17 17 phutil_describe_type($value))); 18 18 } 19 + } 19 20 21 + protected function assertValidRuleRecordValue($value) { 20 22 $map = ManiphestTaskPriority::getTaskPriorityMap(); 21 23 if (!isset($map[$value])) { 22 24 throw new Exception( 23 25 pht( 24 - 'Rule value ("%s") is not a valid task priority.', 25 - $value)); 26 + 'Task priority value ("%s") is not a valid task priority. '. 27 + 'Valid priorities are: %s.', 28 + $value, 29 + implode(', ', array_keys($map)))); 26 30 } 27 31 } 28 32
+7 -3
src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php
··· 9 9 return pht('Change status to'); 10 10 } 11 11 12 - protected function assertValidRuleValue($value) { 12 + protected function assertValidRuleRecordFormat($value) { 13 13 if (!is_string($value)) { 14 14 throw new Exception( 15 15 pht( 16 16 'Status rule value should be a string, but is not (value is "%s").', 17 17 phutil_describe_type($value))); 18 18 } 19 + } 19 20 21 + protected function assertValidRuleRecordValue($value) { 20 22 $map = ManiphestTaskStatus::getTaskStatusMap(); 21 23 if (!isset($map[$value])) { 22 24 throw new Exception( 23 25 pht( 24 - 'Rule value ("%s") is not a valid task status.', 25 - $value)); 26 + 'Task status value ("%s") is not a valid task status. '. 27 + 'Valid statues are: %s.', 28 + $value, 29 + implode(', ', array_keys($map)))); 26 30 } 27 31 } 28 32
+4 -3
src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php
··· 9 9 return pht('Play sound'); 10 10 } 11 11 12 - protected function assertValidRuleValue($value) { 12 + protected function assertValidRuleRecordFormat($value) { 13 13 if (!is_string($value)) { 14 14 throw new Exception( 15 15 pht( 16 16 'Status rule value should be a string, but is not (value is "%s").', 17 17 phutil_describe_type($value))); 18 18 } 19 + } 19 20 21 + protected function assertValidRuleRecordValue($value) { 20 22 $map = self::getSoundMap(); 21 - 22 23 if (!isset($map[$value])) { 23 24 throw new Exception( 24 25 pht( 25 - 'Rule value ("%s") is not a valid sound.', 26 + 'Sound ("%s") is not a valid sound.', 26 27 $value)); 27 28 } 28 29 }
+13 -5
src/applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php
··· 5 5 6 6 const TRIGGERTYPE = 'task.projects.remove'; 7 7 8 - public function getSelectControLname() { 9 - return pht('Remove projects'); 8 + public function getSelectControlname() { 9 + return pht('Remove project tags'); 10 10 } 11 11 12 12 protected function getValueForEditorField() { 13 13 return $this->getDatasource()->getWireTokens($this->getValue()); 14 14 } 15 15 16 - protected function assertValidRuleValue($value) { 16 + protected function assertValidRuleRecordFormat($value) { 17 17 if (!is_array($value)) { 18 18 throw new Exception( 19 19 pht( 20 20 'Remove project rule value should be a list, but is not '. 21 21 '(value is "%s").', 22 22 phutil_describe_type($value))); 23 + } 24 + } 25 + 26 + protected function assertValidRuleRecordValue($value) { 27 + if (!$value) { 28 + throw new Exception( 29 + pht( 30 + 'You must select at least one project tag to remove.')); 23 31 } 24 32 } 25 33 ··· 78 86 } 79 87 80 88 public function getRuleViewLabel() { 81 - return pht('Remove Projects'); 89 + return pht('Remove Project Tags'); 82 90 } 83 91 84 92 public function getRuleViewDescription($value) { 85 93 return pht( 86 - 'Remove projects: %s.', 94 + 'Remove project tags: %s.', 87 95 phutil_tag( 88 96 'strong', 89 97 array(),
+17 -2
src/applications/project/trigger/PhabricatorProjectTriggerRule.php
··· 23 23 final public function setRecord(PhabricatorProjectTriggerRuleRecord $record) { 24 24 $value = $record->getValue(); 25 25 26 - $this->assertValidRuleValue($value); 26 + $this->assertValidRuleRecordFormat($value); 27 27 28 28 $this->record = $record; 29 29 return $this; ··· 45 45 abstract public function getRuleViewLabel(); 46 46 abstract public function getRuleViewDescription($value); 47 47 abstract public function getRuleViewIcon($value); 48 - abstract protected function assertValidRuleValue($value); 48 + abstract protected function assertValidRuleRecordFormat($value); 49 + 50 + final public function getRuleRecordValueValidationException() { 51 + try { 52 + $this->assertValidRuleRecordValue($this->getRecord()->getValue()); 53 + } catch (Exception $ex) { 54 + return $ex; 55 + } 56 + 57 + return null; 58 + } 59 + 60 + protected function assertValidRuleRecordValue($value) { 61 + return; 62 + } 63 + 49 64 abstract protected function newDropTransactions($object, $value); 50 65 abstract protected function newDropEffects($value); 51 66 abstract protected function getDefaultValue();
+1 -1
src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php
··· 13 13 return false; 14 14 } 15 15 16 - protected function assertValidRuleValue($value) { 16 + protected function assertValidRuleRecordFormat($value) { 17 17 return; 18 18 } 19 19
+19 -4
src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php
··· 20 20 } 21 21 22 22 public function validateTransactions($object, array $xactions) { 23 + $actor = $this->getActor(); 23 24 $errors = array(); 24 25 25 26 foreach ($xactions as $xaction) { 26 27 $ruleset = $xaction->getNewValue(); 27 28 28 29 try { 29 - PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( 30 - $ruleset, 31 - $allow_invalid = false, 32 - $xaction->getViewer()); 30 + $rules = 31 + PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( 32 + $ruleset, 33 + $allow_invalid = false, 34 + $actor); 33 35 } catch (PhabricatorProjectTriggerCorruptionException $ex) { 34 36 $errors[] = $this->newInvalidError( 35 37 pht( ··· 37 39 $ex->getMessage()), 38 40 $xaction); 39 41 continue; 42 + } 43 + 44 + foreach ($rules as $rule) { 45 + $exception = $rule->getRuleRecordValueValidationException(); 46 + if ($exception) { 47 + $errors[] = $this->newInvalidError( 48 + pht( 49 + 'Value for "%s" rule is invalid: %s', 50 + $rule->getSelectControlName(), 51 + $exception->getMessage()), 52 + $xaction); 53 + continue; 54 + } 40 55 } 41 56 } 42 57
+1
webroot/rsrc/css/application/project/project-triggers.css
··· 27 27 28 28 .trigger-rules-table td.invalid-cell { 29 29 padding-left: 12px; 30 + width: 100%; 30 31 } 31 32 32 33 .trigger-rules-table td.invalid-cell .phui-icon-view {