@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 custom actions in Herald

Summary:
This was significantly easier than expected. Here's an example of what an extension class might look like:

```
<?php

final class AddRiskReviewHeraldCustomAction extends HeraldCustomAction {

public function appliesToAdapter(HeraldAdapter $adapter) {
return $adapter instanceof HeraldDifferentialRevisionAdapter;
}

public function appliesToRuleType($rule_type) {
return $rule_type == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL ||
$rule_type == HeraldRuleTypeConfig::RULE_TYPE_OBJECT;
}

public function getActionKey() {
return 'custom:add-risk';
}

public function getActionName() {
return 'Add risk rating (JSON)';
}

public function getActionType() {
return HeraldAdapter::VALUE_TEXT;
}

public function applyEffect(
HeraldAdapter $adapter,
$object,
HeraldEffect $effect) {

$key = "phragile:risk-rating";

// Read existing value.
$field_list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_VIEW);
$field_list->readFieldsFromStorage($object);
$field_list = mpull($field_list->getFields(), null, 'getFieldKey');
$field = $field_list[$key];
$field->setObject($object);
$field->setViewer(PhabricatorUser::getOmnipotentUser());

$risk = $field->getValue();
$old_risk = $risk; // PHP copies arrays by default!

// Add new value to array.
$herald_args = phutil_json_decode($effect->getTarget());
$risk[$herald_args['key']] = array(
'value' => $herald_args['value'],
'reason' => $herald_args['reason']);
$risk_key = $herald_args['key'];

// Set new value.
$adapter->queueTransaction(
id(new DifferentialTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
->setMetadataValue('customfield:key', $key)
->setOldValue($old_risk)
->setNewValue($risk));

return new HeraldApplyTranscript(
$effect,
true,
pht(
'Modifying automatic risk ratings (key: %s)!',
$risk_key));
}

}
```

Test Plan: Created a custom action for differential revisions, set up a Herald rule to match and trigger the custom action, did 'arc diff' and saw the action trigger in the transcripts.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: locutus, edutibau, ite-klass, epriestley, Korvin

Maniphest Tasks: T4884

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

