Page MenuHomePhabricator

D8585.diff
No OneTemporary

D8585.diff

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

Mime Type
text/plain
Expires
Fri, Apr 4, 3:18 AM (3 d, 20 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7225477
Default Alt Text
D8585.diff (14 KB)

Event Timeline