Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php
index 85ed148db7..f774578240 100644
--- a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php
+++ b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php
@@ -1,73 +1,80 @@
<?php
final class ManiphestPriorityEmailCommand
extends ManiphestEmailCommand {
public function getCommand() {
return 'priority';
}
public function getCommandSyntax() {
return '**!priority** //priority//';
}
public function getCommandSummary() {
return pht('Change the priority of a task.');
}
public function getCommandDescription() {
$names = ManiphestTaskPriority::getTaskPriorityMap();
$keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$table = array();
$table[] = '| '.pht('Priority').' | '.pht('Keywords');
$table[] = '|---|---|';
foreach ($keywords as $priority => $words) {
+ if (ManiphestTaskPriority::isDisabledPriority($priority)) {
+ continue;
+ }
$words = implode(', ', $words);
$table[] = '| '.$names[$priority].' | '.$words;
}
$table = implode("\n", $table);
return pht(
"To change the priority of a task, specify the desired priority, like ".
"`%s`. This table shows the configured names for priority levels.".
"\n\n%s\n\n".
"If you specify an invalid priority, the command is ignored. This ".
"command has no effect if you do not specify a priority.",
'!priority high',
$table);
}
public function buildTransactions(
PhabricatorUser $viewer,
PhabricatorApplicationTransactionInterface $object,
PhabricatorMetaMTAReceivedMail $mail,
$command,
array $argv) {
$xactions = array();
$target = phutil_utf8_strtolower(head($argv));
$priority = null;
$keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
foreach ($keywords as $key => $words) {
foreach ($words as $word) {
if ($word == $target) {
$priority = $key;
break;
}
}
}
if ($priority === null) {
return array();
}
+ if (ManiphestTaskPriority::isDisabledPriority($priority)) {
+ return array();
+ }
+
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
->setNewValue($priority);
return $xactions;
}
}
diff --git a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php
index c9fa07494f..aa74e33bec 100644
--- a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php
+++ b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php
@@ -1,72 +1,80 @@
<?php
final class ManiphestStatusEmailCommand
extends ManiphestEmailCommand {
public function getCommand() {
return 'status';
}
public function getCommandSyntax() {
return '**!status** //status//';
}
public function getCommandSummary() {
return pht('Change the status of a task.');
}
public function getCommandDescription() {
$names = ManiphestTaskStatus::getTaskStatusMap();
$keywords = ManiphestTaskStatus::getTaskStatusKeywordsMap();
$table = array();
$table[] = '| '.pht('Status').' | '.pht('Keywords');
$table[] = '|---|---|';
foreach ($keywords as $status => $words) {
+ if (ManiphestTaskStatus::isDisabledStatus($status)) {
+ continue;
+ }
+
$words = implode(', ', $words);
$table[] = '| '.$names[$status].' | '.$words;
}
$table = implode("\n", $table);
return pht(
"To change the status of a task, specify the desired status, like ".
"`%s`. This table shows the configured names for statuses.\n\n%s\n\n".
"If you specify an invalid status, the command is ignored. This ".
"command has no effect if you do not specify a status.",
'!status invalid',
$table);
}
public function buildTransactions(
PhabricatorUser $viewer,
PhabricatorApplicationTransactionInterface $object,
PhabricatorMetaMTAReceivedMail $mail,
$command,
array $argv) {
$xactions = array();
$target = phutil_utf8_strtolower(head($argv));
$status = null;
$keywords = ManiphestTaskStatus::getTaskStatusKeywordsMap();
foreach ($keywords as $key => $words) {
foreach ($words as $word) {
if ($word == $target) {
$status = $key;
break;
}
}
}
if ($status === null) {
return array();
}
+ if (ManiphestTaskStatus::isDisabledStatus($status)) {
+ return array();
+ }
+
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(ManiphestTransaction::TYPE_STATUS)
->setNewValue($status);
return $xactions;
}
}
diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
index 5a3c05bb22..e4424fef9b 100644
--- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
+++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
@@ -1,332 +1,340 @@
<?php
final class PhabricatorManiphestConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Maniphest');
}
public function getDescription() {
return pht('Configure Maniphest.');
}
public function getFontIcon() {
return 'fa-anchor';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
$priority_defaults = array(
100 => array(
'name' => pht('Unbreak Now!'),
'short' => pht('Unbreak!'),
'color' => 'pink',
'keywords' => array('unbreak'),
),
90 => array(
'name' => pht('Needs Triage'),
'short' => pht('Triage'),
'color' => 'violet',
'keywords' => array('triage'),
),
80 => array(
'name' => pht('High'),
'short' => pht('High'),
'color' => 'red',
'keywords' => array('high'),
),
50 => array(
'name' => pht('Normal'),
'short' => pht('Normal'),
'color' => 'orange',
'keywords' => array('normal'),
),
25 => array(
'name' => pht('Low'),
'short' => pht('Low'),
'color' => 'yellow',
'keywords' => array('low'),
),
0 => array(
'name' => pht('Wishlist'),
'short' => pht('Wish'),
'color' => 'sky',
'keywords' => array('wish', 'wishlist'),
),
);
$status_type = 'custom:ManiphestStatusConfigOptionType';
$status_defaults = array(
'open' => array(
'name' => pht('Open'),
'special' => ManiphestTaskStatus::SPECIAL_DEFAULT,
'prefixes' => array(
'open',
'opens',
'reopen',
'reopens',
),
),
'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',
),
'keywords' => array('closed', 'fixed', 'resolved'),
),
'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' => 'fa-files-o',
'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE,
'closed' => true,
),
'spite' => array(
'name' => pht('Spite'),
'name.full' => pht('Closed, Spite'),
'name.action' => pht('Spited'),
'transaction.icon' => 'fa-thumbs-o-down',
'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 or via the quick close button in
Maniphest). 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. Please see UIExamples, Icons and Images for a list.
- `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.
- `keywords` //Optional list<string>.// Allows you to specify a list
of keywords which can be used with `!status` commands in email to select
this status.
+ - `disabled` //Optional bool.// Marks this status as no longer in use so
+ tasks can not be created or edited to have this status. Existing tasks with
+ this status will not be affected, but you can batch edit them or let them
+ die out on their own.
Statuses will appear in the UI in the order specified. Note the status marked
`special` as `duplicate` is not settable directly and will not appear in UI
elements, and that any status marked `silly` does not appear if Phabricator
is configured with `phabricator.serious-business` set to true.
Examining the default configuration and examples below will probably be helpful
in understanding these options.
EOTEXT
));
$status_example = array(
'open' => array(
'name' => pht('Open'),
'special' => 'default',
),
'closed' => array(
'name' => pht('Closed'),
'special' => 'closed',
'closed' => true,
),
'duplicate' => array(
'name' => pht('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();
foreach ($default_fields as $key => $enabled) {
$default_fields[$key] = array(
'disabled' => !$enabled,
);
}
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
$fields_example = array(
'mycompany.estimated-hours' => array(
'name' => pht('Estimated Hours'),
'type' => 'int',
'caption' => pht('Estimated number of hours this will take.'),
),
);
$fields_json = id(new PhutilJSON())->encodeFormatted($fields_example);
return array(
$this->newOption('maniphest.custom-field-definitions', 'wild', array())
->setSummary(pht('Custom Maniphest fields.'))
->setDescription(
pht(
'Array of custom fields for Maniphest tasks. For details on '.
'adding custom fields to Maniphest, see "Configuring Custom '.
'Fields" in the documentation.'))
->addExample($fields_json, pht('Valid setting')),
$this->newOption('maniphest.fields', $custom_field_type, $default_fields)
->setCustomData(id(new ManiphestTask())->getCustomFieldBaseClass())
->setDescription(pht('Select and reorder task fields.')),
$this->newOption('maniphest.priorities', 'wild', $priority_defaults)
->setSummary(pht('Configure Maniphest priority names.'))
->setDescription(
pht(
'Allows you to edit or override the default priorities available '.
'in Maniphest, like "High", "Normal" and "Low". The configuration '.
'should contain a map of priority constants to priority '.
'specifications (see defaults below for examples).'.
"\n\n".
'The keys you can define for a priority are:'.
"\n\n".
' - `name` Name of the priority.'."\n".
' - `short` Alternate shorter name, used in UIs where there is '.
' not much space available.'."\n".
' - `color` A color for this priority, like "red" or "blue".'.
' - `keywords` An optional list of keywords which can '.
' be used to select this priority when using `!priority` '.
- ' commands in email.'.
+ ' commands in email.'."\n".
+ ' - `disabled` Optional boolean to prevent users from choosing '.
+ ' this priority when creating or editing tasks. Existing '.
+ ' tasks will be unaffected, and can be batch edited to a '.
+ ' different priority or left to eventually die out.'.
"\n\n".
'You can choose which priority is the default for newly created '.
'tasks with `%s`.',
'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(
pht(
'Choose a default priority for newly created tasks. You can '.
'review and adjust available priorities by using the '.
'%s configuration option. The default value (`90`) '.
'corresponds to the default "Needs Triage" priority.',
'maniphest.priorities')),
$this->newOption(
'metamta.maniphest.subject-prefix',
'string',
'[Maniphest]')
->setDescription(pht('Subject prefix for Maniphest mail.')),
$this->newOption(
'maniphest.priorities.unbreak-now',
'int',
100)
->setSummary(pht('Priority used to populate "Unbreak Now" on home.'))
->setDescription(
pht(
'Temporary setting. If set, this priority is used to populate the '.
'"Unbreak Now" panel on the home page. You should adjust this if '.
'you adjust priorities using `%s`.',
'maniphest.priorities')),
$this->newOption(
'maniphest.priorities.needs-triage',
'int',
90)
->setSummary(pht('Priority used to populate "Needs Triage" on home.'))
->setDescription(
pht(
'Temporary setting. If set, this priority is used to populate the '.
'"Needs Triage" panel on the home page. You should adjust this if '.
'you adjust priorities using `%s`.',
'maniphest.priorities')),
);
}
}
diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php
index 38a56f767b..55ebafe597 100644
--- a/src/applications/maniphest/constants/ManiphestTaskPriority.php
+++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php
@@ -1,114 +1,119 @@
<?php
final class ManiphestTaskPriority extends ManiphestConstants {
/**
* Get the priorities and their full descriptions.
*
* @return map Priorities to descriptions.
*/
public static function getTaskPriorityMap() {
$map = self::getConfig();
foreach ($map as $key => $spec) {
$map[$key] = idx($spec, 'name', $key);
}
return $map;
}
/**
* Get the priorities and their command keywords.
*
* @return map Priorities to lists of command keywords.
*/
public static function getTaskPriorityKeywordsMap() {
$map = self::getConfig();
foreach ($map as $key => $spec) {
$words = idx($spec, 'keywords', array());
if (!is_array($words)) {
$words = array($words);
}
foreach ($words as $word_key => $word) {
$words[$word_key] = phutil_utf8_strtolower($word);
}
$words = array_unique($words);
$map[$key] = $words;
}
return $map;
}
/**
* Get the priorities and their related short (one-word) descriptions.
*
* @return map Priorities to short descriptions.
*/
public static function getShortNameMap() {
$map = self::getConfig();
foreach ($map as $key => $spec) {
$map[$key] = idx($spec, 'short', idx($spec, 'name', $key));
}
return $map;
}
/**
* Get a map from priority constants to their colors.
*
* @return map<int, string> Priorities to colors.
*/
public static function getColorMap() {
$map = self::getConfig();
foreach ($map as $key => $spec) {
$map[$key] = idx($spec, 'color', 'grey');
}
return $map;
}
/**
* Return the default priority for this instance of Phabricator.
*
* @return int The value of the default priority constant.
*/
public static function getDefaultPriority() {
return PhabricatorEnv::getEnvConfig('maniphest.default-priority');
}
/**
* Retrieve the full name of the priority level provided.
*
* @param int A priority level.
* @return string The priority name if the level is a valid one.
*/
public static function getTaskPriorityName($priority) {
return idx(self::getTaskPriorityMap(), $priority, $priority);
}
/**
* Retrieve the color of the priority level given
*
* @param int A priority level.
* @return string The color of the priority if the level is valid,
* or black if it is not.
*/
public static function getTaskPriorityColor($priority) {
return idx(self::getColorMap(), $priority, 'black');
}
public static function getTaskPriorityIcon($priority) {
return 'fa-arrow-right';
}
+ public static function isDisabledPriority($priority) {
+ $config = idx(self::getConfig(), $priority, array());
+ return idx($config, 'disabled', false);
+ }
+
private static function getConfig() {
$config = PhabricatorEnv::getEnvConfig('maniphest.priorities');
krsort($config);
return $config;
}
}
diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php
index 5904b47595..5efc2ea56c 100644
--- a/src/applications/maniphest/constants/ManiphestTaskStatus.php
+++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php
@@ -1,355 +1,360 @@
<?php
/**
* @task validate Configuration Validation
*/
final class ManiphestTaskStatus extends ManiphestConstants {
const STATUS_OPEN = 'open';
const STATUS_CLOSED_RESOLVED = 'resolved';
const STATUS_CLOSED_WONTFIX = 'wontfix';
const STATUS_CLOSED_INVALID = 'invalid';
const STATUS_CLOSED_DUPLICATE = 'duplicate';
const STATUS_CLOSED_SPITE = 'spite';
const SPECIAL_DEFAULT = 'default';
const SPECIAL_CLOSED = 'closed';
const SPECIAL_DUPLICATE = 'duplicate';
private static function getStatusConfig() {
return PhabricatorEnv::getEnvConfig('maniphest.statuses');
}
private static function getEnabledStatusMap() {
$spec = self::getStatusConfig();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
foreach ($spec as $const => $status) {
if ($is_serious && !empty($status['silly'])) {
unset($spec[$const]);
continue;
}
}
return $spec;
}
public static function getTaskStatusMap() {
return ipull(self::getEnabledStatusMap(), 'name');
}
/**
* Get the statuses and their command keywords.
*
* @return map Statuses to lists of command keywords.
*/
public static function getTaskStatusKeywordsMap() {
$map = self::getEnabledStatusMap();
foreach ($map as $key => $spec) {
$words = idx($spec, 'keywords', array());
if (!is_array($words)) {
$words = array($words);
}
// For statuses, we include the status name because it's usually
// at least somewhat meaningful.
$words[] = $key;
foreach ($words as $word_key => $word) {
$words[$word_key] = phutil_utf8_strtolower($word);
}
$words = array_unique($words);
$map[$key] = $words;
}
return $map;
}
public static function getTaskStatusName($status) {
return self::getStatusAttribute($status, 'name', pht('Unknown Status'));
}
public static function getTaskStatusFullName($status) {
$name = self::getStatusAttribute($status, 'name.full');
if ($name !== null) {
return $name;
}
return self::getStatusAttribute($status, 'name', pht('Unknown Status'));
}
public static function renderFullDescription($status) {
if (self::isOpenStatus($status)) {
$color = 'status';
$icon_color = 'bluegrey';
} else {
$color = 'status-dark';
$icon_color = '';
}
$icon = self::getStatusIcon($status);
$img = id(new PHUIIconView())
->setIconFont($icon.' '.$icon_color);
$tag = phutil_tag(
'span',
array(
'class' => 'phui-header-status phui-header-'.$color,
),
array(
$img,
self::getTaskStatusFullName($status),
));
return $tag;
}
private static function getSpecialStatus($special) {
foreach (self::getStatusConfig() as $const => $status) {
if (idx($status, 'special') == $special) {
return $const;
}
}
return null;
}
public static function getDefaultStatus() {
return self::getSpecialStatus(self::SPECIAL_DEFAULT);
}
public static function getDefaultClosedStatus() {
return self::getSpecialStatus(self::SPECIAL_CLOSED);
}
public static function getDuplicateStatus() {
return self::getSpecialStatus(self::SPECIAL_DUPLICATE);
}
public static function getOpenStatusConstants() {
$result = array();
foreach (self::getEnabledStatusMap() as $const => $status) {
if (empty($status['closed'])) {
$result[] = $const;
}
}
return $result;
}
public static function getClosedStatusConstants() {
$all = array_keys(self::getTaskStatusMap());
$open = self::getOpenStatusConstants();
return array_diff($all, $open);
}
public static function isOpenStatus($status) {
foreach (self::getOpenStatusConstants() as $constant) {
if ($status == $constant) {
return true;
}
}
return false;
}
public static function isClosedStatus($status) {
return !self::isOpenStatus($status);
}
public static function getStatusActionName($status) {
return self::getStatusAttribute($status, 'name.action');
}
public static function getStatusColor($status) {
return self::getStatusAttribute($status, 'transaction.color');
}
+ public static function isDisabledStatus($status) {
+ return self::getStatusAttribute($status, 'disabled');
+ }
+
public static function getStatusIcon($status) {
$icon = self::getStatusAttribute($status, 'transaction.icon');
if ($icon) {
return $icon;
}
if (self::isOpenStatus($status)) {
return 'fa-exclamation-circle';
} else {
return 'fa-check-square-o';
}
}
public static function getStatusPrefixMap() {
$map = array();
foreach (self::getEnabledStatusMap() as $const => $status) {
foreach (idx($status, 'prefixes', array()) as $prefix) {
$map[$prefix] = $const;
}
}
$map += array(
'ref' => null,
'refs' => null,
'references' => null,
'cf.' => null,
);
return $map;
}
public static function getStatusSuffixMap() {
$map = array();
foreach (self::getEnabledStatusMap() as $const => $status) {
foreach (idx($status, 'suffixes', array()) as $prefix) {
$map[$prefix] = $const;
}
}
return $map;
}
private static function getStatusAttribute($status, $key, $default = null) {
$config = self::getStatusConfig();
$spec = idx($config, $status);
if ($spec) {
return idx($spec, $key, $default);
}
return $default;
}
/* -( Configuration Validation )------------------------------------------- */
/**
* @task validate
*/
public static function isValidStatusConstant($constant) {
if (strlen($constant) > 12) {
return false;
}
if (!preg_match('/^[a-z0-9]+\z/', $constant)) {
return false;
}
return true;
}
/**
* @task validate
*/
public static function validateConfiguration(array $config) {
foreach ($config as $key => $value) {
if (!self::isValidStatusConstant($key)) {
throw new Exception(
pht(
'Key "%s" is not a valid status constant. Status constants must '.
'be 1-12 characters long and contain only lowercase letters (a-z) '.
'and digits (0-9). For example, "%s" or "%s" are reasonable '.
'choices.',
$key,
'open',
'closed'));
}
if (!is_array($value)) {
throw new Exception(
pht(
'Value for key "%s" should be a dictionary.',
$key));
}
PhutilTypeSpec::checkMap(
$value,
array(
'name' => 'string',
'name.full' => 'optional string',
'name.action' => 'optional string',
'closed' => 'optional bool',
'special' => 'optional string',
'transaction.icon' => 'optional string',
'transaction.color' => 'optional string',
'silly' => 'optional bool',
'prefixes' => 'optional list<string>',
'suffixes' => 'optional list<string>',
'keywords' => 'optional list<string>',
+ 'disabled' => 'optional bool',
));
}
$special_map = array();
foreach ($config as $key => $value) {
$special = idx($value, 'special');
if (!$special) {
continue;
}
if (isset($special_map[$special])) {
throw new Exception(
pht(
'Configuration has two statuses both marked with the special '.
'attribute "%s" ("%s" and "%s"). There should be only one.',
$special,
$special_map[$special],
$key));
}
switch ($special) {
case self::SPECIAL_DEFAULT:
if (!empty($value['closed'])) {
throw new Exception(
pht(
'Status "%s" is marked as default, but it is a closed '.
'status. The default status should be an open status.',
$key));
}
break;
case self::SPECIAL_CLOSED:
if (empty($value['closed'])) {
throw new Exception(
pht(
'Status "%s" is marked as the default status for closing '.
'tasks, but is not a closed status. It should be a closed '.
'status.',
$key));
}
break;
case self::SPECIAL_DUPLICATE:
if (empty($value['closed'])) {
throw new Exception(
pht(
'Status "%s" is marked as the status for closing tasks as '.
'duplicates, but it is not a closed status. It should '.
'be a closed status.',
$key));
}
break;
}
$special_map[$special] = $key;
}
// NOTE: We're not explicitly validating that we have at least one open
// and one closed status, because the DEFAULT and CLOSED specials imply
// that to be true. If those change in the future, that might become a
// reasonable thing to validate.
$required = array(
self::SPECIAL_DEFAULT,
self::SPECIAL_CLOSED,
self::SPECIAL_DUPLICATE,
);
foreach ($required as $required_special) {
if (!isset($special_map[$required_special])) {
throw new Exception(
pht(
'Configuration defines no task status with special attribute '.
'"%s", but you must specify a status which fills this special '.
'role.',
$required_special));
}
}
}
}
diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php
index 3dfb740009..235b8b3a4d 100644
--- a/src/applications/maniphest/editor/ManiphestEditEngine.php
+++ b/src/applications/maniphest/editor/ManiphestEditEngine.php
@@ -1,127 +1,184 @@
<?php
final class ManiphestEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'maniphest.task';
public function getEngineName() {
return pht('Maniphest Tasks');
}
public function getEngineApplicationClass() {
return 'PhabricatorManiphestApplication';
}
protected function newEditableObject() {
return ManiphestTask::initializeNewTask($this->getViewer());
}
protected function newObjectQuery() {
return id(new ManiphestTaskQuery());
}
protected function getObjectCreateTitleText($object) {
return pht('Create New Task');
}
protected function getObjectEditTitleText($object) {
return pht('Edit %s %s', $object->getMonogram(), $object->getTitle());
}
protected function getObjectEditShortText($object) {
return $object->getMonogram();
}
protected function getObjectCreateShortText() {
return pht('Create Task');
}
protected function getCommentViewHeaderText($object) {
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if (!$is_serious) {
return pht('Weigh In');
}
return parent::getCommentViewHeaderText($object);
}
protected function getObjectViewURI($object) {
return '/'.$object->getMonogram();
}
protected function buildCustomEditFields($object) {
- // See T4819.
- $status_map = ManiphestTaskStatus::getTaskStatusMap();
- $dup_status = ManiphestTaskStatus::getDuplicateStatus();
- if ($object->getStatus() != $dup_status) {
- unset($status_map[$dup_status]);
- }
-
- $priority_map = ManiphestTaskPriority::getTaskPriorityMap();
+ $status_map = $this->getTaskStatusMap($object);
+ $priority_map = $this->getTaskPriorityMap($object);
if ($object->isClosed()) {
$priority_label = null;
$owner_label = null;
$default_status = ManiphestTaskStatus::getDefaultStatus();
} else {
$priority_label = pht('Change Priority');
$owner_label = pht('Assign / Claim');
$default_status = ManiphestTaskStatus::getDefaultClosedStatus();
}
if ($object->getOwnerPHID()) {
$owner_value = array($object->getOwnerPHID());
} else {
$owner_value = array($this->getViewer()->getPHID());
}
return array(
id(new PhabricatorTextEditField())
->setKey('title')
->setLabel(pht('Title'))
->setDescription(pht('Name of the task.'))
->setTransactionType(ManiphestTransaction::TYPE_TITLE)
->setIsRequired(true)
->setValue($object->getTitle()),
id(new PhabricatorSelectEditField())
->setKey('status')
->setLabel(pht('Status'))
->setDescription(pht('Status of the task.'))
->setTransactionType(ManiphestTransaction::TYPE_STATUS)
->setValue($object->getStatus())
->setOptions($status_map)
->setCommentActionLabel(pht('Change Status'))
->setCommentActionDefaultValue($default_status),
id(new PhabricatorUsersEditField())
->setKey('owner')
->setAliases(array('ownerPHID', 'assign', 'assigned'))
->setLabel(pht('Assigned To'))
->setDescription(pht('User who is responsible for the task.'))
->setTransactionType(ManiphestTransaction::TYPE_OWNER)
->setSingleValue($object->getOwnerPHID())
->setCommentActionLabel($owner_label)
->setCommentActionDefaultValue($owner_value),
id(new PhabricatorSelectEditField())
->setKey('priority')
->setLabel(pht('Priority'))
->setDescription(pht('Priority of the task.'))
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
->setValue($object->getPriority())
->setOptions($priority_map)
->setCommentActionLabel($priority_label),
id(new PhabricatorRemarkupEditField())
->setKey('description')
->setLabel(pht('Description'))
->setDescription(pht('Task description.'))
->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
->setValue($object->getDescription()),
);
}
protected function getEditorURI() {
// TODO: Remove when cutting over.
return $this->getApplication()->getApplicationURI('editpro/');
}
+ private function getTaskStatusMap(ManiphestTask $task) {
+ $status_map = ManiphestTaskStatus::getTaskStatusMap();
+
+ $current_status = $task->getStatus();
+
+ // If the current status is something we don't recognize (maybe an older
+ // status which was deleted), put a dummy entry in the status map so that
+ // saving the form doesn't destroy any data by accident.
+ if (idx($status_map, $current_status) === null) {
+ $status_map[$current_status] = pht('<Unknown: %s>', $current_status);
+ }
+
+ $dup_status = ManiphestTaskStatus::getDuplicateStatus();
+ foreach ($status_map as $status => $status_name) {
+ // Always keep the task's current status.
+ if ($status == $current_status) {
+ continue;
+ }
+
+ // Don't allow tasks to be changed directly into "Closed, Duplicate"
+ // status. Instead, you have to merge them. See T4819.
+ if ($status == $dup_status) {
+ unset($status_map[$status]);
+ continue;
+ }
+
+ // Don't let new or existing tasks be moved into a disabled status.
+ if (ManiphestTaskStatus::isDisabledStatus($status)) {
+ unset($status_map[$status]);
+ continue;
+ }
+ }
+
+ return $status_map;
+ }
+
+ private function getTaskPriorityMap(ManiphestTask $task) {
+ $priority_map = ManiphestTaskPriority::getTaskPriorityMap();
+ $current_priority = $task->getPriority();
+
+ // If the current value isn't a legitimate one, put it in the dropdown
+ // anyway so saving the form doesn't cause a side effects.
+ if (idx($priority_map, $current_priority) === null) {
+ $priority_map[$current_priority] = pht(
+ '<Unknown: %s>',
+ $current_priority);
+ }
+
+ foreach ($priority_map as $priority => $priority_name) {
+ // Always keep the current priority.
+ if ($priority == $current_priority) {
+ continue;
+ }
+
+ if (ManiphestTaskPriority::isDisabledPriority($priority)) {
+ unset($priority_map[$priority]);
+ continue;
+ }
+ }
+
+ return $priority_map;
+ }
+
}
diff --git a/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php b/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php
index 4e7dd639d6..217267b8cb 100644
--- a/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php
+++ b/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php
@@ -1,41 +1,47 @@
<?php
final class ManiphestTaskPriorityDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse Priorities');
}
public function getPlaceholderText() {
return pht('Type a task priority name...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorManiphestApplication';
}
public function loadResults() {
$results = $this->buildResults();
return $this->filterResultsAgainstTokens($results);
}
public function renderTokens(array $values) {
return $this->renderTokensFromResults($this->buildResults(), $values);
}
private function buildResults() {
$results = array();
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
foreach ($priority_map as $value => $name) {
- $results[$value] = id(new PhabricatorTypeaheadResult())
+ $result = id(new PhabricatorTypeaheadResult())
->setIcon(ManiphestTaskPriority::getTaskPriorityIcon($value))
->setPHID($value)
->setName($name);
+
+ if (ManiphestTaskPriority::isDisabledPriority($value)) {
+ $result->setClosed(pht('Disabled'));
+ }
+
+ $results[$value] = $result;
}
return $results;
}
}
diff --git a/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php b/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php
index 8f7e881a9f..fe59c974e6 100644
--- a/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php
+++ b/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php
@@ -1,42 +1,48 @@
<?php
final class ManiphestTaskStatusDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse Statuses');
}
public function getPlaceholderText() {
return pht('Type a task status name...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorManiphestApplication';
}
public function loadResults() {
$results = $this->buildResults();
return $this->filterResultsAgainstTokens($results);
}
protected function renderSpecialTokens(array $values) {
return $this->renderTokensFromResults($this->buildResults(), $values);
}
private function buildResults() {
$results = array();
$status_map = ManiphestTaskStatus::getTaskStatusMap();
foreach ($status_map as $value => $name) {
- $results[$value] = id(new PhabricatorTypeaheadResult())
+ $result = id(new PhabricatorTypeaheadResult())
->setIcon(ManiphestTaskStatus::getStatusIcon($value))
->setPHID($value)
->setName($name);
+
+ if (ManiphestTaskStatus::isDisabledStatus($value)) {
+ $result->setClosed(pht('Disabled'));
+ }
+
+ $results[$value] = $result;
}
return $results;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 28, 3:42 AM (9 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
107737
Default Alt Text
(41 KB)

Event Timeline