@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+235 -78
+4
src/__phutil_library_map__.php
··· 882 882 'ManiphestReplyHandler' => 'applications/maniphest/mail/ManiphestReplyHandler.php', 883 883 'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php', 884 884 'ManiphestSearchIndexer' => 'applications/maniphest/search/ManiphestSearchIndexer.php', 885 + 'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php', 885 886 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', 886 887 'ManiphestSubscribeController' => 'applications/maniphest/controller/ManiphestSubscribeController.php', 887 888 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', ··· 1323 1324 'PhabricatorConfigIssueListController' => 'applications/config/controller/PhabricatorConfigIssueListController.php', 1324 1325 'PhabricatorConfigIssueViewController' => 'applications/config/controller/PhabricatorConfigIssueViewController.php', 1325 1326 'PhabricatorConfigJSON' => 'applications/config/json/PhabricatorConfigJSON.php', 1327 + 'PhabricatorConfigJSONOptionType' => 'applications/config/custom/PhabricatorConfigJSONOptionType.php', 1326 1328 'PhabricatorConfigListController' => 'applications/config/controller/PhabricatorConfigListController.php', 1327 1329 'PhabricatorConfigLocalSource' => 'infrastructure/env/PhabricatorConfigLocalSource.php', 1328 1330 'PhabricatorConfigManagementDeleteWorkflow' => 'applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php', ··· 3521 3523 'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler', 3522 3524 'ManiphestReportController' => 'ManiphestController', 3523 3525 'ManiphestSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 3526 + 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType', 3524 3527 'ManiphestSubpriorityController' => 'ManiphestController', 3525 3528 'ManiphestSubscribeController' => 'ManiphestController', 3526 3529 'ManiphestTask' => ··· 4032 4035 'PhabricatorConfigIgnoreController' => 'PhabricatorApplicationsController', 4033 4036 'PhabricatorConfigIssueListController' => 'PhabricatorConfigController', 4034 4037 'PhabricatorConfigIssueViewController' => 'PhabricatorConfigController', 4038 + 'PhabricatorConfigJSONOptionType' => 'PhabricatorConfigOptionType', 4035 4039 'PhabricatorConfigListController' => 'PhabricatorConfigController', 4036 4040 'PhabricatorConfigLocalSource' => 'PhabricatorConfigProxySource', 4037 4041 'PhabricatorConfigManagementDeleteWorkflow' => 'PhabricatorConfigManagementWorkflow',
-1
src/applications/config/controller/PhabricatorConfigEditController.php
··· 102 102 $error_view = null; 103 103 if ($errors) { 104 104 $error_view = id(new AphrontErrorView()) 105 - ->setTitle(pht('You broke everything!')) 106 105 ->setErrors($errors); 107 106 } else if ($option->getHidden()) { 108 107 $msg = pht(
+62
src/applications/config/custom/PhabricatorConfigJSONOptionType.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigJSONOptionType 4 + extends PhabricatorConfigOptionType { 5 + 6 + public function readRequest( 7 + PhabricatorConfigOption $option, 8 + AphrontRequest $request) { 9 + 10 + $e_value = null; 11 + $errors = array(); 12 + $storage_value = $request->getStr('value'); 13 + $display_value = $request->getStr('value'); 14 + 15 + if (strlen($display_value)) { 16 + $storage_value = phutil_json_decode($display_value); 17 + if ($storage_value === null) { 18 + $e_value = pht('Invalid'); 19 + $errors[] = pht( 20 + 'Configuration value should be specified in JSON. The provided '. 21 + 'value is not valid JSON.'); 22 + } else { 23 + try { 24 + $this->validateOption($option, $storage_value); 25 + } catch (Exception $ex) { 26 + $e_value = pht('Invalid'); 27 + $errors[] = $ex->getMessage(); 28 + } 29 + } 30 + } else { 31 + $storage_value = null; 32 + } 33 + 34 + return array($e_value, $errors, $storage_value, $display_value); 35 + } 36 + 37 + public function getDisplayValue( 38 + PhabricatorConfigOption $option, 39 + PhabricatorConfigEntry $entry) { 40 + $value = $entry->getValue(); 41 + if (!$value) { 42 + return ''; 43 + } 44 + 45 + $json = new PhutilJSON(); 46 + return $json->encodeFormatted($value); 47 + } 48 + 49 + public function renderControl( 50 + PhabricatorConfigOption $option, 51 + $display_value, 52 + $e_value) { 53 + 54 + return id(new AphrontFormTextAreaControl()) 55 + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 56 + ->setName('value') 57 + ->setLabel(pht('Value')) 58 + ->setValue($display_value) 59 + ->setError($e_value); 60 + } 61 + 62 + }
+10
src/applications/maniphest/config/ManiphestStatusConfigOptionType.php
··· 1 + <?php 2 + 3 + final class ManiphestStatusConfigOptionType 4 + extends PhabricatorConfigJSONOptionType { 5 + 6 + public function validateOption(PhabricatorConfigOption $option, $value) { 7 + ManiphestTaskStatus::validateConfiguration($value); 8 + } 9 + 10 + }
+158
src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
··· 46 46 ), 47 47 ); 48 48 49 + $status_type = 'custom:ManiphestStatusConfigOptionType'; 50 + $status_defaults = array( 51 + 'open' => array( 52 + 'name' => pht('Open'), 53 + 'special' => ManiphestTaskStatus::SPECIAL_DEFAULT, 54 + ), 55 + 'resolved' => array( 56 + 'name' => pht('Resolved'), 57 + 'name.full' => pht('Closed, Resolved'), 58 + 'closed' => true, 59 + 'special' => ManiphestTaskStatus::SPECIAL_CLOSED, 60 + 'prefixes' => array( 61 + 'closed', 62 + 'closes', 63 + 'close', 64 + 'fix', 65 + 'fixes', 66 + 'fixed', 67 + 'resolve', 68 + 'resolves', 69 + 'resolved', 70 + ), 71 + 'suffixes' => array( 72 + 'as resolved', 73 + 'as fixed', 74 + ), 75 + ), 76 + 'wontfix' => array( 77 + 'name' => pht('Wontfix'), 78 + 'name.full' => pht('Closed, Wontfix'), 79 + 'closed' => true, 80 + 'prefixes' => array( 81 + 'wontfix', 82 + 'wontfixes', 83 + 'wontfixed', 84 + ), 85 + 'suffixes' => array( 86 + 'as wontfix', 87 + ), 88 + ), 89 + 'invalid' => array( 90 + 'name' => pht('Invalid'), 91 + 'name.full' => pht('Closed, Invalid'), 92 + 'closed' => true, 93 + 'prefixes' => array( 94 + 'invalidate', 95 + 'invalidates', 96 + 'invalidated', 97 + ), 98 + 'suffixes' => array( 99 + 'as invalid', 100 + ), 101 + ), 102 + 'duplicate' => array( 103 + 'name' => pht('Duplicate'), 104 + 'name.full' => pht('Closed, Duplicate'), 105 + 'transaction.icon' => 'delete', 106 + 'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE, 107 + 'closed' => true, 108 + ), 109 + 'spite' => array( 110 + 'name' => pht('Spite'), 111 + 'name.full' => pht('Closed, Spite'), 112 + 'name.action' => pht('Spited'), 113 + 'transaction.icon' => 'dislike', 114 + 'silly' => true, 115 + 'closed' => true, 116 + 'prefixes' => array( 117 + 'spite', 118 + 'spites', 119 + 'spited', 120 + ), 121 + 'suffixes' => array( 122 + 'out of spite', 123 + 'as spite', 124 + ), 125 + ), 126 + ); 127 + 128 + $status_description = $this->deformat(pht(<<<EOTEXT 129 + Allows you to edit, add, or remove the task statuses available in Maniphest, 130 + like "Open", "Resolved" and "Invalid". The configuration should contain a map 131 + of status constants to status specifications (see defaults below for examples). 132 + 133 + The constant for each status should be 1-12 characters long and contain only 134 + lowercase letters and digits. Valid examples are "open", "closed", and 135 + "invalid". Users will not normally see these values. 136 + 137 + The keys you can provide in a specification are: 138 + 139 + - `name` //Required string.// Name of the status, like "Invalid". 140 + - `name.full` //Optional string.// Longer name, like "Closed, Invalid". This 141 + appears on the task detail view in the header. 142 + - `name.action` //Optional string.// Action name for email subjects, like 143 + "Marked Invalid". 144 + - `closed` //Optional bool.// Statuses are either "open" or "closed". 145 + Specifying `true` here will mark the status as closed (like "Resolved" or 146 + "Invalid"). By default, statuses are open. 147 + - `special` //Optional string.// Mark this status as special. The special 148 + statuses are: 149 + - `default` This is the default status for newly created tasks. You must 150 + designate one status as default, and it must be an open status. 151 + - `closed` This is the default status for closed tasks (for example, tasks 152 + closed via the "!close" action in email). You must designate one status 153 + as the default closed status, and it must be a closed status. 154 + - `duplicate` This is the status used when tasks are merged into one 155 + another as duplicates. You must designate one status for duplicates, 156 + and it must be a closed status. 157 + - `transaction.icon` //Optional string.// Allows you to choose a different 158 + icon to use for this status when showing status changes in the transaction 159 + log. 160 + - `transaction.color` //Optional string.// Allows you to choose a different 161 + color to use for this status when showing status changes in the transaction 162 + log. 163 + - `silly` //Optional bool.// Marks this status as silly, and thus wholly 164 + inappropriate for use by serious businesses. 165 + - `prefixes` //Optional list<string>.// Allows you to specify a list of 166 + text prefixes which will trigger a task transition into this status 167 + when mentioned in a commit message. For example, providing "closes" here 168 + will allow users to move tasks to this status by writing `Closes T123` in 169 + commit messages. 170 + - `suffixes` //Optional list<string>.// Allows you to specify a list of 171 + text suffixes which will trigger a task transition into this status 172 + when mentioned in a commit message, after a valid prefix. For example, 173 + providing "as invalid" here will allow users to move tasks 174 + to this status by writing `Closes T123 as invalid`, even if another status 175 + is selected by the "Closes" prefix. 176 + 177 + Examining the default configuration and examples below will probably be helpful 178 + in understanding these options. 179 + 180 + EOTEXT 181 + )); 182 + 183 + $status_example = array( 184 + 'open' => array( 185 + 'name' => 'Open', 186 + 'special' => 'default', 187 + ), 188 + 'closed' => array( 189 + 'name' => 'Closed', 190 + 'special' => 'closed', 191 + 'closed' => true, 192 + ), 193 + 'duplicate' => array( 194 + 'name' => 'Duplicate', 195 + 'special' => 'duplicate', 196 + 'closed' => true, 197 + ), 198 + ); 199 + 200 + $json = new PhutilJSON(); 201 + $status_example = $json->encodeFormatted($status_example); 202 + 49 203 // This is intentionally blank for now, until we can move more Maniphest 50 204 // logic to custom fields. 51 205 $default_fields = array(); ··· 92 246 "\n\n". 93 247 'You can choose which priority is the default for newly created '. 94 248 'tasks with `maniphest.default-priority`.')), 249 + $this->newOption('maniphest.statuses', $status_type, $status_defaults) 250 + ->setSummary(pht('Configure Maniphest task statuses.')) 251 + ->setDescription($status_description) 252 + ->addExample($status_example, pht('Minimal Valid Config')), 95 253 $this->newOption('maniphest.default-priority', 'int', 90) 96 254 ->setSummary(pht("Default task priority for create flows.")) 97 255 ->setDescription(
+1 -77
src/applications/maniphest/constants/ManiphestTaskStatus.php
··· 17 17 const SPECIAL_DUPLICATE = 'duplicate'; 18 18 19 19 private static function getStatusConfig() { 20 - return array( 21 - self::STATUS_OPEN => array( 22 - 'name' => pht('Open'), 23 - 'special' => self::SPECIAL_DEFAULT, 24 - ), 25 - self::STATUS_CLOSED_RESOLVED => array( 26 - 'name' => pht('Resolved'), 27 - 'name.full' => pht('Closed, Resolved'), 28 - 'closed' => true, 29 - 'special' => self::SPECIAL_CLOSED, 30 - 'prefixes' => array( 31 - 'closed', 32 - 'closes', 33 - 'close', 34 - 'fix', 35 - 'fixes', 36 - 'fixed', 37 - 'resolve', 38 - 'resolves', 39 - 'resolved', 40 - ), 41 - 'suffixes' => array( 42 - 'as resolved', 43 - 'as fixed', 44 - ), 45 - ), 46 - self::STATUS_CLOSED_WONTFIX => array( 47 - 'name' => pht('Wontfix'), 48 - 'name.full' => pht('Closed, Wontfix'), 49 - 'closed' => true, 50 - 'prefixes' => array( 51 - 'wontfix', 52 - 'wontfixes', 53 - 'wontfixed', 54 - ), 55 - 'suffixes' => array( 56 - 'as wontfix', 57 - ), 58 - ), 59 - self::STATUS_CLOSED_INVALID => array( 60 - 'name' => pht('Invalid'), 61 - 'name.full' => pht('Closed, Invalid'), 62 - 'closed' => true, 63 - 'prefixes' => array( 64 - 'invalidate', 65 - 'invalidates', 66 - 'invalidated', 67 - ), 68 - 'suffixes' => array( 69 - 'as invalid', 70 - ), 71 - ), 72 - self::STATUS_CLOSED_DUPLICATE => array( 73 - 'name' => pht('Duplicate'), 74 - 'name.full' => pht('Closed, Duplicate'), 75 - 'transaction.icon' => 'delete', 76 - 'special' => self::SPECIAL_DUPLICATE, 77 - 'closed' => true, 78 - ), 79 - self::STATUS_CLOSED_SPITE => array( 80 - 'name' => pht('Spite'), 81 - 'name.full' => pht('Closed, Spite'), 82 - 'name.action' => pht('Spited'), 83 - 'transaction.icon' => 'dislike', 84 - 'silly' => true, 85 - 'closed' => true, 86 - 'prefixes' => array( 87 - 'spite', 88 - 'spites', 89 - 'spited', 90 - ), 91 - 'suffixes' => array( 92 - 'out of spite', 93 - 'as spite', 94 - ), 95 - ), 96 - ); 20 + return PhabricatorEnv::getEnvConfig('maniphest.statuses'); 97 21 } 98 22 99 23 private static function getEnabledStatusMap() {