1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +01:00

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
This commit is contained in:
epriestley 2019-09-12 16:05:40 -07:00
parent 3e60128037
commit 41f0b8b0a3
4 changed files with 120 additions and 12 deletions

View file

@ -344,6 +344,8 @@ dictionary with these keys:
- `children` //Optional map.// Configure options shown to the user when - `children` //Optional map.// Configure options shown to the user when
they "Create Subtask". See below. they "Create Subtask". See below.
- `fields` //Optional map.// Configure field behaviors. See below. - `fields` //Optional map.// Configure field behaviors. See below.
- `mutations` //Optional list.// Configure which subtypes this subtype
can easily be converted to by using the "Change Subtype" action. See below.
Each subtype must have a unique key, and you must define a subtype with Each subtype must have a unique key, and you must define a subtype with
the key "%s", which is used as a default subtype. the key "%s", which is used as a default subtype.
@ -421,6 +423,31 @@ Each field supports these options:
subtypes. subtypes.
- `name` //Optional string.// Custom name of this field for the subtype. - `name` //Optional string.// Custom name of this field for the subtype.
The `mutations` key allows you to control the behavior of the "Change Subtype"
action above the comment area. By default, this action allows users to change
the task subtype into any other subtype.
If you'd prefer to make it more difficult to change subtypes or offer only a
subset of subtypes, you can specify the list of subtypes that "Change Subtypes"
offers. For example, if you have several similar subtypes and want to allow
tasks to be converted between them but not easily converted to other types,
you can make the "Change Subtypes" control show only these options like this:
```
{
...
"mutations": ["bug", "issue", "defect"]
...
}
```
If you specify an empty list, the "Change Subtypes" action will be completely
hidden.
This mutation list is advisory and only configures the UI. Tasks may still be
converted across subtypes freely by using the Bulk Editor or API.
EOTEXT EOTEXT
, ,
$subtype_default_key)); $subtype_default_key));

View file

@ -14,6 +14,7 @@ final class PhabricatorEditEngineSubtype
private $childSubtypes = array(); private $childSubtypes = array();
private $childIdentifiers = array(); private $childIdentifiers = array();
private $fieldConfiguration = array(); private $fieldConfiguration = array();
private $mutations;
public function setKey($key) { public function setKey($key) {
$this->key = $key; $this->key = $key;
@ -78,6 +79,15 @@ final class PhabricatorEditEngineSubtype
return $this->childIdentifiers; return $this->childIdentifiers;
} }
public function setMutations($mutations) {
$this->mutations = $mutations;
return $this;
}
public function getMutations() {
return $this->mutations;
}
public function hasTagView() { public function hasTagView() {
return (bool)strlen($this->getTagText()); return (bool)strlen($this->getTagText());
} }
@ -152,6 +162,7 @@ final class PhabricatorEditEngineSubtype
'icon' => 'optional string', 'icon' => 'optional string',
'children' => 'optional map<string, wild>', 'children' => 'optional map<string, wild>',
'fields' => 'optional map<string, wild>', 'fields' => 'optional map<string, wild>',
'mutations' => 'optional list<string>',
)); ));
$key = $value['key']; $key = $value['key'];
@ -217,6 +228,28 @@ final class PhabricatorEditEngineSubtype
'with key "%s". This subtype is required and must be defined.', 'with key "%s". This subtype is required and must be defined.',
self::SUBTYPE_DEFAULT)); self::SUBTYPE_DEFAULT));
} }
foreach ($config as $value) {
$key = idx($value, 'key');
$mutations = idx($value, 'mutations');
if (!$mutations) {
continue;
}
foreach ($mutations as $mutation) {
if (!isset($map[$mutation])) {
throw new Exception(
pht(
'Subtype configuration is invalid: subtype with key "%s" '.
'specifies that it can mutate into subtype "%s", but that is '.
'not a valid subtype.',
$key,
$mutation));
}
}
}
} }
public static function newSubtypeMap(array $config) { public static function newSubtypeMap(array $config) {
@ -267,6 +300,8 @@ final class PhabricatorEditEngineSubtype
} }
} }
$subtype->setMutations(idx($entry, 'mutations'));
$map[$key] = $subtype; $map[$key] = $subtype;
} }

View file

@ -53,6 +53,44 @@ final class PhabricatorEditEngineSubtypeMap
return clone($this->datasource); return clone($this->datasource);
} }
public function getMutationMap($source_key) {
return mpull($this->getMutations($source_key), 'getName');
}
public function getMutations($source_key) {
$mutations = $this->subtypes;
$subtype = idx($this->subtypes, $source_key);
if ($subtype) {
$map = $subtype->getMutations();
if ($map !== null) {
$map = array_fuse($map);
foreach ($mutations as $key => $mutation) {
if ($key === $source_key) {
// This is the current subtype, so we always want to show it.
continue;
}
if (isset($map[$key])) {
// This is an allowed mutation, so keep it.
continue;
}
// Discard other subtypes as mutation options.
unset($mutations[$key]);
}
}
}
// If the only available mutation is the current subtype, treat this like
// no mutations are available.
if (array_keys($mutations) === array($source_key)) {
$mutations = array();
}
return $mutations;
}
public function getCreateFormsForSubtype( public function getCreateFormsForSubtype(
PhabricatorEditEngine $edit_engine, PhabricatorEditEngine $edit_engine,
PhabricatorEditEngineSubtypeInterface $object) { PhabricatorEditEngineSubtypeInterface $object) {

View file

@ -29,9 +29,17 @@ final class PhabricatorSubtypeEditEngineExtension
PhabricatorApplicationTransactionInterface $object) { PhabricatorApplicationTransactionInterface $object) {
$subtype_type = PhabricatorTransactions::TYPE_SUBTYPE; $subtype_type = PhabricatorTransactions::TYPE_SUBTYPE;
$subtype_value = $object->getEditEngineSubtype();
$map = $object->newEditEngineSubtypeMap(); $map = $object->newEditEngineSubtypeMap();
if ($object->getID()) {
$options = $map->getMutationMap($subtype_value);
} else {
// NOTE: This is a crude proxy for "are we in the bulk edit workflow".
// We want to allow any mutation.
$options = $map->getDisplayMap(); $options = $map->getDisplayMap();
}
$subtype_field = id(new PhabricatorSelectEditField()) $subtype_field = id(new PhabricatorSelectEditField())
->setKey(self::EDITKEY) ->setKey(self::EDITKEY)
@ -40,12 +48,12 @@ final class PhabricatorSubtypeEditEngineExtension
->setTransactionType($subtype_type) ->setTransactionType($subtype_type)
->setConduitDescription(pht('Change the object subtype.')) ->setConduitDescription(pht('Change the object subtype.'))
->setConduitTypeDescription(pht('New object subtype key.')) ->setConduitTypeDescription(pht('New object subtype key.'))
->setValue($object->getEditEngineSubtype()) ->setValue($subtype_value)
->setOptions($options); ->setOptions($options);
// If subtypes are configured, enable changing them from the bulk editor // If subtypes are configured, enable changing them from the bulk editor.
// and comment action stack. // Bulk editor
if ($map->getCount() > 1) { if ($options) {
$subtype_field $subtype_field
->setBulkEditLabel(pht('Change subtype to')) ->setBulkEditLabel(pht('Change subtype to'))
->setCommentActionLabel(pht('Change Subtype')) ->setCommentActionLabel(pht('Change Subtype'))