+212 -75
+1
src/__phutil_library_map__.php
··· 806 806 'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php', 807 807 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', 808 808 'HeraldController' => 'applications/herald/controller/HeraldController.php', 809 + 'HeraldCustomAction' => 'applications/herald/extension/HeraldCustomAction.php', 809 810 'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php', 810 811 'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/HeraldDifferentialRevisionAdapter.php', 811 812 'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php',
+77 -9
src/applications/herald/adapter/HeraldAdapter.php
··· 101 101 private $contentSource; 102 102 private $isNewObject; 103 103 private $customFields = false; 104 + private $customActions = null; 105 + private $queuedTransactions = array(); 106 + 107 + public function getCustomActions() { 108 + if ($this->customActions === null) { 109 + $this->customActions = id(new PhutilSymbolLoader()) 110 + ->setAncestorClass('HeraldCustomAction') 111 + ->loadObjects(); 112 + 113 + foreach ($this->customActions as $key => $object) { 114 + if (!$object->appliesToAdapter($this)) { 115 + unset($this->customActions[$key]); 116 + } 117 + } 118 + } 119 + 120 + return $this->customActions; 121 + } 104 122 105 123 public function setContentSource(PhabricatorContentSource $content_source) { 106 124 $this->contentSource = $content_source; ··· 145 163 } 146 164 } 147 165 148 - abstract public function applyHeraldEffects(array $effects); 166 + public abstract function applyHeraldEffects(array $effects); 167 + 168 + protected function handleCustomHeraldEffect(HeraldEffect $effect) { 169 + foreach ($this->getCustomActions() as $custom_action) { 170 + if ($effect->getAction() == $custom_action->getActionKey()) { 171 + $result = $custom_action->applyEffect( 172 + $this, 173 + $this->getObject(), 174 + $effect); 175 + 176 + if ($result !== null) { 177 + return $result; 178 + } 179 + } 180 + } 181 + 182 + return null; 183 + } 149 184 150 185 public function isAvailableToUser(PhabricatorUser $viewer) { 151 186 $applications = id(new PhabricatorApplicationQuery()) ··· 155 190 ->execute(); 156 191 157 192 return !empty($applications); 193 + } 194 + 195 + public function queueTransaction($transaction) { 196 + $this->queuedTransactions[] = $transaction; 197 + } 198 + 199 + public function getQueuedTransactions() { 200 + return $this->queuedTransactions; 158 201 } 159 202 160 203 ··· 645 688 646 689 /* -( Actions )------------------------------------------------------------ */ 647 690 648 - abstract public function getActions($rule_type); 691 + public function getActions($rule_type) { 692 + $results = array(); 693 + foreach ($this->getCustomActions() as $custom_action) { 694 + if ($custom_action->appliesToRuleType($rule_type)) { 695 + $results[] = $custom_action->getActionKey(); 696 + } 697 + } 698 + return $results; 699 + } 649 700 650 701 public function getActionNameMap($rule_type) { 651 702 switch ($rule_type) { 652 703 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 653 704 case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: 654 - return array( 705 + $standard = array( 655 706 self::ACTION_NOTHING => pht('Do nothing'), 656 707 self::ACTION_ADD_CC => pht('Add emails to CC'), 657 708 self::ACTION_REMOVE_CC => pht('Remove emails from CC'), ··· 666 717 self::ACTION_REQUIRE_SIGNATURE => pht('Require legal signatures'), 667 718 self::ACTION_BLOCK => pht('Block change with message'), 668 719 ); 720 + break; 669 721 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 670 - return array( 722 + $standard = array( 671 723 self::ACTION_NOTHING => pht('Do nothing'), 672 724 self::ACTION_ADD_CC => pht('Add me to CC'), 673 725 self::ACTION_REMOVE_CC => pht('Remove me from CC'), ··· 680 732 self::ACTION_ADD_BLOCKING_REVIEWERS => 681 733 pht('Add me as a blocking reviewer'), 682 734 ); 735 + break; 683 736 default: 684 737 throw new Exception("Unknown rule type '{$rule_type}'!"); 685 738 } 739 + 740 + foreach ($this->getCustomActions() as $custom_action) { 741 + if ($custom_action->appliesToRuleType($rule_type)) { 742 + $standard[$custom_action->getActionKey()] = 743 + $custom_action->getActionName(); 744 + } 745 + } 746 + 747 + return $standard; 686 748 } 687 749 688 750 public function willSaveAction( ··· 814 876 } 815 877 } 816 878 817 - public static function getValueTypeForAction($action, $rule_type) { 879 + public function getValueTypeForAction($action, $rule_type) { 818 880 $is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); 819 881 820 882 if ($is_personal) { ··· 832 894 return self::VALUE_FLAG_COLOR; 833 895 case self::ACTION_ADD_PROJECTS: 834 896 return self::VALUE_PROJECT; 835 - default: 836 - throw new Exception("Unknown or invalid action '{$action}'."); 837 897 } 838 898 } else { 839 899 switch ($action) { ··· 859 919 return self::VALUE_LEGAL_DOCUMENTS; 860 920 case self::ACTION_BLOCK: 861 921 return self::VALUE_TEXT; 862 - default: 863 - throw new Exception("Unknown or invalid action '{$action}'."); 864 922 } 865 923 } 924 + 925 + foreach ($this->getCustomActions() as $custom_action) { 926 + if ($custom_action->appliesToRuleType($rule_type)) { 927 + if ($action === $custom_action->getActionKey()) { 928 + return $custom_action->getActionType(); 929 + } 930 + } 931 + } 932 + 933 + throw new Exception("Unknown or invalid action '".$action."'."); 866 934 } 867 935 868 936
+25 -15
src/applications/herald/adapter/HeraldCommitAdapter.php
··· 139 139 switch ($rule_type) { 140 140 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 141 141 case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: 142 - return array( 143 - self::ACTION_ADD_CC, 144 - self::ACTION_EMAIL, 145 - self::ACTION_AUDIT, 146 - self::ACTION_APPLY_BUILD_PLANS, 147 - self::ACTION_NOTHING 148 - ); 142 + return array_merge( 143 + array( 144 + self::ACTION_ADD_CC, 145 + self::ACTION_EMAIL, 146 + self::ACTION_AUDIT, 147 + self::ACTION_APPLY_BUILD_PLANS, 148 + self::ACTION_NOTHING 149 + ), 150 + parent::getActions($rule_type)); 149 151 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 150 - return array( 151 - self::ACTION_ADD_CC, 152 - self::ACTION_EMAIL, 153 - self::ACTION_FLAG, 154 - self::ACTION_AUDIT, 155 - self::ACTION_NOTHING, 156 - ); 152 + return array_merge( 153 + array( 154 + self::ACTION_ADD_CC, 155 + self::ACTION_EMAIL, 156 + self::ACTION_FLAG, 157 + self::ACTION_AUDIT, 158 + self::ACTION_NOTHING, 159 + ), 160 + parent::getActions($rule_type)); 157 161 } 158 162 } 159 163 ··· 544 548 $this->commit->getPHID()); 545 549 break; 546 550 default: 547 - throw new Exception("No rules to handle action '{$action}'."); 551 + $custom_result = parent::handleCustomHeraldEffect($effect); 552 + if ($custom_result === null) { 553 + throw new Exception("No rules to handle action '{$action}'."); 554 + } 555 + 556 + $result[] = $custom_result; 557 + break; 548 558 } 549 559 } 550 560 return $result;
+30 -20
src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
··· 345 345 public function getActions($rule_type) { 346 346 switch ($rule_type) { 347 347 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 348 - return array( 349 - self::ACTION_ADD_CC, 350 - self::ACTION_REMOVE_CC, 351 - self::ACTION_EMAIL, 352 - self::ACTION_ADD_REVIEWERS, 353 - self::ACTION_ADD_BLOCKING_REVIEWERS, 354 - self::ACTION_APPLY_BUILD_PLANS, 355 - self::ACTION_REQUIRE_SIGNATURE, 356 - self::ACTION_NOTHING, 357 - ); 348 + return array_merge( 349 + array( 350 + self::ACTION_ADD_CC, 351 + self::ACTION_REMOVE_CC, 352 + self::ACTION_EMAIL, 353 + self::ACTION_ADD_REVIEWERS, 354 + self::ACTION_ADD_BLOCKING_REVIEWERS, 355 + self::ACTION_APPLY_BUILD_PLANS, 356 + self::ACTION_REQUIRE_SIGNATURE, 357 + self::ACTION_NOTHING, 358 + ), 359 + parent::getActions($rule_type)); 358 360 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 359 - return array( 360 - self::ACTION_ADD_CC, 361 - self::ACTION_REMOVE_CC, 362 - self::ACTION_EMAIL, 363 - self::ACTION_FLAG, 364 - self::ACTION_ADD_REVIEWERS, 365 - self::ACTION_ADD_BLOCKING_REVIEWERS, 366 - self::ACTION_NOTHING, 367 - ); 361 + return array_merge( 362 + array( 363 + self::ACTION_ADD_CC, 364 + self::ACTION_REMOVE_CC, 365 + self::ACTION_EMAIL, 366 + self::ACTION_FLAG, 367 + self::ACTION_ADD_REVIEWERS, 368 + self::ACTION_ADD_BLOCKING_REVIEWERS, 369 + self::ACTION_NOTHING, 370 + ), 371 + parent::getActions($rule_type)); 368 372 } 369 373 } 370 374 ··· 491 495 pht('Required signatures.')); 492 496 break; 493 497 default: 494 - throw new Exception("No rules to handle action '{$action}'."); 498 + $custom_result = parent::handleCustomHeraldEffect($effect); 499 + if ($custom_result === null) { 500 + throw new Exception("No rules to handle action '{$action}'."); 501 + } 502 + 503 + $result[] = $custom_result; 504 + break; 495 505 } 496 506 } 497 507 return $result;
+25 -15
src/applications/herald/adapter/HeraldManiphestTaskAdapter.php
··· 98 98 public function getActions($rule_type) { 99 99 switch ($rule_type) { 100 100 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 101 - return array( 102 - self::ACTION_ADD_CC, 103 - self::ACTION_EMAIL, 104 - self::ACTION_ASSIGN_TASK, 105 - self::ACTION_ADD_PROJECTS, 106 - self::ACTION_NOTHING, 107 - ); 101 + return array_merge( 102 + array( 103 + self::ACTION_ADD_CC, 104 + self::ACTION_EMAIL, 105 + self::ACTION_ASSIGN_TASK, 106 + self::ACTION_ADD_PROJECTS, 107 + self::ACTION_NOTHING, 108 + ), 109 + parent::getActions($rule_type)); 108 110 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 109 - return array( 110 - self::ACTION_ADD_CC, 111 - self::ACTION_EMAIL, 112 - self::ACTION_FLAG, 113 - self::ACTION_ASSIGN_TASK, 114 - self::ACTION_NOTHING, 115 - ); 111 + return array_merge( 112 + array( 113 + self::ACTION_ADD_CC, 114 + self::ACTION_EMAIL, 115 + self::ACTION_FLAG, 116 + self::ACTION_ASSIGN_TASK, 117 + self::ACTION_NOTHING, 118 + ), 119 + parent::getActions($rule_type)); 116 120 } 117 121 } 118 122 ··· 200 204 pht('Added projects.')); 201 205 break; 202 206 default: 203 - throw new Exception("No rules to handle action '{$action}'."); 207 + $custom_result = parent::handleCustomHeraldEffect($effect); 208 + if ($custom_result === null) { 209 + throw new Exception("No rules to handle action '{$action}'."); 210 + } 211 + 212 + $result[] = $custom_result; 213 + break; 204 214 } 205 215 } 206 216 return $result;
+20 -10
src/applications/herald/adapter/HeraldPholioMockAdapter.php
··· 64 64 public function getActions($rule_type) { 65 65 switch ($rule_type) { 66 66 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 67 - return array( 68 - self::ACTION_ADD_CC, 69 - self::ACTION_NOTHING, 70 - ); 67 + return array_merge( 68 + array( 69 + self::ACTION_ADD_CC, 70 + self::ACTION_NOTHING, 71 + ), 72 + parent::getActions($rule_type)); 71 73 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 72 - return array( 73 - self::ACTION_ADD_CC, 74 - self::ACTION_FLAG, 75 - self::ACTION_NOTHING, 76 - ); 74 + return array_merge( 75 + array( 76 + self::ACTION_ADD_CC, 77 + self::ACTION_FLAG, 78 + self::ACTION_NOTHING, 79 + ), 80 + parent::getActions($rule_type)); 77 81 } 78 82 } 79 83 ··· 133 137 $this->getMock()->getPHID()); 134 138 break; 135 139 default: 136 - throw new Exception("No rules to handle action '{$action}'."); 140 + $custom_result = parent::handleCustomHeraldEffect($effect); 141 + if ($custom_result === null) { 142 + throw new Exception("No rules to handle action '{$action}'."); 143 + } 144 + 145 + $result[] = $custom_result; 146 + break; 137 147 } 138 148 } 139 149 return $result;
+8 -4
src/applications/herald/controller/HeraldRuleController.php
··· 397 397 $current_value = $action->getTarget(); 398 398 break; 399 399 default: 400 - $target_map = array(); 401 - foreach ((array)$action->getTarget() as $fbid) { 402 - $target_map[$fbid] = $handles[$fbid]->getName(); 400 + if (is_array($action->getTarget())) { 401 + $target_map = array(); 402 + foreach ((array)$action->getTarget() as $fbid) { 403 + $target_map[$fbid] = $handles[$fbid]->getName(); 404 + } 405 + $current_value = $target_map; 406 + } else { 407 + $current_value = $action->getTarget(); 403 408 } 404 - $current_value = $target_map; 405 409 break; 406 410 } 407 411
+3 -1
src/applications/herald/controller/HeraldTranscriptController.php
··· 354 354 $target = $target; 355 355 break; 356 356 default: 357 - if ($target) { 357 + if (is_array($target) && $target) { 358 358 foreach ($target as $k => $phid) { 359 359 if (isset($handles[$phid])) { 360 360 $target[$k] = $handles[$phid]->getName(); 361 361 } 362 362 } 363 363 $target = implode(', ', $target); 364 + } else if (is_string($target)) { 365 + $target = $target; 364 366 } else { 365 367 $target = '<empty>'; 366 368 }
+20
src/applications/herald/extension/HeraldCustomAction.php
··· 1 + <?php 2 + 3 + abstract class HeraldCustomAction { 4 + 5 + public abstract function appliesToAdapter(HeraldAdapter $adapter); 6 + 7 + public abstract function appliesToRuleType($rule_type); 8 + 9 + public abstract function getActionKey(); 10 + 11 + public abstract function getActionName(); 12 + 13 + public abstract function getActionType(); 14 + 15 + public abstract function applyEffect( 16 + HeraldAdapter $adapter, 17 + $object, 18 + HeraldEffect $effect); 19 + 20 + }
+3 -1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 2197 2197 $this->setHeraldAdapter($adapter); 2198 2198 $this->setHeraldTranscript($xscript); 2199 2199 2200 - return $this->didApplyHeraldRules($object, $adapter, $xscript); 2200 + return array_merge( 2201 + $this->didApplyHeraldRules($object, $adapter, $xscript), 2202 + $adapter->getQueuedTransactions()); 2201 2203 } 2202 2204 2203 2205 protected function didApplyHeraldRules(