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

Modularize Herald "flag" action, plus update transcripts

Summary:
Ref T8726. This modularizes "Mark with flag", plus rebuilds transcripts in a more modern/flexible way. The big transcript stuff is:

- Transcripts are now translatable.
- Transcripts can now show multiple outputs from a single action. For example, an action like "add A, B, C to subscribers" can now say "added A; B is invalid; C was already subscribed".

Test Plan: {F637784}

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: joshuaspence, eadler, epriestley

Maniphest Tasks: T8726

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

+338 -414
+2 -2
resources/celerity/map.php
··· 73 73 'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad', 74 74 'rsrc/css/application/flag/flag.css' => '5337623f', 75 75 'rsrc/css/application/harbormaster/harbormaster.css' => '49d64eb4', 76 - 'rsrc/css/application/herald/herald-test.css' => '778b008e', 76 + 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 77 77 'rsrc/css/application/herald/herald.css' => '826075fa', 78 78 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 79 79 'rsrc/css/application/maniphest/report.css' => 'f6931fdf', ··· 539 539 'harbormaster-css' => '49d64eb4', 540 540 'herald-css' => '826075fa', 541 541 'herald-rule-editor' => '91a6031b', 542 - 'herald-test-css' => '778b008e', 542 + 'herald-test-css' => 'a52e323e', 543 543 'inline-comment-summary-css' => '51efda3a', 544 544 'javelin-aphlict' => '5359e785', 545 545 'javelin-behavior' => '61cbc29a',
+2
src/__phutil_library_map__.php
··· 2110 2110 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', 2111 2111 'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php', 2112 2112 'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php', 2113 + 'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php', 2113 2114 'PhabricatorFlagColor' => 'applications/flag/constants/PhabricatorFlagColor.php', 2114 2115 'PhabricatorFlagConstants' => 'applications/flag/constants/PhabricatorFlagConstants.php', 2115 2116 'PhabricatorFlagController' => 'applications/flag/controller/PhabricatorFlagController.php', ··· 5988 5989 'PhabricatorFlagDAO', 5989 5990 'PhabricatorPolicyInterface', 5990 5991 ), 5992 + 'PhabricatorFlagAddFlagHeraldAction' => 'HeraldAction', 5991 5993 'PhabricatorFlagColor' => 'PhabricatorFlagConstants', 5992 5994 'PhabricatorFlagConstants' => 'Phobject', 5993 5995 'PhabricatorFlagController' => 'PhabricatorController',
-1
src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php
··· 177 177 self::ACTION_ADD_CC, 178 178 self::ACTION_REMOVE_CC, 179 179 self::ACTION_EMAIL, 180 - self::ACTION_FLAG, 181 180 self::ACTION_ADD_REVIEWERS, 182 181 self::ACTION_ADD_BLOCKING_REVIEWERS, 183 182 ),
-1
src/applications/diffusion/herald/HeraldCommitAdapter.php
··· 102 102 self::ACTION_ADD_CC, 103 103 self::ACTION_REMOVE_CC, 104 104 self::ACTION_EMAIL, 105 - self::ACTION_FLAG, 106 105 self::ACTION_AUDIT, 107 106 ), 108 107 parent::getActions($rule_type));
+88
src/applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php
··· 1 + <?php 2 + 3 + final class PhabricatorFlagAddFlagHeraldAction extends HeraldAction { 4 + 5 + const ACTIONCONST = 'flag'; 6 + 7 + const DO_FLAG = 'do.flag'; 8 + const DO_IGNORE = 'do.flagged'; 9 + 10 + public function getHeraldActionName() { 11 + return pht('Mark with flag'); 12 + } 13 + 14 + public function getActionGroupKey() { 15 + return HeraldSupportActionGroup::ACTIONGROUPKEY; 16 + } 17 + 18 + public function supportsObject($object) { 19 + return ($object instanceof PhabricatorFlaggableInterface); 20 + } 21 + 22 + public function supportsRuleType($rule_type) { 23 + return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); 24 + } 25 + 26 + public function applyEffect($object, HeraldEffect $effect) { 27 + $phid = $this->getAdapter()->getPHID(); 28 + $rule = $effect->getRule(); 29 + $author = $rule->getAuthor(); 30 + 31 + $flag = PhabricatorFlagQuery::loadUserFlag($author, $phid); 32 + if ($flag) { 33 + $this->logEffect(self::DO_IGNORE, $flag->getColor()); 34 + return; 35 + } 36 + 37 + $flag = id(new PhabricatorFlag()) 38 + ->setOwnerPHID($author->getPHID()) 39 + ->setType(phid_get_type($phid)) 40 + ->setObjectPHID($phid) 41 + ->setReasonPHID($rule->getPHID()) 42 + ->setColor($effect->getTarget()) 43 + ->setNote('') 44 + ->save(); 45 + 46 + $this->logEffect(self::DO_FLAG, $flag->getColor()); 47 + } 48 + 49 + public function getHeraldActionValueType() { 50 + return id(new HeraldSelectFieldValue()) 51 + ->setKey('flag.color') 52 + ->setOptions(PhabricatorFlagColor::getColorNameMap()) 53 + ->setDefault(PhabricatorFlagColor::COLOR_BLUE); 54 + } 55 + 56 + protected function getActionEffectMap() { 57 + return array( 58 + self::DO_IGNORE => array( 59 + 'icon' => 'fa-times', 60 + 'color' => 'grey', 61 + 'name' => pht('Already Marked'), 62 + ), 63 + self::DO_FLAG => array( 64 + 'icon' => 'fa-flag', 65 + 'name' => pht('Flagged'), 66 + ), 67 + ); 68 + } 69 + 70 + public function renderActionDescription($value) { 71 + $color = PhabricatorFlagColor::getColorName($value); 72 + return pht('Mark with %s flag.', $color); 73 + } 74 + 75 + public function renderActionEffectDescription($type, $data) { 76 + switch ($type) { 77 + case self::DO_IGNORE: 78 + return pht( 79 + 'Already marked with %s flag.', 80 + PhabricatorFlagColor::getColorName($data)); 81 + case self::DO_FLAG: 82 + return pht( 83 + 'Marked with "%s" flag.', 84 + PhabricatorFlagColor::getColorName($data)); 85 + } 86 + } 87 + 88 + }
+49 -2
src/applications/herald/action/HeraldAction.php
··· 3 3 abstract class HeraldAction extends Phobject { 4 4 5 5 private $adapter; 6 + private $viewer; 6 7 private $applyLog = array(); 7 8 8 9 const STANDARD_NONE = 'standard.none'; ··· 12 13 abstract public function supportsObject($object); 13 14 abstract public function supportsRuleType($rule_type); 14 15 abstract public function applyEffect($object, HeraldEffect $effect); 16 + abstract public function renderActionEffectDescription($type, $data); 15 17 16 18 public function getActionGroupKey() { 17 19 return null; ··· 66 68 return $this->adapter; 67 69 } 68 70 71 + final public function setViewer(PhabricatorUser $viewer) { 72 + $this->viewer = $viewer; 73 + return $this; 74 + } 75 + 76 + final public function getViewer() { 77 + return $this->viewer; 78 + } 79 + 69 80 final public function getActionConstant() { 70 81 $class = new ReflectionClass($this); 71 82 ··· 106 117 } 107 118 108 119 protected function logEffect($type, $data = null) { 109 - return; 120 + if (!is_string($type)) { 121 + throw new Exception( 122 + pht( 123 + 'Effect type passed to "%s" must be a scalar string.', 124 + 'logEffect()')); 125 + } 126 + 127 + $this->applyLog[] = array( 128 + 'type' => $type, 129 + 'data' => $data, 130 + ); 131 + 132 + return $this; 110 133 } 111 134 112 135 final public function getApplyTranscript(HeraldEffect $effect) { 113 - $context = 'v2/'.phutil_json_encode($this->applyLog); 136 + $context = $this->applyLog; 114 137 $this->applyLog = array(); 115 138 return new HeraldApplyTranscript($effect, true, $context); 139 + } 140 + 141 + protected function getActionEffectMap() { 142 + throw new PhutilMethodNotImplementedException(); 143 + } 144 + 145 + private function getActionEffectSpec($type) { 146 + $map = $this->getActionEffectMap(); 147 + return idx($map, $type, array()); 148 + } 149 + 150 + public function renderActionEffectIcon($type, $data) { 151 + $map = $this->getActionEffectSpec($type); 152 + return idx($map, 'icon'); 153 + } 154 + 155 + public function renderActionEffectColor($type, $data) { 156 + $map = $this->getActionEffectSpec($type); 157 + return idx($map, 'color'); 158 + } 159 + 160 + public function renderActionEffectName($type, $data) { 161 + $map = $this->getActionEffectSpec($type); 162 + return idx($map, 'name'); 116 163 } 117 164 118 165 }
+19 -1
src/applications/herald/action/HeraldDoNothingAction.php
··· 22 22 } 23 23 24 24 public function applyEffect($object, HeraldEffect $effect) { 25 - $this->logEffect($effect, self::DO_NOTHING); 25 + $this->logEffect(self::DO_NOTHING); 26 26 } 27 27 28 28 public function getHeraldActionStandardType() { 29 29 return self::STANDARD_NONE; 30 + } 31 + 32 + protected function getActionEffectMap() { 33 + return array( 34 + self::DO_NOTHING => array( 35 + 'icon' => 'fa-check', 36 + 'color' => 'grey', 37 + 'name' => pht('Did Nothing'), 38 + ), 39 + ); 40 + } 41 + 42 + public function renderActionDescription($value) { 43 + return pht('Do nothing.'); 44 + } 45 + 46 + public function renderActionEffectDescription($type, $data) { 47 + return pht('Did nothing.'); 30 48 } 31 49 32 50 }
+12 -72
src/applications/herald/adapter/HeraldAdapter.php
··· 30 30 const ACTION_REMOVE_CC = 'remcc'; 31 31 const ACTION_EMAIL = 'email'; 32 32 const ACTION_AUDIT = 'audit'; 33 - const ACTION_FLAG = 'flag'; 34 33 const ACTION_ASSIGN_TASK = 'assigntask'; 35 34 const ACTION_ADD_PROJECTS = 'addprojects'; 36 35 const ACTION_REMOVE_PROJECTS = 'removeprojects'; ··· 670 669 return $actions; 671 670 } 672 671 673 - private function getActionImplementation($key) { 672 + public function getActionImplementation($key) { 674 673 return idx($this->getActionImplementationMap(), $key); 675 674 } 676 675 ··· 728 727 self::ACTION_REMOVE_CC => pht('Remove Subscribers'), 729 728 self::ACTION_EMAIL => pht('Send an email to'), 730 729 self::ACTION_AUDIT => pht('Trigger an Audit by'), 731 - self::ACTION_FLAG => pht('Mark with flag'), 732 730 self::ACTION_ASSIGN_TASK => pht('Assign task to'), 733 731 self::ACTION_ADD_PROJECTS => pht('Add projects'), 734 732 self::ACTION_REMOVE_PROJECTS => pht('Remove projects'), ··· 745 743 self::ACTION_REMOVE_CC => pht('Remove me as a subscriber'), 746 744 self::ACTION_EMAIL => pht('Send me an email'), 747 745 self::ACTION_AUDIT => pht('Trigger an Audit by me'), 748 - self::ACTION_FLAG => pht('Mark with flag'), 749 746 self::ACTION_ASSIGN_TASK => pht('Assign task to me'), 750 747 self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'), 751 748 self::ACTION_ADD_BLOCKING_REVIEWERS => ··· 798 795 // For personal rules, force these actions to target the rule owner. 799 796 $target = array($author_phid); 800 797 break; 801 - case self::ACTION_FLAG: 802 - // Make sure flag color is valid; set to blue if not. 803 - $color_map = PhabricatorFlagColor::getColorNameMap(); 804 - if (empty($color_map[$target])) { 805 - $target = PhabricatorFlagColor::COLOR_BLUE; 806 - } 807 - break; 808 798 case self::ACTION_BLOCK: 809 799 break; 810 800 default: ··· 846 836 case self::ACTION_ADD_REVIEWERS: 847 837 case self::ACTION_ADD_BLOCKING_REVIEWERS: 848 838 return new HeraldEmptyFieldValue(); 849 - case self::ACTION_FLAG: 850 - return $this->buildFlagColorFieldValue(); 851 839 case self::ACTION_ADD_PROJECTS: 852 840 case self::ACTION_REMOVE_PROJECTS: 853 841 return $this->buildTokenizerFieldValue( ··· 864 852 case self::ACTION_REMOVE_PROJECTS: 865 853 return $this->buildTokenizerFieldValue( 866 854 new PhabricatorProjectDatasource()); 867 - case self::ACTION_FLAG: 868 - return $this->buildFlagColorFieldValue(); 869 855 case self::ACTION_ASSIGN_TASK: 870 856 return $this->buildTokenizerFieldValue( 871 857 new PhabricatorPeopleDatasource()); ··· 893 879 throw new Exception(pht("Unknown or invalid action '%s'.", $action)); 894 880 } 895 881 896 - private function buildFlagColorFieldValue() { 897 - return id(new HeraldSelectFieldValue()) 898 - ->setKey('flag.color') 899 - ->setOptions(PhabricatorFlagColor::getColorNameMap()) 900 - ->setDefault(PhabricatorFlagColor::COLOR_BLUE); 901 - } 902 - 903 882 private function buildTokenizerFieldValue( 904 883 PhabricatorTypeaheadDatasource $datasource) { 905 884 ··· 1051 1030 ), 1052 1031 array( 1053 1032 $icon, 1054 - $this->renderActionAsText($action, $handles), 1033 + $this->renderActionAsText($viewer, $action, $handles), 1055 1034 )); 1056 1035 } 1057 1036 ··· 1083 1062 } 1084 1063 1085 1064 private function renderActionAsText( 1065 + PhabricatorUser $viewer, 1086 1066 HeraldActionRecord $action, 1087 1067 PhabricatorHandleList $handles) { 1068 + 1069 + $impl = $this->getActionImplementation($action->getAction()); 1070 + if ($impl) { 1071 + $value = $action->getTarget(); 1072 + return $impl->renderActionDescription($viewer, $value); 1073 + } 1074 + 1088 1075 $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; 1089 1076 1090 1077 $action_type = $action->getAction(); ··· 1118 1105 HeraldActionRecord $action, 1119 1106 PhabricatorHandleList $handles) { 1120 1107 1108 + // TODO: This should be driven through HeraldAction. 1109 + 1121 1110 $target = $action->getTarget(); 1122 1111 if (!is_array($target)) { 1123 1112 $target = array($target); 1124 1113 } 1125 1114 foreach ($target as $index => $val) { 1126 1115 switch ($action->getAction()) { 1127 - case self::ACTION_FLAG: 1128 - $target[$index] = PhabricatorFlagColor::getColorName($val); 1129 - break; 1130 1116 default: 1131 1117 $handle = $handles->getHandleIfExists($val); 1132 1118 if ($handle) { ··· 1222 1208 case self::ACTION_ADD_CC: 1223 1209 case self::ACTION_REMOVE_CC: 1224 1210 return $this->applySubscribersEffect($effect); 1225 - case self::ACTION_FLAG: 1226 - return $this->applyFlagEffect($effect); 1227 1211 case self::ACTION_EMAIL: 1228 1212 return $this->applyEmailEffect($effect); 1229 1213 default: ··· 1363 1347 1364 1348 return new HeraldApplyTranscript($effect, true, $message); 1365 1349 } 1366 - 1367 - 1368 - /** 1369 - * @task apply 1370 - */ 1371 - private function applyFlagEffect(HeraldEffect $effect) { 1372 - $phid = $this->getPHID(); 1373 - $color = $effect->getTarget(); 1374 - 1375 - $rule = $effect->getRule(); 1376 - $user = $rule->getAuthor(); 1377 - 1378 - $flag = PhabricatorFlagQuery::loadUserFlag($user, $phid); 1379 - if ($flag) { 1380 - return new HeraldApplyTranscript( 1381 - $effect, 1382 - false, 1383 - pht('Object already flagged.')); 1384 - } 1385 - 1386 - $handle = id(new PhabricatorHandleQuery()) 1387 - ->setViewer($user) 1388 - ->withPHIDs(array($phid)) 1389 - ->executeOne(); 1390 - 1391 - $flag = new PhabricatorFlag(); 1392 - $flag->setOwnerPHID($user->getPHID()); 1393 - $flag->setType($handle->getType()); 1394 - $flag->setObjectPHID($handle->getPHID()); 1395 - 1396 - // TOOD: Should really be transcript PHID, but it doesn't exist yet. 1397 - $flag->setReasonPHID($user->getPHID()); 1398 - 1399 - $flag->setColor($color); 1400 - $flag->setNote( 1401 - pht('Flagged by Herald Rule "%s".', $rule->getName())); 1402 - $flag->save(); 1403 - 1404 - return new HeraldApplyTranscript( 1405 - $effect, 1406 - true, 1407 - pht('Added flag.')); 1408 - } 1409 - 1410 1350 1411 1351 /** 1412 1352 * @task apply
+1 -1
src/applications/herald/application/PhabricatorHeraldApplication.php
··· 58 58 'transcript/' => array( 59 59 '' => 'HeraldTranscriptListController', 60 60 '(?:query/(?P<queryKey>[^/]+)/)?' => 'HeraldTranscriptListController', 61 - '(?P<id>[1-9]\d*)/(?:(?P<filter>\w+)/)?' 61 + '(?P<id>[1-9]\d*)/' 62 62 => 'HeraldTranscriptController', 63 63 ), 64 64 ),
-1
src/applications/herald/controller/HeraldRuleController.php
··· 367 367 $serial_actions = array(); 368 368 foreach ($rule->getActions() as $action) { 369 369 switch ($action->getAction()) { 370 - case HeraldAdapter::ACTION_FLAG: 371 370 case HeraldAdapter::ACTION_BLOCK: 372 371 $current_value = $action->getTarget(); 373 372 break;
+164 -260
src/applications/herald/controller/HeraldTranscriptController.php
··· 2 2 3 3 final class HeraldTranscriptController extends HeraldController { 4 4 5 - const FILTER_AFFECTED = 'affected'; 6 - const FILTER_OWNED = 'owned'; 7 - const FILTER_ALL = 'all'; 8 - 9 - private $id; 10 - private $filter; 11 5 private $handles; 12 6 private $adapter; 13 7 14 - public function willProcessRequest(array $data) { 15 - $this->id = $data['id']; 16 - $map = $this->getFilterMap(); 17 - $this->filter = idx($data, 'filter'); 18 - if (empty($map[$this->filter])) { 19 - $this->filter = self::FILTER_ALL; 20 - } 21 - } 22 - 23 8 private function getAdapter() { 24 9 return $this->adapter; 25 10 } 26 11 27 - public function processRequest() { 28 - $request = $this->getRequest(); 29 - $viewer = $request->getUser(); 12 + public function handleRequest(AphrontRequest $request) { 13 + $viewer = $this->getViewer(); 30 14 31 15 $xscript = id(new HeraldTranscriptQuery()) 32 16 ->setViewer($viewer) 33 - ->withIDs(array($this->id)) 17 + ->withIDs(array($request->getURIData('id'))) 34 18 ->executeOne(); 35 19 if (!$xscript) { 36 20 return new Aphront404Response(); 37 21 } 38 22 39 23 require_celerity_resource('herald-test-css'); 40 - 41 - $nav = $this->buildSideNav(); 24 + $content = array(); 42 25 43 26 $object_xscript = $xscript->getObjectTranscript(); 44 27 if (!$object_xscript) { ··· 49 32 'p', 50 33 array(), 51 34 pht('Details of this transcript have been garbage collected.'))); 52 - $nav->appendChild($notice); 35 + $content[] = $notice; 53 36 } else { 54 37 $map = HeraldAdapter::getEnabledAdapterMap($viewer); 55 38 $object_type = $object_xscript->getType(); ··· 65 48 66 49 $this->adapter = HeraldAdapter::getAdapterForContentType($object_type); 67 50 68 - $filter = $this->getFilterPHIDs(); 69 - $this->filterTranscript($xscript, $filter); 70 - $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript)); 51 + $phids = $this->getTranscriptPHIDs($xscript); 71 52 $phids = array_unique($phids); 72 53 $phids = array_filter($phids); 73 54 ··· 82 63 pht( 83 64 'This was a dry run to test Herald rules, '. 84 65 'no actions were executed.')); 85 - $nav->appendChild($notice); 66 + $content[] = $notice; 86 67 } 87 68 88 69 $warning_panel = $this->buildWarningPanel($xscript); 89 - $nav->appendChild($warning_panel); 90 - 91 - $apply_xscript_panel = $this->buildApplyTranscriptPanel( 92 - $xscript); 93 - $nav->appendChild($apply_xscript_panel); 94 - 95 - $action_xscript_panel = $this->buildActionTranscriptPanel( 96 - $xscript); 97 - $nav->appendChild($action_xscript_panel); 70 + $content[] = $warning_panel; 98 71 99 - $object_xscript_panel = $this->buildObjectTranscriptPanel( 100 - $xscript); 101 - $nav->appendChild($object_xscript_panel); 72 + $content[] = array( 73 + $this->buildActionTranscriptPanel($xscript), 74 + $this->buildObjectTranscriptPanel($xscript), 75 + ); 102 76 } 103 77 104 78 $crumbs = id($this->buildApplicationCrumbs()) ··· 106 80 pht('Transcripts'), 107 81 $this->getApplicationURI('/transcript/')) 108 82 ->addTextCrumb($xscript->getID()); 109 - $nav->setCrumbs($crumbs); 110 83 111 84 return $this->buildApplicationPage( 112 - $nav, 85 + array( 86 + $crumbs, 87 + $content, 88 + ), 113 89 array( 114 90 'title' => pht('Transcript'), 115 91 )); ··· 146 122 return phutil_tag('span', array('class' => 'condition-test-value'), $value); 147 123 } 148 124 149 - private function buildSideNav() { 150 - $nav = new AphrontSideNavFilterView(); 151 - $nav->setBaseURI(new PhutilURI('/herald/transcript/'.$this->id.'/')); 152 - 153 - $items = array(); 154 - $filters = $this->getFilterMap(); 155 - foreach ($filters as $key => $name) { 156 - $nav->addFilter($key, $name); 157 - } 158 - $nav->selectFilter($this->filter, null); 159 - 160 - return $nav; 161 - } 162 - 163 - protected function getFilterMap() { 164 - return array( 165 - self::FILTER_ALL => pht('All Rules'), 166 - self::FILTER_OWNED => pht('Rules I Own'), 167 - self::FILTER_AFFECTED => pht('Rules that Affected Me'), 168 - ); 169 - } 170 - 171 - 172 - protected function getFilterPHIDs() { 173 - return array($this->getRequest()->getUser()->getPHID()); 174 - } 175 - 176 125 protected function getTranscriptPHIDs($xscript) { 177 126 $phids = array(); 178 127 ··· 228 177 return $phids; 229 178 } 230 179 231 - protected function filterTranscript($xscript, $filter_phids) { 232 - $filter_owned = ($this->filter == self::FILTER_OWNED); 233 - $filter_affected = ($this->filter == self::FILTER_AFFECTED); 234 - 235 - if (!$filter_owned && !$filter_affected) { 236 - // No filtering to be done. 237 - return; 238 - } 239 - 240 - if (!$xscript->getObjectTranscript()) { 241 - return; 242 - } 243 - 244 - $user_phid = $this->getRequest()->getUser()->getPHID(); 245 - 246 - $keep_apply_xscripts = array(); 247 - $keep_rule_xscripts = array(); 248 - 249 - $filter_phids = array_fill_keys($filter_phids, true); 250 - 251 - $rule_xscripts = $xscript->getRuleTranscripts(); 252 - foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) { 253 - $rule_id = $apply_xscript->getRuleID(); 254 - if ($filter_owned) { 255 - if (empty($rule_xscripts[$rule_id])) { 256 - // No associated rule so you can't own this effect. 257 - continue; 258 - } 259 - if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) { 260 - continue; 261 - } 262 - } else if ($filter_affected) { 263 - $targets = (array)$apply_xscript->getTarget(); 264 - if (!array_select_keys($filter_phids, $targets)) { 265 - continue; 266 - } 267 - } 268 - $keep_apply_xscripts[$id] = true; 269 - if ($rule_id) { 270 - $keep_rule_xscripts[$rule_id] = true; 271 - } 272 - } 273 - 274 - foreach ($rule_xscripts as $rule_id => $rule_xscript) { 275 - if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) { 276 - $keep_rule_xscripts[$rule_id] = true; 277 - } 278 - } 279 - 280 - $xscript->setRuleTranscripts( 281 - array_intersect_key( 282 - $xscript->getRuleTranscripts(), 283 - $keep_rule_xscripts)); 284 - 285 - $xscript->setApplyTranscripts( 286 - array_intersect_key( 287 - $xscript->getApplyTranscripts(), 288 - $keep_apply_xscripts)); 289 - 290 - $xscript->setConditionTranscripts( 291 - array_intersect_key( 292 - $xscript->getConditionTranscripts(), 293 - $keep_rule_xscripts)); 294 - } 295 - 296 180 private function buildWarningPanel(HeraldTranscript $xscript) { 297 181 $request = $this->getRequest(); 298 182 $panel = null; ··· 333 217 return $panel; 334 218 } 335 219 336 - private function buildApplyTranscriptPanel(HeraldTranscript $xscript) { 337 - $handles = $this->handles; 220 + private function buildActionTranscriptPanel(HeraldTranscript $xscript) { 221 + $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID'); 222 + 338 223 $adapter = $this->getAdapter(); 339 224 340 - $rule_type_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; 341 - $action_names = $adapter->getActionNameMap($rule_type_global); 225 + $field_names = $adapter->getFieldNameMap(); 226 + $condition_names = $adapter->getConditionNameMap(); 342 227 343 - $list = new PHUIObjectItemListView(); 344 - $list->setStates(true); 345 - $list->setNoDataString(pht('No actions were taken.')); 346 - foreach ($xscript->getApplyTranscripts() as $apply_xscript) { 228 + $handles = $this->handles; 347 229 348 - $target = $apply_xscript->getTarget(); 349 - switch ($apply_xscript->getAction()) { 350 - case HeraldAdapter::ACTION_FLAG: 351 - $target = PhabricatorFlagColor::getColorName($target); 352 - break; 353 - case HeraldAdapter::ACTION_BLOCK: 354 - // Target is a text string. 355 - $target = $target; 356 - break; 357 - default: 358 - // TODO: This should be driven by HeraldActions. 230 + $action_map = $xscript->getApplyTranscripts(); 231 + $action_map = mgroup($action_map, 'getRuleID'); 232 + 233 + $rule_list = id(new PHUIObjectItemListView()) 234 + ->setNoDataString(pht('No Herald rules applied to this object.')); 359 235 360 - if (is_array($target) && $target) { 361 - foreach ($target as $k => $phid) { 362 - if (isset($handles[$phid])) { 363 - $target[$k] = $handles[$phid]->getName(); 364 - } 365 - } 366 - $target = implode(', ', $target); 367 - } else if (is_string($target)) { 368 - $target = $target; 369 - } else { 370 - $target = '<empty>'; 371 - } 372 - break; 373 - } 236 + foreach ($xscript->getRuleTranscripts() as $rule_xscript) { 237 + $rule_id = $rule_xscript->getRuleID(); 374 238 375 - $item = new PHUIObjectItemView(); 239 + $rule_item = id(new PHUIObjectItemView()) 240 + ->setObjectName(pht('H%d', $rule_id)) 241 + ->setHeader($rule_xscript->getRuleName()); 376 242 377 - if ($apply_xscript->getApplied()) { 378 - $item->setState(PHUIObjectItemView::STATE_SUCCESS); 379 - } else { 380 - $item->setState(PHUIObjectItemView::STATE_FAIL); 243 + if (!$rule_xscript->getResult()) { 244 + $rule_item->setDisabled(true); 381 245 } 382 246 383 - $rule = idx( 384 - $action_names, 385 - $apply_xscript->getAction(), 386 - pht('Unknown Action "%s"', $apply_xscript->getAction())); 247 + $rule_list->addItem($rule_item); 387 248 388 - $item->setHeader(pht('%s: %s', $rule, $target)); 389 - $item->addAttribute($apply_xscript->getReason()); 249 + // Build the field/condition transcript. 390 250 391 - // TODO: This is a bit of a mess while actions convert. 251 + $cond_xscripts = $xscript->getConditionTranscriptsForRule($rule_id); 392 252 393 - $item->addAttribute( 394 - pht('Outcome: %s', $apply_xscript->getAppliedReason())); 253 + $cond_list = id(new PHUIStatusListView()); 254 + $cond_list->addItem( 255 + id(new PHUIStatusItemView()) 256 + ->setTarget(phutil_tag('strong', array(), pht('Conditions')))); 395 257 396 - $list->addItem($item); 397 - } 258 + foreach ($cond_xscripts as $cond_xscript) { 259 + if ($cond_xscript->getResult()) { 260 + $icon = 'fa-check'; 261 + $color = 'green'; 262 + $result = pht('Passed'); 263 + } else { 264 + $icon = 'fa-times'; 265 + $color = 'red'; 266 + $result = pht('Failed'); 267 + } 398 268 399 - $box = new PHUIObjectBoxView(); 400 - $box->setHeaderText(pht('Actions Taken')); 401 - $box->appendChild($list); 269 + if ($cond_xscript->getNote()) { 270 + $note = phutil_tag( 271 + 'div', 272 + array( 273 + 'class' => 'herald-condition-note', 274 + ), 275 + $cond_xscript->getNote()); 276 + } else { 277 + $note = null; 278 + } 402 279 403 - return $box; 404 - } 280 + // TODO: This is not really translatable and should be driven through 281 + // HeraldField. 282 + $explanation = pht( 283 + '%s %s %s', 284 + idx($field_names, $cond_xscript->getFieldName(), pht('Unknown')), 285 + idx($condition_names, $cond_xscript->getCondition(), pht('Unknown')), 286 + $this->renderConditionTestValue($cond_xscript, $handles)); 405 287 406 - private function buildActionTranscriptPanel(HeraldTranscript $xscript) { 407 - $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID'); 288 + $cond_item = id(new PHUIStatusItemView()) 289 + ->setIcon($icon, $color) 290 + ->setTarget($result) 291 + ->setNote(array($explanation, $note)); 408 292 409 - $adapter = $this->getAdapter(); 293 + $cond_list->addItem($cond_item); 294 + } 410 295 296 + if ($rule_xscript->getResult()) { 297 + $last_icon = 'fa-check-circle'; 298 + $last_color = 'green'; 299 + $last_result = pht('Passed'); 300 + $last_note = pht('Rule passed.'); 301 + } else { 302 + $last_icon = 'fa-times-circle'; 303 + $last_color = 'red'; 304 + $last_result = pht('Failed'); 305 + $last_note = pht('Rule failed.'); 306 + } 411 307 412 - $field_names = $adapter->getFieldNameMap(); 413 - $condition_names = $adapter->getConditionNameMap(); 308 + $cond_last = id(new PHUIStatusItemView()) 309 + ->setIcon($last_icon, $last_color) 310 + ->setTarget(phutil_tag('strong', array(), $last_result)) 311 + ->setNote($last_note); 312 + $cond_list->addItem($cond_last); 313 + 314 + $cond_box = id(new PHUIBoxView()) 315 + ->appendChild($cond_list) 316 + ->addMargin(PHUI::MARGIN_LARGE_LEFT); 317 + 318 + $rule_item->appendChild($cond_box); 319 + 320 + if (!$rule_xscript->getResult()) { 321 + // If the rule didn't pass, don't generate an action transcript since 322 + // actions didn't apply. 323 + continue; 324 + } 414 325 415 - $handles = $this->handles; 326 + $cond_box->addMargin(PHUI::MARGIN_MEDIUM_BOTTOM); 416 327 417 - $rule_markup = array(); 418 - foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) { 419 - $cond_markup = array(); 420 - foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) { 421 - if ($cond->getNote()) { 422 - $note = phutil_tag_div('herald-condition-note', $cond->getNote()); 423 - } else { 424 - $note = null; 425 - } 328 + $action_xscripts = idx($action_map, $rule_id, array()); 329 + foreach ($action_xscripts as $action_xscript) { 330 + $action_key = $action_xscript->getAction(); 331 + $action = $adapter->getActionImplementation($action_key); 426 332 427 - if ($cond->getResult()) { 428 - $result = phutil_tag( 429 - 'span', 430 - array('class' => 'herald-outcome condition-pass'), 431 - "\xE2\x9C\x93"); 333 + if ($action) { 334 + $name = $action->getHeraldActionName(); 335 + $action->setViewer($this->getViewer()); 432 336 } else { 433 - $result = phutil_tag( 434 - 'span', 435 - array('class' => 'herald-outcome condition-fail'), 436 - "\xE2\x9C\x98"); 337 + $name = pht('Unknown Action ("%s")', $action_key); 437 338 } 438 339 439 - $cond_markup[] = phutil_tag( 440 - 'li', 441 - array(), 442 - pht( 443 - '%s Condition: %s %s %s%s', 444 - $result, 445 - idx($field_names, $cond->getFieldName(), pht('Unknown')), 446 - idx($condition_names, $cond->getCondition(), pht('Unknown')), 447 - $this->renderConditionTestValue($cond, $handles), 448 - $note)); 449 - } 340 + $name = pht('Action: %s', $name); 450 341 451 - if ($rule->getResult()) { 452 - $result = phutil_tag( 453 - 'span', 454 - array('class' => 'herald-outcome rule-pass'), 455 - pht('PASS')); 456 - $class = 'herald-rule-pass'; 457 - } else { 458 - $result = phutil_tag( 459 - 'span', 460 - array('class' => 'herald-outcome rule-fail'), 461 - pht('FAIL')); 462 - $class = 'herald-rule-fail'; 463 - } 342 + $action_list = id(new PHUIStatusListView()); 343 + $action_list->addItem( 344 + id(new PHUIStatusItemView()) 345 + ->setTarget(phutil_tag('strong', array(), $name))); 464 346 465 - $cond_markup[] = phutil_tag( 466 - 'li', 467 - array(), 468 - array($result, $rule->getReason())); 469 - $user_phid = $this->getRequest()->getUser()->getPHID(); 347 + $action_box = id(new PHUIBoxView()) 348 + ->appendChild($action_list) 349 + ->addMargin(PHUI::MARGIN_LARGE_LEFT); 470 350 471 - $name = $rule->getRuleName(); 351 + $rule_item->appendChild($action_box); 472 352 473 - $rule_markup[] = 474 - phutil_tag( 475 - 'li', 476 - array( 477 - 'class' => $class, 478 - ), 479 - phutil_tag_div('rule-name', array( 480 - phutil_tag('strong', array(), $name), 481 - ' ', 482 - phutil_tag('ul', array(), $cond_markup), 483 - ))); 484 - } 353 + $log = $action_xscript->getAppliedReason(); 354 + 355 + // Handle older transcripts which used a static string to record 356 + // action results. 357 + if (!is_array($log)) { 358 + $action_list->addItem( 359 + id(new PHUIStatusItemView()) 360 + ->setIcon('fa-clock-o', 'grey') 361 + ->setTarget(pht('Old Transcript')) 362 + ->setNote( 363 + pht( 364 + 'This is an old transcript which uses an obsolete log '. 365 + 'format. Detailed action information is not available.'))); 366 + continue; 367 + } 368 + 369 + foreach ($log as $entry) { 370 + $type = idx($entry, 'type'); 371 + $data = idx($entry, 'data'); 372 + 373 + if ($action) { 374 + $icon = $action->renderActionEffectIcon($type, $data); 375 + $color = $action->renderActionEffectColor($type, $data); 376 + $name = $action->renderActionEffectName($type, $data); 377 + $note = $action->renderActionEffectDescription($type, $data); 378 + } else { 379 + $icon = 'fa-question-circle'; 380 + $color = 'indigo'; 381 + $name = pht('Unknown Effect ("%s")', $type); 382 + $note = null; 383 + } 485 384 486 - $box = null; 487 - if ($rule_markup) { 488 - $box = new PHUIObjectBoxView(); 489 - $box->setHeaderText(pht('Rule Details')); 490 - $box->appendChild(phutil_tag( 491 - 'ul', 492 - array('class' => 'herald-explain-list'), 493 - $rule_markup)); 385 + $action_item = id(new PHUIStatusItemView()) 386 + ->setIcon($icon, $color) 387 + ->setTarget($name) 388 + ->setNote($note); 389 + 390 + $action_list->addItem($action_item); 391 + } 392 + } 494 393 } 394 + 395 + $box = id(new PHUIObjectBoxView()) 396 + ->setHeaderText(pht('Rule Transcript')) 397 + ->appendChild($rule_list); 398 + 495 399 return $box; 496 400 } 497 401
-1
src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php
··· 84 84 self::ACTION_ADD_CC, 85 85 self::ACTION_REMOVE_CC, 86 86 self::ACTION_EMAIL, 87 - self::ACTION_FLAG, 88 87 self::ACTION_ASSIGN_TASK, 89 88 ), 90 89 parent::getActions($rule_type));
-1
src/applications/pholio/herald/HeraldPholioMockAdapter.php
··· 61 61 array( 62 62 self::ACTION_ADD_CC, 63 63 self::ACTION_REMOVE_CC, 64 - self::ACTION_FLAG, 65 64 ), 66 65 parent::getActions($rule_type)); 67 66 }
-1
src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php
··· 64 64 self::ACTION_ADD_CC, 65 65 self::ACTION_REMOVE_CC, 66 66 self::ACTION_EMAIL, 67 - self::ACTION_FLAG, 68 67 ), 69 68 parent::getActions($rule_type)); 70 69 }
+1 -70
webroot/rsrc/css/application/herald/herald-test.css
··· 2 2 * @provides herald-test-css 3 3 */ 4 4 5 - ul.herald-explain-list { 6 - margin: 12px; 7 - } 8 - 9 - div.herald-condition-note { 10 - clear: both; 11 - margin: .5em 0em .53em 86px; 12 - padding: .5em 1em; 13 - background: #FFFF00; 14 - font-weight: bold; 15 - } 16 - 17 - ul.herald-explain-list .herald-outcome { 18 - margin-right: 6px; 19 - width: 50px; 20 - text-align: center; 21 - position: relative; 22 - float: left; 23 - font-weight: bold; 24 - padding: 1px 2px; 25 - } 26 - 27 - ul.herald-explain-list .condition-fail, 28 - ul.herald-explain-list .rule-fail { 5 + .herald-condition-note { 29 6 color: {$red}; 30 - } 31 - 32 - ul.herald-explain-list .condition-pass, 33 - ul.herald-explain-list .rule-pass { 34 - color: {$green}; 35 - } 36 - 37 - ul.herald-explain-list li.herald-rule-pass, 38 - ul.herald-explain-list li.herald-rule-fail { 39 - margin: 0 0 8px; 40 - } 41 - 42 - ul.herald-explain-list div.rule-name { 43 - padding: 4px 0 8px 0; 44 - } 45 - 46 - ul.herald-explain-list li.herald-rule-fail, 47 - ul.herald-explain-list li.herald-rule-pass { 48 - background: #ffffff; 49 - } 50 - 51 - ul.herald-explain-list ul { 52 - margin: 4px; 53 - } 54 - 55 - ul.herald-explain-list ul li { 56 - padding: 2px 0; 57 - } 58 - 59 - .outcome-failure, 60 - .outcome-success { 61 - font-weight: bold; 62 - } 63 - 64 - .outcome-failure { 65 - color: {$red}; 66 - } 67 - 68 - .outcome-success { 69 - color: {$green}; 70 - } 71 - 72 - .action-header { 73 - font-weight: bold; 74 - padding-top: 12px; 75 - border-bottom: 1px solid {$thinblueborder}; 76 7 } 77 8 78 9 textarea.herald-field-value-transcript {