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

Add a trigger rule to reassign a task

Summary:
Ref T13269. Workboard triggers can now reassign tasks on column drop. Also sprinkles some `setViewer()` calls in places that needed them.

This mostly works, but a few issues:

* To set the owner to unassigned, you must explicitly put the "No Owner" token in the typeahead. Maybe this should just figure out you've put nothing in that field and set it for you?
* I'm pretty sure this was already broken, but if you change the rule type from a tokenizer to a different type, the default for the field doesn't populate correctly: {F6312227}

Also adds a new hook for trigger rules: `getValueForField($value)` which allows you to transform a value stored in the DB into a form suitable for setting on a form control.

Test Plan: Dragged tasks between columns and observed new owners as expected. Didn't try to get fancy to assign tasks to deleted users, users that the viewer can't see, bot users, etc etc. I'm relying on the underlying transaction to hopefully do the right thing.

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin

Maniphest Tasks: T13269

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

+170 -9
+2
src/__phutil_library_map__.php
··· 4202 4202 'PhabricatorProjectTriggerEditor' => 'applications/project/editor/PhabricatorProjectTriggerEditor.php', 4203 4203 'PhabricatorProjectTriggerInvalidRule' => 'applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php', 4204 4204 'PhabricatorProjectTriggerListController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerListController.php', 4205 + 'PhabricatorProjectTriggerManiphestOwnerRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php', 4205 4206 'PhabricatorProjectTriggerManiphestPriorityRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php', 4206 4207 'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php', 4207 4208 'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php', ··· 10368 10369 'PhabricatorProjectTriggerEditor' => 'PhabricatorApplicationTransactionEditor', 10369 10370 'PhabricatorProjectTriggerInvalidRule' => 'PhabricatorProjectTriggerRule', 10370 10371 'PhabricatorProjectTriggerListController' => 'PhabricatorProjectTriggerController', 10372 + 'PhabricatorProjectTriggerManiphestOwnerRule' => 'PhabricatorProjectTriggerRule', 10371 10373 'PhabricatorProjectTriggerManiphestPriorityRule' => 'PhabricatorProjectTriggerRule', 10372 10374 'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule', 10373 10375 'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType',
+5
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 577 577 $panel->addHeaderAction($column_menu); 578 578 579 579 if ($column->canHaveTrigger()) { 580 + $trigger = $column->getTrigger(); 581 + if ($trigger) { 582 + $trigger->setViewer($viewer); 583 + } 584 + 580 585 $trigger_menu = $this->buildTriggerMenu($column); 581 586 $panel->addHeaderAction($trigger_menu); 582 587 }
+6
src/applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php
··· 25 25 $trigger = PhabricatorProjectTrigger::initializeNewTrigger(); 26 26 } 27 27 28 + $trigger->setViewer($viewer); 29 + 28 30 $column_phid = $request->getStr('columnPHID'); 29 31 if ($column_phid) { 30 32 $column = id(new PhabricatorProjectColumnQuery()) ··· 272 274 $rule_list = array_values($rule_list); 273 275 274 276 $type_list = PhabricatorProjectTriggerRule::getAllTriggerRules(); 277 + 278 + foreach ($type_list as $rule) { 279 + $rule->setViewer($this->getViewer()); 280 + } 275 281 $type_list = mpull($type_list, 'newTemplate'); 276 282 $type_list = array_values($type_list); 277 283
+1
src/applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php
··· 20 20 if (!$trigger) { 21 21 return new Aphront404Response(); 22 22 } 23 + $trigger->setViewer($viewer); 23 24 24 25 $rules_view = $this->newRulesView($trigger); 25 26 $columns_view = $this->newColumnsView($trigger);
+23 -7
src/applications/project/storage/PhabricatorProjectTrigger.php
··· 13 13 protected $editPolicy; 14 14 15 15 private $triggerRules; 16 + private $viewer; 16 17 private $usage = self::ATTACHABLE; 17 18 18 19 public static function initializeNewTrigger() { ··· 41 42 return PhabricatorProjectTriggerPHIDType::TYPECONST; 42 43 } 43 44 45 + public function getViewer() { 46 + return $this->viewer; 47 + } 48 + 49 + public function setViewer(PhabricatorUser $user) { 50 + $this->viewer = $user; 51 + return $this; 52 + } 53 + 44 54 public function getDisplayName() { 45 55 $name = $this->getName(); 46 56 if (strlen($name)) { ··· 72 82 parent::setRuleset($ruleset); 73 83 } 74 84 75 - public function getTriggerRules() { 85 + public function getTriggerRules($viewer = null) { 76 86 if ($this->triggerRules === null) { 87 + if (!$viewer) { 88 + $viewer = $this->getViewer(); 89 + } 90 + 77 91 $trigger_rules = self::newTriggerRulesFromRuleSpecifications( 78 92 $this->getRuleset(), 79 - $allow_invalid = true); 93 + $allow_invalid = true, 94 + $viewer); 80 95 81 96 $this->triggerRules = $trigger_rules; 82 97 } ··· 86 101 87 102 public static function newTriggerRulesFromRuleSpecifications( 88 103 array $list, 89 - $allow_invalid) { 104 + $allow_invalid, 105 + PhabricatorUser $viewer) { 90 106 91 107 // NOTE: With "$allow_invalid" set, we're trying to preserve the database 92 108 // state in the rule structure, even if it includes rule types we don't ··· 124 140 if (!is_array($rule)) { 125 141 throw new PhabricatorProjectTriggerCorruptionException( 126 142 pht( 127 - 'Trigger ruleset is corrupt: rule (with key "%s") should be a '. 143 + 'Trigger ruleset is corrupt: rule (at index "%s") should be a '. 128 144 'rule specification, but is actually "%s".', 129 145 $key, 130 146 phutil_describe_type($rule))); ··· 140 156 } catch (PhutilTypeCheckException $ex) { 141 157 throw new PhabricatorProjectTriggerCorruptionException( 142 158 pht( 143 - 'Trigger ruleset is corrupt: rule (with key "%s") is not a '. 159 + 'Trigger ruleset is corrupt: rule (at index "%s") is not a '. 144 160 'valid rule specification: %s', 145 161 $key, 146 162 $ex->getMessage())); ··· 179 195 ->setRecord($record) 180 196 ->setException($ex); 181 197 } 198 + $rule->setViewer($viewer); 182 199 183 200 $trigger_rules[] = $rule; 184 201 } ··· 206 223 $object) { 207 224 208 225 $trigger_xactions = array(); 209 - foreach ($this->getTriggerRules() as $rule) { 226 + foreach ($this->getTriggerRules($viewer) as $rule) { 210 227 $rule 211 - ->setViewer($viewer) 212 228 ->setTrigger($this) 213 229 ->setColumn($column) 214 230 ->setObject($object);
+126
src/applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectTriggerManiphestOwnerRule 4 + extends PhabricatorProjectTriggerRule { 5 + 6 + const TRIGGERTYPE = 'task.owner'; 7 + 8 + public function getSelectControlName() { 9 + return pht('Assign task to'); 10 + } 11 + 12 + protected function getValueForEditorField() { 13 + return $this->getDatasource()->getWireTokens($this->getValue()); 14 + } 15 + 16 + private function convertTokenizerValueToOwner($value) { 17 + $value = head($value); 18 + if ($value === PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN) { 19 + $value = null; 20 + } 21 + return $value; 22 + } 23 + 24 + protected function assertValidRuleValue($value) { 25 + if (!is_array($value)) { 26 + throw new Exception( 27 + pht( 28 + 'Owner rule value should be a list, but is not (value is "%s").', 29 + phutil_describe_type($value))); 30 + } 31 + 32 + if (count($value) != 1) { 33 + throw new Exception( 34 + pht( 35 + 'Owner rule value should be a list of exactly one user PHID, or the '. 36 + 'token "none()" (value is "%s").', 37 + implode(', ', $value))); 38 + } 39 + } 40 + 41 + protected function newDropTransactions($object, $value) { 42 + $value = $this->convertTokenizerValueToOwner($value); 43 + return array( 44 + $this->newTransaction() 45 + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) 46 + ->setNewValue($value), 47 + ); 48 + } 49 + 50 + protected function newDropEffects($value) { 51 + $owner_value = $this->convertTokenizerValueToOwner($value); 52 + 53 + return array( 54 + $this->newEffect() 55 + ->setIcon('fa-user') 56 + ->setContent($this->getRuleViewDescription($value)) 57 + ->addCondition('owner', '!=', $owner_value), 58 + ); 59 + } 60 + 61 + protected function getDefaultValue() { 62 + return null; 63 + } 64 + 65 + protected function getPHUIXControlType() { 66 + return 'tokenizer'; 67 + } 68 + 69 + protected function getDatasource() { 70 + $datasource = id(new ManiphestAssigneeDatasource()) 71 + ->setLimit(1); 72 + 73 + if ($this->getViewer()) { 74 + $datasource->setViewer($this->getViewer()); 75 + } 76 + 77 + return $datasource; 78 + } 79 + 80 + protected function getPHUIXControlSpecification() { 81 + $template = id(new AphrontTokenizerTemplateView()) 82 + ->setViewer($this->getViewer()); 83 + 84 + $template_markup = $template->render(); 85 + $datasource = $this->getDatasource(); 86 + 87 + return array( 88 + 'markup' => (string)hsprintf('%s', $template_markup), 89 + 'config' => array( 90 + 'src' => $datasource->getDatasourceURI(), 91 + 'browseURI' => $datasource->getBrowseURI(), 92 + 'placeholder' => $datasource->getPlaceholderText(), 93 + 'limit' => $datasource->getLimit(), 94 + ), 95 + 'value' => null, 96 + ); 97 + } 98 + 99 + public function getRuleViewLabel() { 100 + return pht('Change Owner'); 101 + } 102 + 103 + public function getRuleViewDescription($value) { 104 + $value = $this->convertTokenizerValueToOwner($value); 105 + 106 + if (!$value) { 107 + return pht('Unassign task.'); 108 + } else { 109 + return pht( 110 + 'Assign task to %s.', 111 + phutil_tag( 112 + 'strong', 113 + array(), 114 + $this->getViewer() 115 + ->renderHandle($value) 116 + ->render())); 117 + } 118 + } 119 + 120 + public function getRuleViewIcon($value) { 121 + return id(new PHUIIconView()) 122 + ->setIcon('fa-user', 'green'); 123 + } 124 + 125 + 126 + }
+5 -1
src/applications/project/trigger/PhabricatorProjectTriggerRule.php
··· 37 37 return $this->getRecord()->getValue(); 38 38 } 39 39 40 + protected function getValueForEditorField() { 41 + return $this->getValue(); 42 + } 43 + 40 44 abstract public function getSelectControlName(); 41 45 abstract public function getRuleViewLabel(); 42 46 abstract public function getRuleViewDescription($value); ··· 130 134 131 135 return array( 132 136 'type' => $record->getType(), 133 - 'value' => $record->getValue(), 137 + 'value' => $this->getValueForEditorField(), 134 138 'isValidRule' => $is_valid, 135 139 'invalidView' => $invalid_view, 136 140 );
+2 -1
src/applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php
··· 28 28 try { 29 29 PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications( 30 30 $ruleset, 31 - $allow_invalid = false); 31 + $allow_invalid = false, 32 + $xaction->getViewer()); 32 33 } catch (PhabricatorProjectTriggerCorruptionException $ex) { 33 34 $errors[] = $this->newInvalidError( 34 35 pht(