mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +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:
parent
3e60128037
commit
41f0b8b0a3
4 changed files with 120 additions and 12 deletions
|
@ -344,6 +344,8 @@ dictionary with these keys:
|
|||
- `children` //Optional map.// Configure options shown to the user when
|
||||
they "Create Subtask". 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
|
||||
the key "%s", which is used as a default subtype.
|
||||
|
@ -421,6 +423,31 @@ Each field supports these options:
|
|||
subtypes.
|
||||
- `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
|
||||
,
|
||||
$subtype_default_key));
|
||||
|
|
|
@ -14,6 +14,7 @@ final class PhabricatorEditEngineSubtype
|
|||
private $childSubtypes = array();
|
||||
private $childIdentifiers = array();
|
||||
private $fieldConfiguration = array();
|
||||
private $mutations;
|
||||
|
||||
public function setKey($key) {
|
||||
$this->key = $key;
|
||||
|
@ -78,6 +79,15 @@ final class PhabricatorEditEngineSubtype
|
|||
return $this->childIdentifiers;
|
||||
}
|
||||
|
||||
public function setMutations($mutations) {
|
||||
$this->mutations = $mutations;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMutations() {
|
||||
return $this->mutations;
|
||||
}
|
||||
|
||||
public function hasTagView() {
|
||||
return (bool)strlen($this->getTagText());
|
||||
}
|
||||
|
@ -152,6 +162,7 @@ final class PhabricatorEditEngineSubtype
|
|||
'icon' => 'optional string',
|
||||
'children' => 'optional map<string, wild>',
|
||||
'fields' => 'optional map<string, wild>',
|
||||
'mutations' => 'optional list<string>',
|
||||
));
|
||||
|
||||
$key = $value['key'];
|
||||
|
@ -217,6 +228,28 @@ final class PhabricatorEditEngineSubtype
|
|||
'with key "%s". This subtype is required and must be defined.',
|
||||
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) {
|
||||
|
@ -267,6 +300,8 @@ final class PhabricatorEditEngineSubtype
|
|||
}
|
||||
}
|
||||
|
||||
$subtype->setMutations(idx($entry, 'mutations'));
|
||||
|
||||
$map[$key] = $subtype;
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,44 @@ final class PhabricatorEditEngineSubtypeMap
|
|||
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(
|
||||
PhabricatorEditEngine $edit_engine,
|
||||
PhabricatorEditEngineSubtypeInterface $object) {
|
||||
|
|
|
@ -29,9 +29,17 @@ final class PhabricatorSubtypeEditEngineExtension
|
|||
PhabricatorApplicationTransactionInterface $object) {
|
||||
|
||||
$subtype_type = PhabricatorTransactions::TYPE_SUBTYPE;
|
||||
$subtype_value = $object->getEditEngineSubtype();
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
$subtype_field = id(new PhabricatorSelectEditField())
|
||||
->setKey(self::EDITKEY)
|
||||
|
@ -40,12 +48,12 @@ final class PhabricatorSubtypeEditEngineExtension
|
|||
->setTransactionType($subtype_type)
|
||||
->setConduitDescription(pht('Change the object subtype.'))
|
||||
->setConduitTypeDescription(pht('New object subtype key.'))
|
||||
->setValue($object->getEditEngineSubtype())
|
||||
->setValue($subtype_value)
|
||||
->setOptions($options);
|
||||
|
||||
// If subtypes are configured, enable changing them from the bulk editor
|
||||
// and comment action stack.
|
||||
if ($map->getCount() > 1) {
|
||||
// If subtypes are configured, enable changing them from the bulk editor.
|
||||
// Bulk editor
|
||||
if ($options) {
|
||||
$subtype_field
|
||||
->setBulkEditLabel(pht('Change subtype to'))
|
||||
->setCommentActionLabel(pht('Change Subtype'))
|
||||
|
|
Loading…
Reference in a new issue