mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
Make Maniphest task statuses user configurable
Summary: Fixes T1812. Moves the internal configuration into public space and documents it. Test Plan: - Tried to set it to some invalid stuff. - Set it to various valid things. - Browsed around, changed statuses, filtered statuses, viewed statuses, merged duplictes, examined transaction record, created tasks. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T1812 Differential Revision: https://secure.phabricator.com/D8585
This commit is contained in:
parent
7713fb5d99
commit
9ca86b69b7
6 changed files with 235 additions and 78 deletions
|
@ -882,6 +882,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestReplyHandler' => 'applications/maniphest/mail/ManiphestReplyHandler.php',
|
||||
'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php',
|
||||
'ManiphestSearchIndexer' => 'applications/maniphest/search/ManiphestSearchIndexer.php',
|
||||
'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php',
|
||||
'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php',
|
||||
'ManiphestSubscribeController' => 'applications/maniphest/controller/ManiphestSubscribeController.php',
|
||||
'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php',
|
||||
|
@ -1323,6 +1324,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorConfigIssueListController' => 'applications/config/controller/PhabricatorConfigIssueListController.php',
|
||||
'PhabricatorConfigIssueViewController' => 'applications/config/controller/PhabricatorConfigIssueViewController.php',
|
||||
'PhabricatorConfigJSON' => 'applications/config/json/PhabricatorConfigJSON.php',
|
||||
'PhabricatorConfigJSONOptionType' => 'applications/config/custom/PhabricatorConfigJSONOptionType.php',
|
||||
'PhabricatorConfigListController' => 'applications/config/controller/PhabricatorConfigListController.php',
|
||||
'PhabricatorConfigLocalSource' => 'infrastructure/env/PhabricatorConfigLocalSource.php',
|
||||
'PhabricatorConfigManagementDeleteWorkflow' => 'applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php',
|
||||
|
@ -3521,6 +3523,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'ManiphestReportController' => 'ManiphestController',
|
||||
'ManiphestSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
|
||||
'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
'ManiphestSubpriorityController' => 'ManiphestController',
|
||||
'ManiphestSubscribeController' => 'ManiphestController',
|
||||
'ManiphestTask' =>
|
||||
|
@ -4032,6 +4035,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorConfigIgnoreController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorConfigIssueListController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigIssueViewController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigJSONOptionType' => 'PhabricatorConfigOptionType',
|
||||
'PhabricatorConfigListController' => 'PhabricatorConfigController',
|
||||
'PhabricatorConfigLocalSource' => 'PhabricatorConfigProxySource',
|
||||
'PhabricatorConfigManagementDeleteWorkflow' => 'PhabricatorConfigManagementWorkflow',
|
||||
|
|
|
@ -102,7 +102,6 @@ final class PhabricatorConfigEditController
|
|||
$error_view = null;
|
||||
if ($errors) {
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setTitle(pht('You broke everything!'))
|
||||
->setErrors($errors);
|
||||
} else if ($option->getHidden()) {
|
||||
$msg = pht(
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorConfigJSONOptionType
|
||||
extends PhabricatorConfigOptionType {
|
||||
|
||||
public function readRequest(
|
||||
PhabricatorConfigOption $option,
|
||||
AphrontRequest $request) {
|
||||
|
||||
$e_value = null;
|
||||
$errors = array();
|
||||
$storage_value = $request->getStr('value');
|
||||
$display_value = $request->getStr('value');
|
||||
|
||||
if (strlen($display_value)) {
|
||||
$storage_value = phutil_json_decode($display_value);
|
||||
if ($storage_value === null) {
|
||||
$e_value = pht('Invalid');
|
||||
$errors[] = pht(
|
||||
'Configuration value should be specified in JSON. The provided '.
|
||||
'value is not valid JSON.');
|
||||
} else {
|
||||
try {
|
||||
$this->validateOption($option, $storage_value);
|
||||
} catch (Exception $ex) {
|
||||
$e_value = pht('Invalid');
|
||||
$errors[] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$storage_value = null;
|
||||
}
|
||||
|
||||
return array($e_value, $errors, $storage_value, $display_value);
|
||||
}
|
||||
|
||||
public function getDisplayValue(
|
||||
PhabricatorConfigOption $option,
|
||||
PhabricatorConfigEntry $entry) {
|
||||
$value = $entry->getValue();
|
||||
if (!$value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$json = new PhutilJSON();
|
||||
return $json->encodeFormatted($value);
|
||||
}
|
||||
|
||||
public function renderControl(
|
||||
PhabricatorConfigOption $option,
|
||||
$display_value,
|
||||
$e_value) {
|
||||
|
||||
return id(new AphrontFormTextAreaControl())
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||
->setName('value')
|
||||
->setLabel(pht('Value'))
|
||||
->setValue($display_value)
|
||||
->setError($e_value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestStatusConfigOptionType
|
||||
extends PhabricatorConfigJSONOptionType {
|
||||
|
||||
public function validateOption(PhabricatorConfigOption $option, $value) {
|
||||
ManiphestTaskStatus::validateConfiguration($value);
|
||||
}
|
||||
|
||||
}
|
|
@ -46,6 +46,160 @@ final class PhabricatorManiphestConfigOptions
|
|||
),
|
||||
);
|
||||
|
||||
$status_type = 'custom:ManiphestStatusConfigOptionType';
|
||||
$status_defaults = array(
|
||||
'open' => array(
|
||||
'name' => pht('Open'),
|
||||
'special' => ManiphestTaskStatus::SPECIAL_DEFAULT,
|
||||
),
|
||||
'resolved' => array(
|
||||
'name' => pht('Resolved'),
|
||||
'name.full' => pht('Closed, Resolved'),
|
||||
'closed' => true,
|
||||
'special' => ManiphestTaskStatus::SPECIAL_CLOSED,
|
||||
'prefixes' => array(
|
||||
'closed',
|
||||
'closes',
|
||||
'close',
|
||||
'fix',
|
||||
'fixes',
|
||||
'fixed',
|
||||
'resolve',
|
||||
'resolves',
|
||||
'resolved',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'as resolved',
|
||||
'as fixed',
|
||||
),
|
||||
),
|
||||
'wontfix' => array(
|
||||
'name' => pht('Wontfix'),
|
||||
'name.full' => pht('Closed, Wontfix'),
|
||||
'closed' => true,
|
||||
'prefixes' => array(
|
||||
'wontfix',
|
||||
'wontfixes',
|
||||
'wontfixed',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'as wontfix',
|
||||
),
|
||||
),
|
||||
'invalid' => array(
|
||||
'name' => pht('Invalid'),
|
||||
'name.full' => pht('Closed, Invalid'),
|
||||
'closed' => true,
|
||||
'prefixes' => array(
|
||||
'invalidate',
|
||||
'invalidates',
|
||||
'invalidated',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'as invalid',
|
||||
),
|
||||
),
|
||||
'duplicate' => array(
|
||||
'name' => pht('Duplicate'),
|
||||
'name.full' => pht('Closed, Duplicate'),
|
||||
'transaction.icon' => 'delete',
|
||||
'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE,
|
||||
'closed' => true,
|
||||
),
|
||||
'spite' => array(
|
||||
'name' => pht('Spite'),
|
||||
'name.full' => pht('Closed, Spite'),
|
||||
'name.action' => pht('Spited'),
|
||||
'transaction.icon' => 'dislike',
|
||||
'silly' => true,
|
||||
'closed' => true,
|
||||
'prefixes' => array(
|
||||
'spite',
|
||||
'spites',
|
||||
'spited',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'out of spite',
|
||||
'as spite',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$status_description = $this->deformat(pht(<<<EOTEXT
|
||||
Allows you to edit, add, or remove the task statuses available in Maniphest,
|
||||
like "Open", "Resolved" and "Invalid". The configuration should contain a map
|
||||
of status constants to status specifications (see defaults below for examples).
|
||||
|
||||
The constant for each status should be 1-12 characters long and contain only
|
||||
lowercase letters and digits. Valid examples are "open", "closed", and
|
||||
"invalid". Users will not normally see these values.
|
||||
|
||||
The keys you can provide in a specification are:
|
||||
|
||||
- `name` //Required string.// Name of the status, like "Invalid".
|
||||
- `name.full` //Optional string.// Longer name, like "Closed, Invalid". This
|
||||
appears on the task detail view in the header.
|
||||
- `name.action` //Optional string.// Action name for email subjects, like
|
||||
"Marked Invalid".
|
||||
- `closed` //Optional bool.// Statuses are either "open" or "closed".
|
||||
Specifying `true` here will mark the status as closed (like "Resolved" or
|
||||
"Invalid"). By default, statuses are open.
|
||||
- `special` //Optional string.// Mark this status as special. The special
|
||||
statuses are:
|
||||
- `default` This is the default status for newly created tasks. You must
|
||||
designate one status as default, and it must be an open status.
|
||||
- `closed` This is the default status for closed tasks (for example, tasks
|
||||
closed via the "!close" action in email). You must designate one status
|
||||
as the default closed status, and it must be a closed status.
|
||||
- `duplicate` This is the status used when tasks are merged into one
|
||||
another as duplicates. You must designate one status for duplicates,
|
||||
and it must be a closed status.
|
||||
- `transaction.icon` //Optional string.// Allows you to choose a different
|
||||
icon to use for this status when showing status changes in the transaction
|
||||
log.
|
||||
- `transaction.color` //Optional string.// Allows you to choose a different
|
||||
color to use for this status when showing status changes in the transaction
|
||||
log.
|
||||
- `silly` //Optional bool.// Marks this status as silly, and thus wholly
|
||||
inappropriate for use by serious businesses.
|
||||
- `prefixes` //Optional list<string>.// Allows you to specify a list of
|
||||
text prefixes which will trigger a task transition into this status
|
||||
when mentioned in a commit message. For example, providing "closes" here
|
||||
will allow users to move tasks to this status by writing `Closes T123` in
|
||||
commit messages.
|
||||
- `suffixes` //Optional list<string>.// Allows you to specify a list of
|
||||
text suffixes which will trigger a task transition into this status
|
||||
when mentioned in a commit message, after a valid prefix. For example,
|
||||
providing "as invalid" here will allow users to move tasks
|
||||
to this status by writing `Closes T123 as invalid`, even if another status
|
||||
is selected by the "Closes" prefix.
|
||||
|
||||
Examining the default configuration and examples below will probably be helpful
|
||||
in understanding these options.
|
||||
|
||||
EOTEXT
|
||||
));
|
||||
|
||||
$status_example = array(
|
||||
'open' => array(
|
||||
'name' => 'Open',
|
||||
'special' => 'default',
|
||||
),
|
||||
'closed' => array(
|
||||
'name' => 'Closed',
|
||||
'special' => 'closed',
|
||||
'closed' => true,
|
||||
),
|
||||
'duplicate' => array(
|
||||
'name' => 'Duplicate',
|
||||
'special' => 'duplicate',
|
||||
'closed' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$json = new PhutilJSON();
|
||||
$status_example = $json->encodeFormatted($status_example);
|
||||
|
||||
// This is intentionally blank for now, until we can move more Maniphest
|
||||
// logic to custom fields.
|
||||
$default_fields = array();
|
||||
|
@ -92,6 +246,10 @@ final class PhabricatorManiphestConfigOptions
|
|||
"\n\n".
|
||||
'You can choose which priority is the default for newly created '.
|
||||
'tasks with `maniphest.default-priority`.')),
|
||||
$this->newOption('maniphest.statuses', $status_type, $status_defaults)
|
||||
->setSummary(pht('Configure Maniphest task statuses.'))
|
||||
->setDescription($status_description)
|
||||
->addExample($status_example, pht('Minimal Valid Config')),
|
||||
$this->newOption('maniphest.default-priority', 'int', 90)
|
||||
->setSummary(pht("Default task priority for create flows."))
|
||||
->setDescription(
|
||||
|
|
|
@ -17,83 +17,7 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
const SPECIAL_DUPLICATE = 'duplicate';
|
||||
|
||||
private static function getStatusConfig() {
|
||||
return array(
|
||||
self::STATUS_OPEN => array(
|
||||
'name' => pht('Open'),
|
||||
'special' => self::SPECIAL_DEFAULT,
|
||||
),
|
||||
self::STATUS_CLOSED_RESOLVED => array(
|
||||
'name' => pht('Resolved'),
|
||||
'name.full' => pht('Closed, Resolved'),
|
||||
'closed' => true,
|
||||
'special' => self::SPECIAL_CLOSED,
|
||||
'prefixes' => array(
|
||||
'closed',
|
||||
'closes',
|
||||
'close',
|
||||
'fix',
|
||||
'fixes',
|
||||
'fixed',
|
||||
'resolve',
|
||||
'resolves',
|
||||
'resolved',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'as resolved',
|
||||
'as fixed',
|
||||
),
|
||||
),
|
||||
self::STATUS_CLOSED_WONTFIX => array(
|
||||
'name' => pht('Wontfix'),
|
||||
'name.full' => pht('Closed, Wontfix'),
|
||||
'closed' => true,
|
||||
'prefixes' => array(
|
||||
'wontfix',
|
||||
'wontfixes',
|
||||
'wontfixed',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'as wontfix',
|
||||
),
|
||||
),
|
||||
self::STATUS_CLOSED_INVALID => array(
|
||||
'name' => pht('Invalid'),
|
||||
'name.full' => pht('Closed, Invalid'),
|
||||
'closed' => true,
|
||||
'prefixes' => array(
|
||||
'invalidate',
|
||||
'invalidates',
|
||||
'invalidated',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'as invalid',
|
||||
),
|
||||
),
|
||||
self::STATUS_CLOSED_DUPLICATE => array(
|
||||
'name' => pht('Duplicate'),
|
||||
'name.full' => pht('Closed, Duplicate'),
|
||||
'transaction.icon' => 'delete',
|
||||
'special' => self::SPECIAL_DUPLICATE,
|
||||
'closed' => true,
|
||||
),
|
||||
self::STATUS_CLOSED_SPITE => array(
|
||||
'name' => pht('Spite'),
|
||||
'name.full' => pht('Closed, Spite'),
|
||||
'name.action' => pht('Spited'),
|
||||
'transaction.icon' => 'dislike',
|
||||
'silly' => true,
|
||||
'closed' => true,
|
||||
'prefixes' => array(
|
||||
'spite',
|
||||
'spites',
|
||||
'spited',
|
||||
),
|
||||
'suffixes' => array(
|
||||
'out of spite',
|
||||
'as spite',
|
||||
),
|
||||
),
|
||||
);
|
||||
return PhabricatorEnv::getEnvConfig('maniphest.statuses');
|
||||
}
|
||||
|
||||
private static function getEnabledStatusMap() {
|
||||
|
|
Loading…
Reference in a new issue