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

Support "Select" custom fields in Herald rules

Summary: Fixes T5016. Ref T655. Ref T8434. Ref T8726. For "select" custom fields, permit construction of Herald rules.

Test Plan: {F602997}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T655, T5016, T8434, T8726

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

+218 -145
+2
src/__phutil_library_map__.php
··· 2777 2777 'PhabricatorStandardCustomFieldText' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php', 2778 2778 'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php', 2779 2779 'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php', 2780 + 'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php', 2780 2781 'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php', 2781 2782 'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php', 2782 2783 'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php', ··· 6678 6679 'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField', 6679 6680 'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldPHIDs', 6680 6681 'PhabricatorStandardPageView' => 'PhabricatorBarePageView', 6682 + 'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource', 6681 6683 'PhabricatorStatusController' => 'PhabricatorController', 6682 6684 'PhabricatorStatusUIExample' => 'PhabricatorUIExample', 6683 6685 'PhabricatorStorageFixtureScopeGuard' => 'Phobject',
+10 -37
src/applications/herald/adapter/HeraldAdapter.php
··· 882 882 HeraldCondition $condition, 883 883 array $handles) { 884 884 885 - $impl = $this->getFieldImplementation($condition->getFieldName()); 886 - if ($impl) { 887 - return $impl->getEditorValue( 888 - $viewer, 889 - $condition->getValue()); 890 - } 885 + $field = $this->requireFieldImplementation($condition->getFieldName()); 891 886 892 - $value = $condition->getValue(); 893 - if (is_array($value)) { 894 - $value_map = array(); 895 - foreach ($value as $k => $phid) { 896 - $value_map[$phid] = $handles[$phid]->getName(); 897 - } 898 - $value = $value_map; 899 - } 900 - 901 - return $value; 887 + return $field->getEditorValue( 888 + $viewer, 889 + $condition->getFieldCondition(), 890 + $condition->getValue()); 902 891 } 903 892 904 893 public function renderRuleAsText( ··· 1020 1009 PhabricatorHandleList $handles, 1021 1010 PhabricatorUser $viewer) { 1022 1011 1023 - $impl = $this->getFieldImplementation($condition->getFieldName()); 1024 - if ($impl) { 1025 - return $impl->renderConditionValue( 1026 - $viewer, 1027 - $condition->getFieldCondition(), 1028 - $condition->getValue()); 1029 - } 1030 - 1031 - $value = $condition->getValue(); 1032 - if (!is_array($value)) { 1033 - $value = array($value); 1034 - } 1035 - 1036 - foreach ($value as $index => $val) { 1037 - $handle = $handles->getHandleIfExists($val); 1038 - if ($handle) { 1039 - $value[$index] = $handle->renderLink(); 1040 - } 1041 - } 1012 + $field = $this->requireFieldImplementation($condition->getFieldName()); 1042 1013 1043 - $value = phutil_implode_html(', ', $value); 1044 - return $value; 1014 + return $field->renderConditionValue( 1015 + $viewer, 1016 + $condition->getFieldCondition(), 1017 + $condition->getValue()); 1045 1018 } 1046 1019 1047 1020 private function renderActionTargetAsText(
+7 -6
src/applications/herald/controller/HeraldTranscriptController.php
··· 116 116 } 117 117 118 118 protected function renderConditionTestValue($condition, $handles) { 119 + // TODO: This is all a hacky mess and should be driven through FieldValue 120 + // eventually. 121 + 119 122 switch ($condition->getFieldName()) { 120 123 case HeraldAnotherRuleField::FIELDCONST: 121 124 $value = array($condition->getTestValue()); ··· 128 131 if (!is_scalar($value) && $value !== null) { 129 132 foreach ($value as $key => $phid) { 130 133 $handle = idx($handles, $phid); 131 - if ($handle) { 134 + if ($handle && $handle->isComplete()) { 132 135 $value[$key] = $handle->getName(); 133 136 } else { 134 - // This shouldn't ever really happen as we are supposed to have 135 - // grabbed handles for everything, but be super liberal in what 136 - // we accept here since we expect all sorts of weird issues as we 137 - // version the system. 138 - $value[$key] = pht('Unknown Object #%s', $phid); 137 + // This happens for things like task priorities, statuses, and 138 + // custom fields. 139 + $value[$key] = $phid; 139 140 } 140 141 } 141 142 sort($value);
+18 -39
src/applications/herald/field/HeraldField.php
··· 24 24 throw new PhutilMethodNotImplementedException(); 25 25 } 26 26 27 + protected function getDatasourceValueMap() { 28 + return null; 29 + } 30 + 27 31 public function getHeraldFieldConditions() { 28 32 $standard_type = $this->getHeraldFieldStandardType(); 29 33 switch ($standard_type) { ··· 103 107 case HeraldAdapter::CONDITION_NOT_EXISTS: 104 108 return new HeraldEmptyFieldValue(); 105 109 default: 106 - return id(new HeraldTokenizerFieldValue()) 110 + $tokenizer = id(new HeraldTokenizerFieldValue()) 107 111 ->setKey($this->getHeraldFieldName()) 108 112 ->setDatasource($this->getDatasource()); 113 + 114 + $value_map = $this->getDatasourceValueMap(); 115 + if ($value_map !== null) { 116 + $tokenizer->setValueMap($value_map); 117 + } 118 + 119 + return $tokenizer; 109 120 } 110 121 break; 111 122 ··· 130 141 $value) { 131 142 132 143 $value_type = $this->getHeraldFieldValueType($condition); 133 - if ($value_type instanceof HeraldFieldValue) { 134 - $value_type->setViewer($viewer); 135 - return $value_type->renderFieldValue($value); 136 - } 137 - 138 - // TODO: While this is less of a mess than it used to be, it would still 139 - // be nice to push this down into individual fields better eventually and 140 - // stop guessing which values are PHIDs and which aren't. 141 - 142 - if (!is_array($value)) { 143 - return $value; 144 - } 145 - 146 - $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN; 147 - 148 - foreach ($value as $key => $val) { 149 - if (is_string($val)) { 150 - if (phid_get_type($val) !== $type_unknown) { 151 - $value[$key] = $viewer->renderHandle($val); 152 - } 153 - } 154 - } 155 - 156 - return phutil_implode_html(', ', $value); 144 + $value_type->setViewer($viewer); 145 + return $value_type->renderFieldValue($value); 157 146 } 158 147 159 148 public function getEditorValue( 160 149 PhabricatorUser $viewer, 150 + $condition, 161 151 $value) { 162 152 163 - // TODO: This should be better structured and pushed down into individual 164 - // fields. As it is used to manually build tokenizer tokens, it can 165 - // probably be removed entirely. 166 - 167 - if (is_array($value)) { 168 - $handles = $viewer->loadHandles($value); 169 - $value_map = array(); 170 - foreach ($value as $k => $phid) { 171 - $value_map[$phid] = $handles[$phid]->getName(); 172 - } 173 - $value = $value_map; 174 - } 175 - 176 - return $value; 153 + $value_type = $this->getHeraldFieldValueType($condition); 154 + $value_type->setViewer($viewer); 155 + return $value_type->renderEditorValue($value); 177 156 } 178 157 179 158 final public function setAdapter(HeraldAdapter $adapter) {
+4
src/applications/herald/value/HeraldEmptyFieldValue.php
··· 15 15 return null; 16 16 } 17 17 18 + public function renderEditorValue($value) { 19 + return null; 20 + } 21 + 18 22 }
+1
src/applications/herald/value/HeraldFieldValue.php
··· 12 12 abstract public function getFieldValueKey(); 13 13 abstract public function getControlType(); 14 14 abstract public function renderFieldValue($value); 15 + abstract public function renderEditorValue($value); 15 16 16 17 public function setViewer(PhabricatorUser $viewer) { 17 18 $this->viewer = $viewer;
+4
src/applications/herald/value/HeraldSelectFieldValue.php
··· 61 61 return idx($options, $value, $value); 62 62 } 63 63 64 + public function renderEditorValue($value) { 65 + return $value; 66 + } 67 + 64 68 }
+4 -1
src/applications/herald/value/HeraldTextFieldValue.php
··· 11 11 return self::CONTROL_TEXT; 12 12 } 13 13 14 + public function renderFieldValue($value) { 15 + return $value; 16 + } 14 17 15 - public function renderFieldValue($value) { 18 + public function renderEditorValue($value) { 16 19 return $value; 17 20 } 18 21
+43
src/applications/herald/value/HeraldTokenizerFieldValue.php
··· 5 5 6 6 private $key; 7 7 private $datasource; 8 + private $valueMap; 8 9 9 10 public function setKey($key) { 10 11 $this->key = $key; ··· 22 23 23 24 public function getDatasource() { 24 25 return $this->datasource; 26 + } 27 + 28 + public function setValueMap(array $value_map) { 29 + $this->valueMap = $value_map; 30 + return $this; 31 + } 32 + 33 + public function getValueMap() { 34 + return $this->valueMap; 25 35 } 26 36 27 37 public function getFieldValueKey() { ··· 54 64 55 65 public function renderFieldValue($value) { 56 66 $viewer = $this->getViewer(); 67 + $value = (array)$value; 68 + 69 + if ($this->valueMap !== null) { 70 + foreach ($value as $k => $v) { 71 + $value[$k] = idx($this->valueMap, $v, $v); 72 + } 73 + return implode(', ', $value); 74 + } 75 + 57 76 return $viewer->renderHandleList((array)$value)->setAsInline(true); 77 + } 78 + 79 + public function renderEditorValue($value) { 80 + $viewer = $this->getViewer(); 81 + $value = (array)$value; 82 + 83 + // TODO: This should eventually render properly through the datasource 84 + // to get icons and colors. 85 + 86 + if ($this->valueMap !== null) { 87 + $map = array(); 88 + foreach ($value as $v) { 89 + $map[$v] = idx($this->valueMap, $v, $v); 90 + } 91 + return $map; 92 + } 93 + 94 + $handles = $viewer->loadHandles($value); 95 + 96 + $map = array(); 97 + foreach ($value as $v) { 98 + $map[$v] = $handles[$v]->getName(); 99 + } 100 + return $map; 58 101 } 59 102 60 103 }
+2 -30
src/applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php
··· 21 21 return new ManiphestTaskPriorityDatasource(); 22 22 } 23 23 24 - public function renderConditionValue( 25 - PhabricatorUser $viewer, 26 - $condition, 27 - $value) { 28 - 29 - $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); 30 - 31 - $value = (array)$value; 32 - foreach ($value as $index => $val) { 33 - $name = idx($priority_map, $val); 34 - if ($name !== null) { 35 - $value[$index] = $name; 36 - } 37 - } 38 - 39 - return implode(', ', $value); 40 - } 41 - 42 - public function getEditorValue( 43 - PhabricatorUser $viewer, 44 - $value) { 45 - 46 - $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); 47 - 48 - $value_map = array(); 49 - foreach ($value as $priority) { 50 - $value_map[$priority] = idx($priority_map, $priority, $priority); 51 - } 52 - 53 - return $value_map; 24 + protected function getDatasourceValueMap() { 25 + return ManiphestTaskPriority::getTaskPriorityMap(); 54 26 } 55 27 56 28 }
+2 -30
src/applications/maniphest/herald/ManiphestTaskStatusHeraldField.php
··· 21 21 return new ManiphestTaskStatusDatasource(); 22 22 } 23 23 24 - public function renderConditionValue( 25 - PhabricatorUser $viewer, 26 - $condition, 27 - $value) { 28 - 29 - $status_map = ManiphestTaskStatus::getTaskStatusMap(); 30 - 31 - $value = (array)$value; 32 - foreach ($value as $index => $val) { 33 - $name = idx($status_map, $val); 34 - if ($name !== null) { 35 - $value[$index] = $name; 36 - } 37 - } 38 - 39 - return implode(', ', $value); 40 - } 41 - 42 - public function getEditorValue( 43 - PhabricatorUser $viewer, 44 - $value) { 45 - 46 - $status_map = ManiphestTaskStatus::getTaskStatusMap(); 47 - 48 - $value_map = array(); 49 - foreach ($value as $status) { 50 - $value_map[$status] = idx($status_map, $status, $status); 51 - } 52 - 53 - return $value_map; 24 + protected function getDatasourceValueMap() { 25 + return ManiphestTaskStatus::getTaskStatusMap(); 54 26 } 55 27 56 28 }
+93
src/infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php
··· 1 + <?php 2 + 3 + final class PhabricatorStandardSelectCustomFieldDatasource 4 + extends PhabricatorTypeaheadDatasource { 5 + 6 + public function getBrowseTitle() { 7 + return pht('Browse Values'); 8 + } 9 + 10 + public function getPlaceholderText() { 11 + return pht('Type a field value...'); 12 + } 13 + 14 + public function getDatasourceApplicationClass() { 15 + return null; 16 + } 17 + 18 + public function loadResults() { 19 + $viewer = $this->getViewer(); 20 + 21 + $class = $this->getParameter('object'); 22 + if (!class_exists($class)) { 23 + throw new Exception( 24 + pht( 25 + 'Custom field class "%s" does not exist.', 26 + $class)); 27 + } 28 + 29 + $reflection = new ReflectionClass($class); 30 + $interface = 'PhabricatorCustomFieldInterface'; 31 + if (!$reflection->implementsInterface($interface)) { 32 + throw new Exception( 33 + pht( 34 + 'Custom field class "%s" does not implement interface "%s".', 35 + $class, 36 + $interface)); 37 + } 38 + 39 + $role = $this->getParameter('role'); 40 + if (!strlen($role)) { 41 + throw new Exception(pht('No custom field role specified.')); 42 + } 43 + 44 + $object = newv($class, array()); 45 + $field_list = PhabricatorCustomField::getObjectFields($object, $role); 46 + 47 + $field_key = $this->getParameter('key'); 48 + if (!strlen($field_key)) { 49 + throw new Exception(pht('No custom field key specified.')); 50 + } 51 + 52 + $field = null; 53 + foreach ($field_list->getFields() as $candidate_field) { 54 + if ($candidate_field->getFieldKey() == $field_key) { 55 + $field = $candidate_field; 56 + break; 57 + } 58 + } 59 + 60 + if ($field === null) { 61 + throw new Exception( 62 + pht( 63 + 'No field with field key "%s" exists for objects of class "%s" with '. 64 + 'custom field role "%s".', 65 + $field_key, 66 + $class, 67 + $role)); 68 + } 69 + 70 + if (!($field instanceof PhabricatorStandardCustomFieldSelect)) { 71 + $field = $field->getProxy(); 72 + if (!($field instanceof PhabricatorStandardCustomFieldSelect)) { 73 + throw new Exception( 74 + pht( 75 + 'Field "%s" is not a standard select field, nor a proxy of a '. 76 + 'standard select field.', 77 + $field_key)); 78 + } 79 + } 80 + 81 + $options = $field->getOptions(); 82 + 83 + $results = array(); 84 + foreach ($options as $key => $option) { 85 + $results[] = id(new PhabricatorTypeaheadResult()) 86 + ->setName($option) 87 + ->setPHID($key); 88 + } 89 + 90 + return $this->filterResultsAgainstTokens($results); 91 + } 92 + 93 + }
+28 -2
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php
··· 59 59 $form->appendChild($control); 60 60 } 61 61 62 - private function getOptions() { 62 + public function getOptions() { 63 63 return $this->getFieldConfigValue('options', array()); 64 64 } 65 65 ··· 78 78 } 79 79 return idx($this->getOptions(), $this->getFieldValue()); 80 80 } 81 - 82 81 83 82 public function getApplicationTransactionTitle( 84 83 PhabricatorApplicationTransaction $xaction) { ··· 108 107 $old, 109 108 $new); 110 109 } 110 + } 111 + 112 + public function shouldAppearInHerald() { 113 + return true; 114 + } 115 + 116 + public function getHeraldFieldConditions() { 117 + return array( 118 + HeraldAdapter::CONDITION_IS_ANY, 119 + HeraldAdapter::CONDITION_IS_NOT_ANY, 120 + ); 121 + } 122 + 123 + public function getHeraldFieldValueType($condition) { 124 + $parameters = array( 125 + 'object' => get_class($this->getObject()), 126 + 'role' => PhabricatorCustomField::ROLE_HERALD, 127 + 'key' => $this->getFieldKey(), 128 + ); 129 + 130 + $datasource = id(new PhabricatorStandardSelectCustomFieldDatasource()) 131 + ->setParameters($parameters); 132 + 133 + return id(new HeraldTokenizerFieldValue()) 134 + ->setKey('custom.'.$this->getFieldKey()) 135 + ->setDatasource($datasource) 136 + ->setValueMap($this->getOptions()); 111 137 } 112 138 113 139 }