Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15459425
D8585.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D8585.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -882,6 +882,7 @@
'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 @@
'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 @@
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
'ManiphestReportController' => 'ManiphestController',
'ManiphestSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
+ 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'ManiphestSubpriorityController' => 'ManiphestController',
'ManiphestSubscribeController' => 'ManiphestController',
'ManiphestTask' =>
@@ -4032,6 +4035,7 @@
'PhabricatorConfigIgnoreController' => 'PhabricatorApplicationsController',
'PhabricatorConfigIssueListController' => 'PhabricatorConfigController',
'PhabricatorConfigIssueViewController' => 'PhabricatorConfigController',
+ 'PhabricatorConfigJSONOptionType' => 'PhabricatorConfigOptionType',
'PhabricatorConfigListController' => 'PhabricatorConfigController',
'PhabricatorConfigLocalSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigManagementDeleteWorkflow' => 'PhabricatorConfigManagementWorkflow',
diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php
--- a/src/applications/config/controller/PhabricatorConfigEditController.php
+++ b/src/applications/config/controller/PhabricatorConfigEditController.php
@@ -102,7 +102,6 @@
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
- ->setTitle(pht('You broke everything!'))
->setErrors($errors);
} else if ($option->getHidden()) {
$msg = pht(
diff --git a/src/applications/config/custom/PhabricatorConfigJSONOptionType.php b/src/applications/config/custom/PhabricatorConfigJSONOptionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/config/custom/PhabricatorConfigJSONOptionType.php
@@ -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);
+ }
+
+}
diff --git a/src/applications/maniphest/config/ManiphestStatusConfigOptionType.php b/src/applications/maniphest/config/ManiphestStatusConfigOptionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/config/ManiphestStatusConfigOptionType.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ManiphestStatusConfigOptionType
+ extends PhabricatorConfigJSONOptionType {
+
+ public function validateOption(PhabricatorConfigOption $option, $value) {
+ ManiphestTaskStatus::validateConfiguration($value);
+ }
+
+}
diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
--- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
+++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
@@ -46,6 +46,160 @@
),
);
+ $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 @@
"\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(
diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php
--- a/src/applications/maniphest/constants/ManiphestTaskStatus.php
+++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php
@@ -17,83 +17,7 @@
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() {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Apr 1, 12:48 PM (6 d, 7 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7225477
Default Alt Text
D8585.diff (14 KB)
Attached To
Mode
D8585: Make Maniphest task statuses user configurable
Attached
Detach File
Event Timeline
Log In to Comment