@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 Herald action modularization more aggressive

Summary:
Ref T8726. Herald actions are technically sort-of modular already, but make them more aggressively modular similar to `HeraldField`.

I plan to obsolete and replace `HeraldCustomAction`.

Test Plan: Saw actions in nice groups; created and ran a "Do Nothing" action. Transcripts are a bit rough for now.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: joshuaspence, epriestley

Maniphest Tasks: T8726

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

+467 -104
+11 -11
resources/celerity/map.php
··· 382 382 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 383 383 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 384 384 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 385 - 'rsrc/js/application/herald/HeraldRuleEditor.js' => '52684226', 385 + 'rsrc/js/application/herald/HeraldRuleEditor.js' => '91a6031b', 386 386 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 387 387 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 388 388 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', ··· 538 538 'global-drag-and-drop-css' => '697324ad', 539 539 'harbormaster-css' => '49d64eb4', 540 540 'herald-css' => '826075fa', 541 - 'herald-rule-editor' => '52684226', 541 + 'herald-rule-editor' => '91a6031b', 542 542 'herald-test-css' => '778b008e', 543 543 'inline-comment-summary-css' => '51efda3a', 544 544 'javelin-aphlict' => '5359e785', ··· 1172 1172 'javelin-dom', 1173 1173 'javelin-reactor-dom', 1174 1174 ), 1175 - 52684226 => array( 1176 - 'multirow-row-manager', 1177 - 'javelin-install', 1178 - 'javelin-util', 1179 - 'javelin-dom', 1180 - 'javelin-stratcom', 1181 - 'javelin-json', 1182 - 'phabricator-prefab', 1183 - ), 1184 1175 '5359e785' => array( 1185 1176 'javelin-install', 1186 1177 'javelin-util', ··· 1538 1529 'javelin-behavior', 1539 1530 'javelin-dom', 1540 1531 'javelin-stratcom', 1532 + ), 1533 + '91a6031b' => array( 1534 + 'multirow-row-manager', 1535 + 'javelin-install', 1536 + 'javelin-util', 1537 + 'javelin-dom', 1538 + 'javelin-stratcom', 1539 + 'javelin-json', 1540 + 'phabricator-prefab', 1541 1541 ), 1542 1542 '93d0c9e3' => array( 1543 1543 'javelin-behavior',
+17 -1
src/__phutil_library_map__.php
··· 1007 1007 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 1008 1008 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 1009 1009 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', 1010 + 'HeraldAction' => 'applications/herald/action/HeraldAction.php', 1011 + 'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php', 1010 1012 'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php', 1011 1013 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', 1012 1014 'HeraldAlwaysField' => 'applications/herald/field/HeraldAlwaysField.php', ··· 1024 1026 'HeraldDifferentialDiffAdapter' => 'applications/differential/herald/HeraldDifferentialDiffAdapter.php', 1025 1027 'HeraldDifferentialRevisionAdapter' => 'applications/differential/herald/HeraldDifferentialRevisionAdapter.php', 1026 1028 'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php', 1029 + 'HeraldDoNothingAction' => 'applications/herald/action/HeraldDoNothingAction.php', 1027 1030 'HeraldEditFieldGroup' => 'applications/herald/field/HeraldEditFieldGroup.php', 1028 1031 'HeraldEffect' => 'applications/herald/engine/HeraldEffect.php', 1029 1032 'HeraldEmptyFieldValue' => 'applications/herald/value/HeraldEmptyFieldValue.php', ··· 1032 1035 'HeraldFieldGroup' => 'applications/herald/field/HeraldFieldGroup.php', 1033 1036 'HeraldFieldTestCase' => 'applications/herald/field/__tests__/HeraldFieldTestCase.php', 1034 1037 'HeraldFieldValue' => 'applications/herald/value/HeraldFieldValue.php', 1038 + 'HeraldGroup' => 'applications/herald/group/HeraldGroup.php', 1035 1039 'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php', 1036 1040 'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php', 1037 1041 'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php', 1038 1042 'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php', 1039 1043 'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php', 1040 1044 'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php', 1045 + 'HeraldNotifyActionGroup' => 'applications/herald/action/HeraldNotifyActionGroup.php', 1041 1046 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php', 1042 1047 'HeraldPholioMockAdapter' => 'applications/pholio/herald/HeraldPholioMockAdapter.php', 1043 1048 'HeraldPreCommitAdapter' => 'applications/diffusion/herald/HeraldPreCommitAdapter.php', 1044 1049 'HeraldPreCommitContentAdapter' => 'applications/diffusion/herald/HeraldPreCommitContentAdapter.php', 1045 1050 'HeraldPreCommitRefAdapter' => 'applications/diffusion/herald/HeraldPreCommitRefAdapter.php', 1051 + 'HeraldPreventActionGroup' => 'applications/herald/action/HeraldPreventActionGroup.php', 1046 1052 'HeraldProjectsField' => 'applications/project/herald/HeraldProjectsField.php', 1047 1053 'HeraldRecursiveConditionsException' => 'applications/herald/engine/exception/HeraldRecursiveConditionsException.php', 1048 1054 'HeraldRelatedFieldGroup' => 'applications/herald/field/HeraldRelatedFieldGroup.php', ··· 1065 1071 'HeraldSelectFieldValue' => 'applications/herald/value/HeraldSelectFieldValue.php', 1066 1072 'HeraldSpaceField' => 'applications/spaces/herald/HeraldSpaceField.php', 1067 1073 'HeraldSubscribersField' => 'applications/subscriptions/herald/HeraldSubscribersField.php', 1074 + 'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php', 1068 1075 'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php', 1069 1076 'HeraldTestConsoleController' => 'applications/herald/controller/HeraldTestConsoleController.php', 1070 1077 'HeraldTextFieldValue' => 'applications/herald/value/HeraldTextFieldValue.php', ··· 1077 1084 'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php', 1078 1085 'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php', 1079 1086 'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php', 1087 + 'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php', 1080 1088 'Javelin' => 'infrastructure/javelin/Javelin.php', 1081 1089 'JavelinReactorUIExample' => 'applications/uiexample/examples/JavelinReactorUIExample.php', 1082 1090 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', ··· 4684 4692 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4685 4693 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4686 4694 'HarbormasterWorker' => 'PhabricatorWorker', 4695 + 'HeraldAction' => 'Phobject', 4696 + 'HeraldActionGroup' => 'HeraldGroup', 4687 4697 'HeraldActionRecord' => 'HeraldDAO', 4688 4698 'HeraldAdapter' => 'Phobject', 4689 4699 'HeraldAlwaysField' => 'HeraldField', ··· 4701 4711 'HeraldDifferentialDiffAdapter' => 'HeraldDifferentialAdapter', 4702 4712 'HeraldDifferentialRevisionAdapter' => 'HeraldDifferentialAdapter', 4703 4713 'HeraldDisableController' => 'HeraldController', 4714 + 'HeraldDoNothingAction' => 'HeraldAction', 4704 4715 'HeraldEditFieldGroup' => 'HeraldFieldGroup', 4705 4716 'HeraldEffect' => 'Phobject', 4706 4717 'HeraldEmptyFieldValue' => 'HeraldFieldValue', 4707 4718 'HeraldEngine' => 'Phobject', 4708 4719 'HeraldField' => 'Phobject', 4709 - 'HeraldFieldGroup' => 'Phobject', 4720 + 'HeraldFieldGroup' => 'HeraldGroup', 4710 4721 'HeraldFieldTestCase' => 'PhutilTestCase', 4711 4722 'HeraldFieldValue' => 'Phobject', 4723 + 'HeraldGroup' => 'Phobject', 4712 4724 'HeraldInvalidActionException' => 'Exception', 4713 4725 'HeraldInvalidConditionException' => 'Exception', 4714 4726 'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability', 4715 4727 'HeraldManiphestTaskAdapter' => 'HeraldAdapter', 4716 4728 'HeraldNewController' => 'HeraldController', 4717 4729 'HeraldNewObjectField' => 'HeraldField', 4730 + 'HeraldNotifyActionGroup' => 'HeraldActionGroup', 4718 4731 'HeraldObjectTranscript' => 'Phobject', 4719 4732 'HeraldPholioMockAdapter' => 'HeraldAdapter', 4720 4733 'HeraldPreCommitAdapter' => 'HeraldAdapter', 4721 4734 'HeraldPreCommitContentAdapter' => 'HeraldPreCommitAdapter', 4722 4735 'HeraldPreCommitRefAdapter' => 'HeraldPreCommitAdapter', 4736 + 'HeraldPreventActionGroup' => 'HeraldActionGroup', 4723 4737 'HeraldProjectsField' => 'HeraldField', 4724 4738 'HeraldRecursiveConditionsException' => 'Exception', 4725 4739 'HeraldRelatedFieldGroup' => 'HeraldFieldGroup', ··· 4748 4762 'HeraldSelectFieldValue' => 'HeraldFieldValue', 4749 4763 'HeraldSpaceField' => 'HeraldField', 4750 4764 'HeraldSubscribersField' => 'HeraldField', 4765 + 'HeraldSupportActionGroup' => 'HeraldActionGroup', 4751 4766 'HeraldSupportFieldGroup' => 'HeraldFieldGroup', 4752 4767 'HeraldTestConsoleController' => 'HeraldController', 4753 4768 'HeraldTextFieldValue' => 'HeraldFieldValue', ··· 4764 4779 'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4765 4780 'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine', 4766 4781 'HeraldTranscriptTestCase' => 'PhabricatorTestCase', 4782 + 'HeraldUtilityActionGroup' => 'HeraldActionGroup', 4767 4783 'Javelin' => 'Phobject', 4768 4784 'JavelinReactorUIExample' => 'PhabricatorUIExample', 4769 4785 'JavelinUIExample' => 'PhabricatorUIExample',
-1
src/applications/differential/herald/HeraldDifferentialDiffAdapter.php
··· 75 75 return array_merge( 76 76 array( 77 77 self::ACTION_BLOCK, 78 - self::ACTION_NOTHING, 79 78 ), 80 79 parent::getActions($rule_type)); 81 80 }
-2
src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php
··· 169 169 self::ACTION_ADD_BLOCKING_REVIEWERS, 170 170 self::ACTION_APPLY_BUILD_PLANS, 171 171 self::ACTION_REQUIRE_SIGNATURE, 172 - self::ACTION_NOTHING, 173 172 ), 174 173 parent::getActions($rule_type)); 175 174 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: ··· 181 180 self::ACTION_FLAG, 182 181 self::ACTION_ADD_REVIEWERS, 183 182 self::ACTION_ADD_BLOCKING_REVIEWERS, 184 - self::ACTION_NOTHING, 185 183 ), 186 184 parent::getActions($rule_type)); 187 185 }
-2
src/applications/diffusion/herald/HeraldCommitAdapter.php
··· 94 94 self::ACTION_EMAIL, 95 95 self::ACTION_AUDIT, 96 96 self::ACTION_APPLY_BUILD_PLANS, 97 - self::ACTION_NOTHING, 98 97 ), 99 98 parent::getActions($rule_type)); 100 99 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: ··· 105 104 self::ACTION_EMAIL, 106 105 self::ACTION_FLAG, 107 106 self::ACTION_AUDIT, 108 - self::ACTION_NOTHING, 109 107 ), 110 108 parent::getActions($rule_type)); 111 109 }
-2
src/applications/diffusion/herald/HeraldPreCommitAdapter.php
··· 81 81 array( 82 82 self::ACTION_BLOCK, 83 83 self::ACTION_EMAIL, 84 - self::ACTION_NOTHING, 85 84 ), 86 85 parent::getActions($rule_type)); 87 86 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 88 87 return array_merge( 89 88 array( 90 89 self::ACTION_EMAIL, 91 - self::ACTION_NOTHING, 92 90 ), 93 91 parent::getActions($rule_type)); 94 92 }
+118
src/applications/herald/action/HeraldAction.php
··· 1 + <?php 2 + 3 + abstract class HeraldAction extends Phobject { 4 + 5 + private $adapter; 6 + private $applyLog = array(); 7 + 8 + const STANDARD_NONE = 'standard.none'; 9 + const STANDARD_PHID_LIST = 'standard.phid.list'; 10 + 11 + abstract public function getHeraldActionName(); 12 + abstract public function supportsObject($object); 13 + abstract public function supportsRuleType($rule_type); 14 + abstract public function applyEffect($object, HeraldEffect $effect); 15 + 16 + public function getActionGroupKey() { 17 + return null; 18 + } 19 + 20 + public function getActionsForObject($object) { 21 + return array($this->getActionConstant() => $this); 22 + } 23 + 24 + protected function getDatasource() { 25 + throw new PhutilMethodNotImplementedException(); 26 + } 27 + 28 + protected function getDatasourceValueMap() { 29 + return null; 30 + } 31 + 32 + public function getHeraldActionStandardType() { 33 + throw new PhutilMethodNotImplementedException(); 34 + } 35 + 36 + public function getHeraldActionValueType() { 37 + switch ($this->getHeraldActionStandardType()) { 38 + case self::STANDARD_NONE: 39 + return new HeraldEmptyFieldValue(); 40 + case self::STANDARD_PHID_LIST: 41 + $tokenizer = id(new HeraldTokenizerFieldValue()) 42 + ->setKey($this->getHeraldFieldName()) 43 + ->setDatasource($this->getDatasource()); 44 + 45 + $value_map = $this->getDatasourceValueMap(); 46 + if ($value_map !== null) { 47 + $tokenizer->setValueMap($value_map); 48 + } 49 + 50 + return $tokenizer; 51 + } 52 + 53 + throw new PhutilMethodNotImplementedException(); 54 + } 55 + 56 + public function willSaveActionValue($value) { 57 + return $value; 58 + } 59 + 60 + final public function setAdapter(HeraldAdapter $adapter) { 61 + $this->adapter = $adapter; 62 + return $this; 63 + } 64 + 65 + final public function getAdapter() { 66 + return $this->adapter; 67 + } 68 + 69 + final public function getActionConstant() { 70 + $class = new ReflectionClass($this); 71 + 72 + $const = $class->getConstant('ACTIONCONST'); 73 + if ($const === false) { 74 + throw new Exception( 75 + pht( 76 + '"%s" class "%s" must define a "%s" property.', 77 + __CLASS__, 78 + get_class($this), 79 + 'ACTIONCONST')); 80 + } 81 + 82 + $limit = self::getActionConstantByteLimit(); 83 + if (!is_string($const) || (strlen($const) > $limit)) { 84 + throw new Exception( 85 + pht( 86 + '"%s" class "%s" has an invalid "%s" property. Action constants '. 87 + 'must be strings and no more than %s bytes in length.', 88 + __CLASS__, 89 + get_class($this), 90 + 'ACTIONCONST', 91 + new PhutilNumber($limit))); 92 + } 93 + 94 + return $const; 95 + } 96 + 97 + final public static function getActionConstantByteLimit() { 98 + return 64; 99 + } 100 + 101 + final public static function getAllActions() { 102 + return id(new PhutilClassMapQuery()) 103 + ->setAncestorClass(__CLASS__) 104 + ->setUniqueMethod('getActionConstant') 105 + ->execute(); 106 + } 107 + 108 + protected function logEffect($type, $data = null) { 109 + return; 110 + } 111 + 112 + final public function getApplyTranscript(HeraldEffect $effect) { 113 + $context = 'v2/'.phutil_json_encode($this->applyLog); 114 + $this->applyLog = array(); 115 + return new HeraldApplyTranscript($effect, true, $context); 116 + } 117 + 118 + }
+28
src/applications/herald/action/HeraldActionGroup.php
··· 1 + <?php 2 + 3 + abstract class HeraldActionGroup extends HeraldGroup { 4 + 5 + final public function getGroupKey() { 6 + $class = new ReflectionClass($this); 7 + 8 + $const = $class->getConstant('ACTIONGROUPKEY'); 9 + if ($const === false) { 10 + throw new Exception( 11 + pht( 12 + '"%s" class "%s" must define a "%s" property.', 13 + __CLASS__, 14 + get_class($this), 15 + 'ACTIONGROUPKEY')); 16 + } 17 + 18 + return $const; 19 + } 20 + 21 + final public static function getAllActionGroups() { 22 + return id(new PhutilClassMapQuery()) 23 + ->setAncestorClass(__CLASS__) 24 + ->setUniqueMethod('getGroupKey') 25 + ->setSortMethod('getSortKey') 26 + ->execute(); 27 + } 28 + }
+32
src/applications/herald/action/HeraldDoNothingAction.php
··· 1 + <?php 2 + 3 + final class HeraldDoNothingAction extends HeraldAction { 4 + 5 + const ACTIONCONST = 'nothing'; 6 + const DO_NOTHING = 'do.nothing'; 7 + 8 + public function getHeraldActionName() { 9 + return pht('Do nothing'); 10 + } 11 + 12 + public function getActionGroupKey() { 13 + return HeraldUtilityActionGroup::ACTIONGROUPKEY; 14 + } 15 + 16 + public function supportsObject($object) { 17 + return true; 18 + } 19 + 20 + public function supportsRuleType($rule_type) { 21 + return true; 22 + } 23 + 24 + public function applyEffect($object, HeraldEffect $effect) { 25 + $this->logEffect($effect, self::DO_NOTHING); 26 + } 27 + 28 + public function getHeraldActionStandardType() { 29 + return self::STANDARD_NONE; 30 + } 31 + 32 + }
+15
src/applications/herald/action/HeraldNotifyActionGroup.php
··· 1 + <?php 2 + 3 + final class HeraldNotifyActionGroup extends HeraldActionGroup { 4 + 5 + const ACTIONGROUPKEY = 'notify'; 6 + 7 + public function getGroupLabel() { 8 + return pht('Notify'); 9 + } 10 + 11 + protected function getGroupOrder() { 12 + return 2000; 13 + } 14 + 15 + }
+15
src/applications/herald/action/HeraldPreventActionGroup.php
··· 1 + <?php 2 + 3 + final class HeraldPreventActionGroup extends HeraldActionGroup { 4 + 5 + const ACTIONGROUPKEY = 'prevent'; 6 + 7 + public function getGroupLabel() { 8 + return pht('Prevent'); 9 + } 10 + 11 + protected function getGroupOrder() { 12 + return 3000; 13 + } 14 + 15 + }
+15
src/applications/herald/action/HeraldSupportActionGroup.php
··· 1 + <?php 2 + 3 + final class HeraldSupportActionGroup extends HeraldActionGroup { 4 + 5 + const ACTIONGROUPKEY = 'support'; 6 + 7 + public function getGroupLabel() { 8 + return pht('Supporting Applications'); 9 + } 10 + 11 + protected function getGroupOrder() { 12 + return 4000; 13 + } 14 + 15 + }
+15
src/applications/herald/action/HeraldUtilityActionGroup.php
··· 1 + <?php 2 + 3 + final class HeraldUtilityActionGroup extends HeraldActionGroup { 4 + 5 + const ACTIONGROUPKEY = 'utility'; 6 + 7 + public function getGroupLabel() { 8 + return pht('Utility'); 9 + } 10 + 11 + protected function getGroupOrder() { 12 + return 10000; 13 + } 14 + 15 + }
+100 -16
src/applications/herald/adapter/HeraldAdapter.php
··· 29 29 const ACTION_ADD_CC = 'addcc'; 30 30 const ACTION_REMOVE_CC = 'remcc'; 31 31 const ACTION_EMAIL = 'email'; 32 - const ACTION_NOTHING = 'nothing'; 33 32 const ACTION_AUDIT = 'audit'; 34 33 const ACTION_FLAG = 'flag'; 35 34 const ACTION_ASSIGN_TASK = 'assigntask'; ··· 50 49 private $forcedEmailPHIDs = array(); 51 50 private $unsubscribedPHIDs; 52 51 private $fieldMap; 52 + private $actionMap; 53 53 54 54 public function getEmailPHIDs() { 55 55 return array_values($this->emailPHIDs); ··· 615 615 616 616 /* -( Actions )------------------------------------------------------------ */ 617 617 618 + private function getActionImplementationMap() { 619 + if ($this->actionMap === null) { 620 + // We can't use PhutilClassMapQuery here because action expansion 621 + // depends on the adapter and object. 622 + 623 + $object = $this->getObject(); 624 + 625 + $map = array(); 626 + $all = HeraldAction::getAllActions(); 627 + foreach ($all as $key => $action) { 628 + $action = id(clone $action)->setAdapter($this); 629 + 630 + if (!$action->supportsObject($object)) { 631 + continue; 632 + } 633 + 634 + $subactions = $action->getActionsForObject($object); 635 + foreach ($subactions as $subkey => $subaction) { 636 + if (isset($map[$subkey])) { 637 + throw new Exception( 638 + pht( 639 + 'Two HeraldActions (of classes "%s" and "%s") have the same '. 640 + 'action key ("%s") after expansion for an object of class '. 641 + '"%s" inside adapter "%s". Each action must have a unique '. 642 + 'action key.', 643 + get_class($subaction), 644 + get_class($map[$subkey]), 645 + $subkey, 646 + get_class($object), 647 + get_class($this))); 648 + } 649 + 650 + $subaction = id(clone $subaction)->setAdapter($this); 651 + 652 + $map[$subkey] = $subaction; 653 + } 654 + } 655 + $this->actionMap = $map; 656 + } 657 + 658 + return $this->actionMap; 659 + } 660 + 661 + private function getActionsForRuleType($rule_type) { 662 + $actions = $this->getActionImplementationMap(); 663 + 664 + foreach ($actions as $key => $action) { 665 + if (!$action->supportsRuleType($rule_type)) { 666 + unset($actions[$key]); 667 + } 668 + } 669 + 670 + return $actions; 671 + } 672 + 673 + private function getActionImplementation($key) { 674 + return idx($this->getActionImplementationMap(), $key); 675 + } 676 + 677 + public function getActionKeys() { 678 + return array_keys($this->getActionImplementationMap()); 679 + } 680 + 681 + public function getActionGroupKey($action_key) { 682 + $action = $this->getActionImplementation($action_key); 683 + if (!$action) { 684 + return null; 685 + } 686 + 687 + return $action->getActionGroupKey(); 688 + } 689 + 618 690 public function getCustomActionsForRuleType($rule_type) { 619 691 $results = array(); 620 692 foreach ($this->getCustomActions() as $custom_action) { ··· 640 712 } 641 713 } 642 714 715 + foreach ($this->getActionsForRuleType($rule_type) as $key => $action) { 716 + $actions[] = $key; 717 + } 718 + 643 719 return $actions; 644 720 } 645 721 ··· 648 724 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 649 725 case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: 650 726 $standard = array( 651 - self::ACTION_NOTHING => pht('Do nothing'), 652 727 self::ACTION_ADD_CC => pht('Add Subscribers'), 653 728 self::ACTION_REMOVE_CC => pht('Remove Subscribers'), 654 729 self::ACTION_EMAIL => pht('Send an email to'), ··· 666 741 break; 667 742 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 668 743 $standard = array( 669 - self::ACTION_NOTHING => pht('Do nothing'), 670 744 self::ACTION_ADD_CC => pht('Add me as a subscriber'), 671 745 self::ACTION_REMOVE_CC => pht('Remove me as a subscriber'), 672 746 self::ACTION_EMAIL => pht('Send me an email'), ··· 685 759 $custom_actions = $this->getCustomActionsForRuleType($rule_type); 686 760 $standard += mpull($custom_actions, 'getActionName', 'getActionKey'); 687 761 762 + foreach ($this->getActionsForRuleType($rule_type) as $key => $action) { 763 + $standard[$key] = $action->getHeraldActionName(); 764 + } 765 + 688 766 return $standard; 689 767 } 690 768 ··· 692 770 HeraldRule $rule, 693 771 HeraldActionRecord $action) { 694 772 773 + $impl = $this->getActionImplementation($action->getAction()); 774 + if ($impl) { 775 + $target = $action->getTarget(); 776 + $target = $impl->willSaveActionValue($target); 777 + $action->setTarget($target); 778 + return; 779 + } 780 + 695 781 $target = $action->getTarget(); 696 782 if (is_array($target)) { 697 783 $target = array_keys($target); ··· 720 806 } 721 807 break; 722 808 case self::ACTION_BLOCK: 723 - case self::ACTION_NOTHING: 724 809 break; 725 810 default: 726 811 throw new HeraldInvalidActionException( ··· 744 829 } 745 830 746 831 public function getValueTypeForAction($action, $rule_type) { 832 + $impl = $this->getActionImplementation($action); 833 + if ($impl) { 834 + return $impl->getHeraldActionValueType(); 835 + } 836 + 747 837 $is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); 748 838 749 839 if ($is_personal) { ··· 751 841 case self::ACTION_ADD_CC: 752 842 case self::ACTION_REMOVE_CC: 753 843 case self::ACTION_EMAIL: 754 - case self::ACTION_NOTHING: 755 844 case self::ACTION_AUDIT: 756 845 case self::ACTION_ASSIGN_TASK: 757 846 case self::ACTION_ADD_REVIEWERS: ··· 771 860 case self::ACTION_EMAIL: 772 861 return $this->buildTokenizerFieldValue( 773 862 new PhabricatorMetaMTAMailableDatasource()); 774 - case self::ACTION_NOTHING: 775 - return new HeraldEmptyFieldValue(); 776 863 case self::ACTION_ADD_PROJECTS: 777 864 case self::ACTION_REMOVE_PROJECTS: 778 865 return $this->buildTokenizerFieldValue( ··· 1108 1195 protected function applyStandardEffect(HeraldEffect $effect) { 1109 1196 $action = $effect->getAction(); 1110 1197 1198 + $impl = $this->getActionImplementation($action); 1199 + if ($impl) { 1200 + $impl->applyEffect($this->getObject(), $effect); 1201 + return $impl->getApplyTranscript($effect); 1202 + } 1203 + 1111 1204 $rule_type = $effect->getRule()->getRuleType(); 1112 1205 $supported = $this->getActions($rule_type); 1113 1206 $supported = array_fuse($supported); ··· 1133 1226 return $this->applyFlagEffect($effect); 1134 1227 case self::ACTION_EMAIL: 1135 1228 return $this->applyEmailEffect($effect); 1136 - case self::ACTION_NOTHING: 1137 - return $this->applyNothingEffect($effect); 1138 1229 default: 1139 1230 break; 1140 1231 } ··· 1151 1242 } 1152 1243 1153 1244 return $result; 1154 - } 1155 - 1156 - private function applyNothingEffect(HeraldEffect $effect) { 1157 - return new HeraldApplyTranscript( 1158 - $effect, 1159 - true, 1160 - pht('Did nothing.')); 1161 1245 } 1162 1246 1163 1247 /**
+54 -33
src/applications/herald/controller/HeraldRuleController.php
··· 427 427 } 428 428 } 429 429 430 - $group_map = array(); 431 - foreach ($field_map as $field_key => $field_name) { 432 - $group_key = $adapter->getFieldGroupKey($field_key); 433 - $group_map[$group_key][$field_key] = $field_name; 434 - } 435 - 436 - $field_groups = HeraldFieldGroup::getAllFieldGroups(); 437 - 438 - $groups = array(); 439 - foreach ($group_map as $group_key => $options) { 440 - asort($options); 441 - 442 - $field_group = idx($field_groups, $group_key); 443 - if ($field_group) { 444 - $group_label = $field_group->getGroupLabel(); 445 - $group_order = $field_group->getSortKey(); 446 - } else { 447 - $group_label = nonempty($group_key, pht('Other')); 448 - $group_order = 'Z'; 449 - } 450 - 451 - $groups[] = array( 452 - 'label' => $group_label, 453 - 'options' => $options, 454 - 'order' => $group_order, 455 - ); 456 - } 457 - 458 - $groups = array_values(isort($groups, 'order')); 459 - 460 430 $config_info = array(); 461 - $config_info['fields'] = $groups; 431 + $config_info['fields'] = $this->getFieldGroups($adapter, $field_map); 462 432 $config_info['conditions'] = $all_conditions; 463 - $config_info['actions'] = $action_map; 433 + $config_info['actions'] = $this->getActionGroups($adapter, $action_map); 464 434 $config_info['valueMap'] = array(); 465 435 466 436 foreach ($field_map as $field => $name) { ··· 492 462 493 463 $config_info['rule_type'] = $rule->getRuleType(); 494 464 495 - foreach ($config_info['actions'] as $action => $name) { 465 + foreach ($action_map as $action => $name) { 496 466 try { 497 467 $value_key = $adapter->getValueTypeForAction( 498 468 $action, ··· 658 628 659 629 return $all_rules; 660 630 } 631 + 632 + private function getFieldGroups(HeraldAdapter $adapter, array $field_map) { 633 + $group_map = array(); 634 + foreach ($field_map as $field_key => $field_name) { 635 + $group_key = $adapter->getFieldGroupKey($field_key); 636 + $group_map[$group_key][$field_key] = $field_name; 637 + } 638 + 639 + return $this->getGroups( 640 + $group_map, 641 + HeraldFieldGroup::getAllFieldGroups()); 642 + } 643 + 644 + private function getActionGroups(HeraldAdapter $adapter, array $action_map) { 645 + $group_map = array(); 646 + foreach ($action_map as $action_key => $action_name) { 647 + $group_key = $adapter->getActionGroupKey($action_key); 648 + $group_map[$group_key][$action_key] = $action_name; 649 + } 650 + 651 + return $this->getGroups( 652 + $group_map, 653 + HeraldActionGroup::getAllActionGroups()); 654 + } 655 + 656 + private function getGroups(array $item_map, array $group_list) { 657 + assert_instances_of($group_list, 'HeraldGroup'); 658 + 659 + $groups = array(); 660 + foreach ($item_map as $group_key => $options) { 661 + asort($options); 662 + 663 + $group_object = idx($group_list, $group_key); 664 + if ($group_object) { 665 + $group_label = $group_object->getGroupLabel(); 666 + $group_order = $group_object->getSortKey(); 667 + } else { 668 + $group_label = nonempty($group_key, pht('Other')); 669 + $group_order = 'Z'; 670 + } 671 + 672 + $groups[] = array( 673 + 'label' => $group_label, 674 + 'options' => $options, 675 + 'order' => $group_order, 676 + ); 677 + } 678 + 679 + return array_values(isort($groups, 'order')); 680 + } 681 + 661 682 662 683 }
+5 -3
src/applications/herald/controller/HeraldTranscriptController.php
··· 347 347 348 348 $target = $apply_xscript->getTarget(); 349 349 switch ($apply_xscript->getAction()) { 350 - case HeraldAdapter::ACTION_NOTHING: 351 - $target = null; 352 - break; 353 350 case HeraldAdapter::ACTION_FLAG: 354 351 $target = PhabricatorFlagColor::getColorName($target); 355 352 break; ··· 358 355 $target = $target; 359 356 break; 360 357 default: 358 + // TODO: This should be driven by HeraldActions. 359 + 361 360 if (is_array($target) && $target) { 362 361 foreach ($target as $k => $phid) { 363 362 if (isset($handles[$phid])) { ··· 388 387 389 388 $item->setHeader(pht('%s: %s', $rule, $target)); 390 389 $item->addAttribute($apply_xscript->getReason()); 390 + 391 + // TODO: This is a bit of a mess while actions convert. 392 + 391 393 $item->addAttribute( 392 394 pht('Outcome: %s', $apply_xscript->getAppliedReason())); 393 395
+2 -12
src/applications/herald/field/HeraldFieldGroup.php
··· 1 1 <?php 2 2 3 - abstract class HeraldFieldGroup extends Phobject { 4 - 5 - abstract public function getGroupLabel(); 6 - 7 - protected function getGroupOrder() { 8 - return 1000; 9 - } 3 + abstract class HeraldFieldGroup extends HeraldGroup { 10 4 11 5 final public function getGroupKey() { 12 6 $class = new ReflectionClass($this); ··· 18 12 '"%s" class "%s" must define a "%s" property.', 19 13 __CLASS__, 20 14 get_class($this), 21 - 'FIELDCONST')); 15 + 'FIELDGROUPKEY')); 22 16 } 23 17 24 18 return $const; 25 - } 26 - 27 - public function getSortKey() { 28 - return sprintf('A%08d%s', $this->getGroupOrder(), $this->getGroupLabel()); 29 19 } 30 20 31 21 final public static function getAllFieldGroups() {
+15
src/applications/herald/group/HeraldGroup.php
··· 1 + <?php 2 + 3 + abstract class HeraldGroup extends Phobject { 4 + 5 + abstract public function getGroupLabel(); 6 + 7 + protected function getGroupOrder() { 8 + return 1000; 9 + } 10 + 11 + public function getSortKey() { 12 + return sprintf('A%08d%s', $this->getGroupOrder(), $this->getGroupLabel()); 13 + } 14 + 15 + }
-2
src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php
··· 76 76 self::ACTION_REMOVE_CC, 77 77 self::ACTION_EMAIL, 78 78 self::ACTION_ASSIGN_TASK, 79 - self::ACTION_NOTHING, 80 79 ), 81 80 parent::getActions($rule_type)); 82 81 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: ··· 87 86 self::ACTION_EMAIL, 88 87 self::ACTION_FLAG, 89 88 self::ACTION_ASSIGN_TASK, 90 - self::ACTION_NOTHING, 91 89 ), 92 90 parent::getActions($rule_type)); 93 91 }
-2
src/applications/pholio/herald/HeraldPholioMockAdapter.php
··· 54 54 array( 55 55 self::ACTION_ADD_CC, 56 56 self::ACTION_REMOVE_CC, 57 - self::ACTION_NOTHING, 58 57 ), 59 58 parent::getActions($rule_type)); 60 59 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: ··· 63 62 self::ACTION_ADD_CC, 64 63 self::ACTION_REMOVE_CC, 65 64 self::ACTION_FLAG, 66 - self::ACTION_NOTHING, 67 65 ), 68 66 parent::getActions($rule_type)); 69 67 }
-2
src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php
··· 56 56 self::ACTION_ADD_CC, 57 57 self::ACTION_REMOVE_CC, 58 58 self::ACTION_EMAIL, 59 - self::ACTION_NOTHING, 60 59 ), 61 60 parent::getActions($rule_type)); 62 61 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: ··· 66 65 self::ACTION_REMOVE_CC, 67 66 self::ACTION_EMAIL, 68 67 self::ACTION_FLAG, 69 - self::ACTION_NOTHING, 70 68 ), 71 69 parent::getActions($rule_type)); 72 70 }
+25 -15
webroot/rsrc/js/application/herald/HeraldRuleEditor.js
··· 322 322 _renderCondition : function(row_id) { 323 323 var groups = this._config.info.fields; 324 324 325 - var optgroups = []; 326 - for (var ii = 0; ii < groups.length; ii++) { 327 - var group = groups[ii]; 328 - var options = []; 329 - for (var k in group.options) { 330 - options.push(JX.$N('option', {value: k}, group.options[k])); 331 - } 332 - optgroups.push(JX.$N('optgroup', {label: group.label}, options)); 333 - } 334 - 335 325 var attrs = { 336 326 sigil: 'field-select' 337 327 }; 338 328 339 - var field_select = JX.$N('select', attrs, optgroups); 329 + var field_select = this._renderGroupSelect(groups, attrs); 340 330 field_select.value = this._config.conditions[row_id][0]; 341 331 342 332 var field_cell = JX.$N('td', {sigil: 'field-cell'}, field_select); ··· 352 342 delete actions[k]; 353 343 } 354 344 }, 345 + 346 + _renderGroupSelect: function(groups, attrs) { 347 + var optgroups = []; 348 + for (var ii = 0; ii < groups.length; ii++) { 349 + var group = groups[ii]; 350 + var options = []; 351 + for (var k in group.options) { 352 + options.push(JX.$N('option', {value: k}, group.options[k])); 353 + } 354 + optgroups.push(JX.$N('optgroup', {label: group.label}, options)); 355 + } 356 + 357 + return JX.$N('select', attrs, optgroups); 358 + }, 359 + 355 360 _newAction : function(data) { 356 361 data = data || []; 357 362 var temprow = this._actionsRowManager.addRow([]); ··· 361 366 this._renderAction(data)); 362 367 this._onactionchange(r); 363 368 }, 369 + 364 370 _renderAction : function(action) { 365 - var action_select = this._renderSelect( 366 - this._config.info.actions, 367 - action[0], 368 - 'action-select'); 371 + var groups = this._config.info.actions; 372 + var attrs = { 373 + sigil: 'action-select' 374 + }; 375 + 376 + var action_select = this._renderGroupSelect(groups, attrs); 377 + action_select.value = action[0]; 378 + 369 379 var action_cell = JX.$N('td', {sigil: 'action-cell'}, action_select); 370 380 371 381 var target_cell = JX.$N(