@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 subtypes to specify "mutations", to control the behavior of the "Change Subtype" action

Summary:
Fixes T13415. Provide a way for subtypes to customize the behavior of "Change Subtype" actions that appear above comment areas.

Subtypes may disable this action by specifying `"mutations": []`, or provide a list of subtypes.

The bulk editor and API can still perform any change.

Test Plan:
- Tried to define an invalid "mutations" list with a bad subtype, got a sensible error.
- Specified a limited mutations list and an empty mutations list, verified that corresponding tasks got corresponding actions.
- Used the bulk editor to perform a freeform mutation.
- Verified that tasks of a subtype with no "mutations" still work the same way they used to (allow mutation into any subtype).

Maniphest Tasks: T13415

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

+120 -12
+34 -7
src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
··· 344 344 - `children` //Optional map.// Configure options shown to the user when 345 345 they "Create Subtask". See below. 346 346 - `fields` //Optional map.// Configure field behaviors. See below. 347 + - `mutations` //Optional list.// Configure which subtypes this subtype 348 + can easily be converted to by using the "Change Subtype" action. See below. 347 349 348 350 Each subtype must have a unique key, and you must define a subtype with 349 351 the key "%s", which is used as a default subtype. ··· 404 406 task subtypes. For example: 405 407 406 408 ``` 407 - { 408 - ... 409 - "fields": { 410 - "custom.some-field": { 411 - "disabled": true 409 + { 410 + ... 411 + "fields": { 412 + "custom.some-field": { 413 + "disabled": true 414 + } 412 415 } 416 + ... 413 417 } 414 - ... 415 - } 416 418 ``` 417 419 418 420 Each field supports these options: ··· 420 422 - `disabled` //Optional bool.// Allows you to disable fields on certain 421 423 subtypes. 422 424 - `name` //Optional string.// Custom name of this field for the subtype. 425 + 426 + 427 + The `mutations` key allows you to control the behavior of the "Change Subtype" 428 + action above the comment area. By default, this action allows users to change 429 + the task subtype into any other subtype. 430 + 431 + If you'd prefer to make it more difficult to change subtypes or offer only a 432 + subset of subtypes, you can specify the list of subtypes that "Change Subtypes" 433 + offers. For example, if you have several similar subtypes and want to allow 434 + tasks to be converted between them but not easily converted to other types, 435 + you can make the "Change Subtypes" control show only these options like this: 436 + 437 + ``` 438 + { 439 + ... 440 + "mutations": ["bug", "issue", "defect"] 441 + ... 442 + } 443 + ``` 444 + 445 + If you specify an empty list, the "Change Subtypes" action will be completely 446 + hidden. 447 + 448 + This mutation list is advisory and only configures the UI. Tasks may still be 449 + converted across subtypes freely by using the Bulk Editor or API. 423 450 424 451 EOTEXT 425 452 ,
+35
src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php
··· 14 14 private $childSubtypes = array(); 15 15 private $childIdentifiers = array(); 16 16 private $fieldConfiguration = array(); 17 + private $mutations; 17 18 18 19 public function setKey($key) { 19 20 $this->key = $key; ··· 78 79 return $this->childIdentifiers; 79 80 } 80 81 82 + public function setMutations($mutations) { 83 + $this->mutations = $mutations; 84 + return $this; 85 + } 86 + 87 + public function getMutations() { 88 + return $this->mutations; 89 + } 90 + 81 91 public function hasTagView() { 82 92 return (bool)strlen($this->getTagText()); 83 93 } ··· 152 162 'icon' => 'optional string', 153 163 'children' => 'optional map<string, wild>', 154 164 'fields' => 'optional map<string, wild>', 165 + 'mutations' => 'optional list<string>', 155 166 )); 156 167 157 168 $key = $value['key']; ··· 217 228 'with key "%s". This subtype is required and must be defined.', 218 229 self::SUBTYPE_DEFAULT)); 219 230 } 231 + 232 + foreach ($config as $value) { 233 + $key = idx($value, 'key'); 234 + 235 + $mutations = idx($value, 'mutations'); 236 + if (!$mutations) { 237 + continue; 238 + } 239 + 240 + foreach ($mutations as $mutation) { 241 + if (!isset($map[$mutation])) { 242 + throw new Exception( 243 + pht( 244 + 'Subtype configuration is invalid: subtype with key "%s" '. 245 + 'specifies that it can mutate into subtype "%s", but that is '. 246 + 'not a valid subtype.', 247 + $key, 248 + $mutation)); 249 + } 250 + } 251 + } 252 + 220 253 } 221 254 222 255 public static function newSubtypeMap(array $config) { ··· 266 299 $field_configuration); 267 300 } 268 301 } 302 + 303 + $subtype->setMutations(idx($entry, 'mutations')); 269 304 270 305 $map[$key] = $subtype; 271 306 }
+38
src/applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php
··· 53 53 return clone($this->datasource); 54 54 } 55 55 56 + public function getMutationMap($source_key) { 57 + return mpull($this->getMutations($source_key), 'getName'); 58 + } 59 + 60 + public function getMutations($source_key) { 61 + $mutations = $this->subtypes; 62 + 63 + $subtype = idx($this->subtypes, $source_key); 64 + if ($subtype) { 65 + $map = $subtype->getMutations(); 66 + if ($map !== null) { 67 + $map = array_fuse($map); 68 + foreach ($mutations as $key => $mutation) { 69 + if ($key === $source_key) { 70 + // This is the current subtype, so we always want to show it. 71 + continue; 72 + } 73 + 74 + if (isset($map[$key])) { 75 + // This is an allowed mutation, so keep it. 76 + continue; 77 + } 78 + 79 + // Discard other subtypes as mutation options. 80 + unset($mutations[$key]); 81 + } 82 + } 83 + } 84 + 85 + // If the only available mutation is the current subtype, treat this like 86 + // no mutations are available. 87 + if (array_keys($mutations) === array($source_key)) { 88 + $mutations = array(); 89 + } 90 + 91 + return $mutations; 92 + } 93 + 56 94 public function getCreateFormsForSubtype( 57 95 PhabricatorEditEngine $edit_engine, 58 96 PhabricatorEditEngineSubtypeInterface $object) {
+13 -5
src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php
··· 29 29 PhabricatorApplicationTransactionInterface $object) { 30 30 31 31 $subtype_type = PhabricatorTransactions::TYPE_SUBTYPE; 32 + $subtype_value = $object->getEditEngineSubtype(); 32 33 33 34 $map = $object->newEditEngineSubtypeMap(); 34 - $options = $map->getDisplayMap(); 35 + 36 + if ($object->getID()) { 37 + $options = $map->getMutationMap($subtype_value); 38 + } else { 39 + // NOTE: This is a crude proxy for "are we in the bulk edit workflow". 40 + // We want to allow any mutation. 41 + $options = $map->getDisplayMap(); 42 + } 35 43 36 44 $subtype_field = id(new PhabricatorSelectEditField()) 37 45 ->setKey(self::EDITKEY) ··· 40 48 ->setTransactionType($subtype_type) 41 49 ->setConduitDescription(pht('Change the object subtype.')) 42 50 ->setConduitTypeDescription(pht('New object subtype key.')) 43 - ->setValue($object->getEditEngineSubtype()) 51 + ->setValue($subtype_value) 44 52 ->setOptions($options); 45 53 46 - // If subtypes are configured, enable changing them from the bulk editor 47 - // and comment action stack. 48 - if ($map->getCount() > 1) { 54 + // If subtypes are configured, enable changing them from the bulk editor. 55 + // Bulk editor 56 + if ($options) { 49 57 $subtype_field 50 58 ->setBulkEditLabel(pht('Change subtype to')) 51 59 ->setCommentActionLabel(pht('Change Subtype'))