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
|
- `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.
|
||||||
|
@ -404,15 +406,15 @@ The `fields` key can configure the behavior of custom fields on specific
|
||||||
task subtypes. For example:
|
task subtypes. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
"fields": {
|
"fields": {
|
||||||
"custom.some-field": {
|
"custom.some-field": {
|
||||||
"disabled": true
|
"disabled": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
...
|
||||||
}
|
}
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Each field supports these options:
|
Each field supports these options:
|
||||||
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
$options = $map->getDisplayMap();
|
|
||||||
|
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())
|
$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'))
|
||||||
|
|
Loading…
Reference in a new issue