@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.

Add basic, rough support for changing field behavior based on object subtype

Summary:
Ref T13248. This will probably need quite a bit of refinement, but we can reasonably allow subtype definitions to adjust custom field behavior.

Some places where we use fields are global, and always need to show all the fields. For example, on `/maniphest/`, where you can search across all tasks, you need to be able to search across all fields that are present on any task.

Likewise, if you "export" a bunch of tasks into a spreadsheet, we need to have columns for every field.

However, when you're clearly in the scope of a particular task (like viewing or editing `T123`), there's no reason we can't hide fields based on the task subtype.

To start with, allow subtypes to override "disabled" and "name" for custom fields.

Test Plan:
- Defined several custom fields and several subtypes.
- Disabled/renamed some fields for some subtypes.
- Viewed/edited tasks of different subtypes, got desired field behavior.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13248

Differential Revision: https://secure.phabricator.com/D20161

+175 -2
+23
src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
··· 342 342 - `icon` //Optional string.// Icon for the subtype. 343 343 - `children` //Optional map.// Configure options shown to the user when 344 344 they "Create Subtask". See below. 345 + - `fields` //Optional map.// Configure field behaviors. See below. 345 346 346 347 Each subtype must have a unique key, and you must define a subtype with 347 348 the key "%s", which is used as a default subtype. ··· 397 398 398 399 If only one option would be presented, the user will be taken directly to the 399 400 appropriate form instead of being prompted to choose a form. 401 + 402 + The `fields` key can configure the behavior of custom fields on specific 403 + task subtypes. For example: 404 + 405 + ``` 406 + { 407 + ... 408 + "fields": { 409 + "custom.some-field": { 410 + "disabled": true 411 + } 412 + } 413 + ... 414 + } 415 + ``` 416 + 417 + Each field supports these options: 418 + 419 + - `disabled` //Optional bool.// Allows you to disable fields on certain 420 + subtypes. 421 + - `name` //Optional string.// Custom name of this field for the subtype. 422 + 400 423 EOTEXT 401 424 , 402 425 $subtype_default_key));
+17 -2
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 165 165 $extensions = array(); 166 166 } 167 167 168 + // See T13248. Create a template object to provide to extensions. We 169 + // adjust the template to have the intended subtype, so that extensions 170 + // may change behavior based on the form subtype. 171 + 172 + $template_object = clone $object; 173 + if ($this->getIsCreate()) { 174 + if ($this->supportsSubtypes()) { 175 + $config = $this->getEditEngineConfiguration(); 176 + $subtype = $config->getSubtype(); 177 + $template_object->setSubtype($subtype); 178 + } 179 + } 180 + 168 181 foreach ($extensions as $extension) { 169 182 $extension->setViewer($viewer); 170 183 171 - if (!$extension->supportsObject($this, $object)) { 184 + if (!$extension->supportsObject($this, $template_object)) { 172 185 continue; 173 186 } 174 187 175 - $extension_fields = $extension->buildCustomEditFields($this, $object); 188 + $extension_fields = $extension->buildCustomEditFields( 189 + $this, 190 + $template_object); 176 191 177 192 // TODO: Validate this in more detail with a more tailored error. 178 193 assert_instances_of($extension_fields, 'PhabricatorEditField');
+34
src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php
··· 13 13 private $color; 14 14 private $childSubtypes = array(); 15 15 private $childIdentifiers = array(); 16 + private $fieldConfiguration = array(); 16 17 17 18 public function setKey($key) { 18 19 $this->key = $key; ··· 94 95 return $view; 95 96 } 96 97 98 + public function setSubtypeFieldConfiguration( 99 + $subtype_key, 100 + array $configuration) { 101 + $this->fieldConfiguration[$subtype_key] = $configuration; 102 + return $this; 103 + } 104 + 105 + public function getSubtypeFieldConfiguration($subtype_key) { 106 + return idx($this->fieldConfiguration, $subtype_key); 107 + } 108 + 97 109 public static function validateSubtypeKey($subtype) { 98 110 if (strlen($subtype) > 64) { 99 111 throw new Exception( ··· 139 151 'color' => 'optional string', 140 152 'icon' => 'optional string', 141 153 'children' => 'optional map<string, wild>', 154 + 'fields' => 'optional map<string, wild>', 142 155 )); 143 156 144 157 $key = $value['key']; ··· 183 196 'or the other, but not both.')); 184 197 } 185 198 } 199 + 200 + $fields = idx($value, 'fields'); 201 + if ($fields) { 202 + foreach ($fields as $field_key => $configuration) { 203 + PhutilTypeSpec::checkMap( 204 + $configuration, 205 + array( 206 + 'disabled' => 'optional bool', 207 + 'name' => 'optional string', 208 + )); 209 + } 210 + } 186 211 } 187 212 188 213 if (!isset($map[self::SUBTYPE_DEFAULT])) { ··· 231 256 232 257 if ($child_forms) { 233 258 $subtype->setChildFormIdentifiers($child_forms); 259 + } 260 + 261 + $field_configurations = idx($entry, 'fields'); 262 + if ($field_configurations) { 263 + foreach ($field_configurations as $field_key => $field_configuration) { 264 + $subtype->setSubtypeFieldConfiguration( 265 + $field_key, 266 + $field_configuration); 267 + } 234 268 } 235 269 236 270 $map[$key] = $subtype;
+87
src/infrastructure/customfield/field/PhabricatorCustomField.php
··· 74 74 $spec, 75 75 $object); 76 76 77 + $fields = self::adjustCustomFieldsForObjectSubtype( 78 + $object, 79 + $role, 80 + $fields); 81 + 77 82 foreach ($fields as $key => $field) { 83 + // NOTE: We perform this filtering in "buildFieldList()", but may need 84 + // to filter again after subtype adjustment. 85 + if (!$field->isFieldEnabled()) { 86 + unset($fields[$key]); 87 + continue; 88 + } 89 + 78 90 if (!$field->shouldEnableForRole($role)) { 79 91 unset($fields[$key]); 92 + continue; 80 93 } 81 94 } 82 95 ··· 1620 1633 } 1621 1634 1622 1635 return null; 1636 + } 1637 + 1638 + private static function adjustCustomFieldsForObjectSubtype( 1639 + PhabricatorCustomFieldInterface $object, 1640 + $role, 1641 + array $fields) { 1642 + assert_instances_of($fields, __CLASS__); 1643 + 1644 + // We only apply subtype adjustment for some roles. For example, when 1645 + // writing Herald rules or building a Search interface, we always want to 1646 + // show all the fields in their default state, so we do not apply any 1647 + // adjustments. 1648 + $subtype_roles = array( 1649 + self::ROLE_EDITENGINE, 1650 + self::ROLE_VIEW, 1651 + ); 1652 + 1653 + $subtype_roles = array_fuse($subtype_roles); 1654 + if (!isset($subtype_roles[$role])) { 1655 + return $fields; 1656 + } 1657 + 1658 + // If the object doesn't support subtypes, we can't possibly make 1659 + // any adjustments based on subtype. 1660 + if (!($object instanceof PhabricatorEditEngineSubtypeInterface)) { 1661 + return $fields; 1662 + } 1663 + 1664 + $subtype_map = $object->newEditEngineSubtypeMap(); 1665 + $subtype_key = $object->getEditEngineSubtype(); 1666 + $subtype_object = $subtype_map->getSubtype($subtype_key); 1667 + 1668 + $map = array(); 1669 + foreach ($fields as $field) { 1670 + $modern_key = $field->getModernFieldKey(); 1671 + if (!strlen($modern_key)) { 1672 + continue; 1673 + } 1674 + 1675 + $map[$modern_key] = $field; 1676 + } 1677 + 1678 + foreach ($map as $field_key => $field) { 1679 + // For now, only support overriding standard custom fields. In the 1680 + // future there's no technical or product reason we couldn't let you 1681 + // override (some properites of) other fields like "Title", but they 1682 + // don't usually support appropriate "setX()" methods today. 1683 + if (!($field instanceof PhabricatorStandardCustomField)) { 1684 + // For fields that are proxies on top of StandardCustomField, which 1685 + // is how most application custom fields work today, we can reconfigure 1686 + // the proxied field instead. 1687 + $field = $field->getProxy(); 1688 + if (!$field || !($field instanceof PhabricatorStandardCustomField)) { 1689 + continue; 1690 + } 1691 + } 1692 + 1693 + $subtype_config = $subtype_object->getSubtypeFieldConfiguration( 1694 + $field_key); 1695 + 1696 + if (!$subtype_config) { 1697 + continue; 1698 + } 1699 + 1700 + if (isset($subtype_config['disabled'])) { 1701 + $field->setIsEnabled(!$subtype_config['disabled']); 1702 + } 1703 + 1704 + if (isset($subtype_config['name'])) { 1705 + $field->setFieldName($subtype_config['name']); 1706 + } 1707 + } 1708 + 1709 + return $fields; 1623 1710 } 1624 1711 1625 1712 }
+14
src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
··· 18 18 private $isCopyable; 19 19 private $hasStorageValue; 20 20 private $isBuiltin; 21 + private $isEnabled = true; 21 22 22 23 abstract public function getFieldType(); 23 24 ··· 173 174 174 175 public function getRawStandardFieldKey() { 175 176 return $this->rawKey; 177 + } 178 + 179 + public function setIsEnabled($is_enabled) { 180 + $this->isEnabled = $is_enabled; 181 + return $this; 182 + } 183 + 184 + public function getIsEnabled() { 185 + return $this->isEnabled; 186 + } 187 + 188 + public function isFieldEnabled() { 189 + return $this->getIsEnabled(); 176 190 } 177 191 178 192