diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 66f6a41cc4..0bcd39abef 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index af9ceda313..bd0079750f 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -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( diff --git a/src/applications/config/custom/PhabricatorConfigJSONOptionType.php b/src/applications/config/custom/PhabricatorConfigJSONOptionType.php new file mode 100644 index 0000000000..91250e05c4 --- /dev/null +++ b/src/applications/config/custom/PhabricatorConfigJSONOptionType.php @@ -0,0 +1,62 @@ +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 index 0000000000..826de0184a --- /dev/null +++ b/src/applications/maniphest/config/ManiphestStatusConfigOptionType.php @@ -0,0 +1,10 @@ + 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(<<.// 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.// 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( diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php index dc277a6c7a..f7b552cdfc 100644 --- a/src/applications/maniphest/constants/ManiphestTaskStatus.php +++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php @@ -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() {