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
This commit is contained in:
@@ -806,6 +806,7 @@ phutil_register_library_map(array(
|
||||
'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php',
|
||||
'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php',
|
||||
'HeraldController' => 'applications/herald/controller/HeraldController.php',
|
||||
'HeraldCustomAction' => 'applications/herald/extension/HeraldCustomAction.php',
|
||||
'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php',
|
||||
'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/HeraldDifferentialRevisionAdapter.php',
|
||||
'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php',
|
||||
|
||||
@@ -101,6 +101,24 @@ abstract class HeraldAdapter {
|
||||
private $contentSource;
|
||||
private $isNewObject;
|
||||
private $customFields = false;
|
||||
private $customActions = null;
|
||||
private $queuedTransactions = array();
|
||||
|
||||
public function getCustomActions() {
|
||||
if ($this->customActions === null) {
|
||||
$this->customActions = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass('HeraldCustomAction')
|
||||
->loadObjects();
|
||||
|
||||
foreach ($this->customActions as $key => $object) {
|
||||
if (!$object->appliesToAdapter($this)) {
|
||||
unset($this->customActions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->customActions;
|
||||
}
|
||||
|
||||
public function setContentSource(PhabricatorContentSource $content_source) {
|
||||
$this->contentSource = $content_source;
|
||||
@@ -145,7 +163,24 @@ abstract class HeraldAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function applyHeraldEffects(array $effects);
|
||||
public abstract function applyHeraldEffects(array $effects);
|
||||
|
||||
protected function handleCustomHeraldEffect(HeraldEffect $effect) {
|
||||
foreach ($this->getCustomActions() as $custom_action) {
|
||||
if ($effect->getAction() == $custom_action->getActionKey()) {
|
||||
$result = $custom_action->applyEffect(
|
||||
$this,
|
||||
$this->getObject(),
|
||||
$effect);
|
||||
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAvailableToUser(PhabricatorUser $viewer) {
|
||||
$applications = id(new PhabricatorApplicationQuery())
|
||||
@@ -157,6 +192,14 @@ abstract class HeraldAdapter {
|
||||
return !empty($applications);
|
||||
}
|
||||
|
||||
public function queueTransaction($transaction) {
|
||||
$this->queuedTransactions[] = $transaction;
|
||||
}
|
||||
|
||||
public function getQueuedTransactions() {
|
||||
return $this->queuedTransactions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOTE: You generally should not override this; it exists to support legacy
|
||||
@@ -645,13 +688,21 @@ abstract class HeraldAdapter {
|
||||
|
||||
/* -( Actions )------------------------------------------------------------ */
|
||||
|
||||
abstract public function getActions($rule_type);
|
||||
public function getActions($rule_type) {
|
||||
$results = array();
|
||||
foreach ($this->getCustomActions() as $custom_action) {
|
||||
if ($custom_action->appliesToRuleType($rule_type)) {
|
||||
$results[] = $custom_action->getActionKey();
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function getActionNameMap($rule_type) {
|
||||
switch ($rule_type) {
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
|
||||
return array(
|
||||
$standard = array(
|
||||
self::ACTION_NOTHING => pht('Do nothing'),
|
||||
self::ACTION_ADD_CC => pht('Add emails to CC'),
|
||||
self::ACTION_REMOVE_CC => pht('Remove emails from CC'),
|
||||
@@ -666,8 +717,9 @@ abstract class HeraldAdapter {
|
||||
self::ACTION_REQUIRE_SIGNATURE => pht('Require legal signatures'),
|
||||
self::ACTION_BLOCK => pht('Block change with message'),
|
||||
);
|
||||
break;
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
|
||||
return array(
|
||||
$standard = array(
|
||||
self::ACTION_NOTHING => pht('Do nothing'),
|
||||
self::ACTION_ADD_CC => pht('Add me to CC'),
|
||||
self::ACTION_REMOVE_CC => pht('Remove me from CC'),
|
||||
@@ -680,9 +732,19 @@ abstract class HeraldAdapter {
|
||||
self::ACTION_ADD_BLOCKING_REVIEWERS =>
|
||||
pht('Add me as a blocking reviewer'),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown rule type '{$rule_type}'!");
|
||||
}
|
||||
|
||||
foreach ($this->getCustomActions() as $custom_action) {
|
||||
if ($custom_action->appliesToRuleType($rule_type)) {
|
||||
$standard[$custom_action->getActionKey()] =
|
||||
$custom_action->getActionName();
|
||||
}
|
||||
}
|
||||
|
||||
return $standard;
|
||||
}
|
||||
|
||||
public function willSaveAction(
|
||||
@@ -814,7 +876,7 @@ abstract class HeraldAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
public static function getValueTypeForAction($action, $rule_type) {
|
||||
public function getValueTypeForAction($action, $rule_type) {
|
||||
$is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
|
||||
|
||||
if ($is_personal) {
|
||||
@@ -832,8 +894,6 @@ abstract class HeraldAdapter {
|
||||
return self::VALUE_FLAG_COLOR;
|
||||
case self::ACTION_ADD_PROJECTS:
|
||||
return self::VALUE_PROJECT;
|
||||
default:
|
||||
throw new Exception("Unknown or invalid action '{$action}'.");
|
||||
}
|
||||
} else {
|
||||
switch ($action) {
|
||||
@@ -859,10 +919,18 @@ abstract class HeraldAdapter {
|
||||
return self::VALUE_LEGAL_DOCUMENTS;
|
||||
case self::ACTION_BLOCK:
|
||||
return self::VALUE_TEXT;
|
||||
default:
|
||||
throw new Exception("Unknown or invalid action '{$action}'.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getCustomActions() as $custom_action) {
|
||||
if ($custom_action->appliesToRuleType($rule_type)) {
|
||||
if ($action === $custom_action->getActionKey()) {
|
||||
return $custom_action->getActionType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Unknown or invalid action '".$action."'.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -139,21 +139,25 @@ final class HeraldCommitAdapter extends HeraldAdapter {
|
||||
switch ($rule_type) {
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_AUDIT,
|
||||
self::ACTION_APPLY_BUILD_PLANS,
|
||||
self::ACTION_NOTHING
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_AUDIT,
|
||||
self::ACTION_APPLY_BUILD_PLANS,
|
||||
self::ACTION_NOTHING
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_AUDIT,
|
||||
self::ACTION_NOTHING,
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_AUDIT,
|
||||
self::ACTION_NOTHING,
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,7 +548,13 @@ final class HeraldCommitAdapter extends HeraldAdapter {
|
||||
$this->commit->getPHID());
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
$custom_result = parent::handleCustomHeraldEffect($effect);
|
||||
if ($custom_result === null) {
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
}
|
||||
|
||||
$result[] = $custom_result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
|
||||
@@ -345,26 +345,30 @@ final class HeraldDifferentialRevisionAdapter extends HeraldAdapter {
|
||||
public function getActions($rule_type) {
|
||||
switch ($rule_type) {
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_REMOVE_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_ADD_REVIEWERS,
|
||||
self::ACTION_ADD_BLOCKING_REVIEWERS,
|
||||
self::ACTION_APPLY_BUILD_PLANS,
|
||||
self::ACTION_REQUIRE_SIGNATURE,
|
||||
self::ACTION_NOTHING,
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_REMOVE_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_ADD_REVIEWERS,
|
||||
self::ACTION_ADD_BLOCKING_REVIEWERS,
|
||||
self::ACTION_APPLY_BUILD_PLANS,
|
||||
self::ACTION_REQUIRE_SIGNATURE,
|
||||
self::ACTION_NOTHING,
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_REMOVE_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_ADD_REVIEWERS,
|
||||
self::ACTION_ADD_BLOCKING_REVIEWERS,
|
||||
self::ACTION_NOTHING,
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_REMOVE_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_ADD_REVIEWERS,
|
||||
self::ACTION_ADD_BLOCKING_REVIEWERS,
|
||||
self::ACTION_NOTHING,
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +495,13 @@ final class HeraldDifferentialRevisionAdapter extends HeraldAdapter {
|
||||
pht('Required signatures.'));
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
$custom_result = parent::handleCustomHeraldEffect($effect);
|
||||
if ($custom_result === null) {
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
}
|
||||
|
||||
$result[] = $custom_result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
|
||||
@@ -98,21 +98,25 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
||||
public function getActions($rule_type) {
|
||||
switch ($rule_type) {
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_ASSIGN_TASK,
|
||||
self::ACTION_ADD_PROJECTS,
|
||||
self::ACTION_NOTHING,
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_ASSIGN_TASK,
|
||||
self::ACTION_ADD_PROJECTS,
|
||||
self::ACTION_NOTHING,
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_ASSIGN_TASK,
|
||||
self::ACTION_NOTHING,
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_EMAIL,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_ASSIGN_TASK,
|
||||
self::ACTION_NOTHING,
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +204,13 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
||||
pht('Added projects.'));
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
$custom_result = parent::handleCustomHeraldEffect($effect);
|
||||
if ($custom_result === null) {
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
}
|
||||
|
||||
$result[] = $custom_result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
|
||||
@@ -64,16 +64,20 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
|
||||
public function getActions($rule_type) {
|
||||
switch ($rule_type) {
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_NOTHING,
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_NOTHING,
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
|
||||
return array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_NOTHING,
|
||||
);
|
||||
return array_merge(
|
||||
array(
|
||||
self::ACTION_ADD_CC,
|
||||
self::ACTION_FLAG,
|
||||
self::ACTION_NOTHING,
|
||||
),
|
||||
parent::getActions($rule_type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +137,13 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
|
||||
$this->getMock()->getPHID());
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
$custom_result = parent::handleCustomHeraldEffect($effect);
|
||||
if ($custom_result === null) {
|
||||
throw new Exception("No rules to handle action '{$action}'.");
|
||||
}
|
||||
|
||||
$result[] = $custom_result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
|
||||
@@ -397,11 +397,15 @@ final class HeraldRuleController extends HeraldController {
|
||||
$current_value = $action->getTarget();
|
||||
break;
|
||||
default:
|
||||
$target_map = array();
|
||||
foreach ((array)$action->getTarget() as $fbid) {
|
||||
$target_map[$fbid] = $handles[$fbid]->getName();
|
||||
if (is_array($action->getTarget())) {
|
||||
$target_map = array();
|
||||
foreach ((array)$action->getTarget() as $fbid) {
|
||||
$target_map[$fbid] = $handles[$fbid]->getName();
|
||||
}
|
||||
$current_value = $target_map;
|
||||
} else {
|
||||
$current_value = $action->getTarget();
|
||||
}
|
||||
$current_value = $target_map;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -354,13 +354,15 @@ final class HeraldTranscriptController extends HeraldController {
|
||||
$target = $target;
|
||||
break;
|
||||
default:
|
||||
if ($target) {
|
||||
if (is_array($target) && $target) {
|
||||
foreach ($target as $k => $phid) {
|
||||
if (isset($handles[$phid])) {
|
||||
$target[$k] = $handles[$phid]->getName();
|
||||
}
|
||||
}
|
||||
$target = implode(', ', $target);
|
||||
} else if (is_string($target)) {
|
||||
$target = $target;
|
||||
} else {
|
||||
$target = '<empty>';
|
||||
}
|
||||
|
||||
20
src/applications/herald/extension/HeraldCustomAction.php
Normal file
20
src/applications/herald/extension/HeraldCustomAction.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
abstract class HeraldCustomAction {
|
||||
|
||||
public abstract function appliesToAdapter(HeraldAdapter $adapter);
|
||||
|
||||
public abstract function appliesToRuleType($rule_type);
|
||||
|
||||
public abstract function getActionKey();
|
||||
|
||||
public abstract function getActionName();
|
||||
|
||||
public abstract function getActionType();
|
||||
|
||||
public abstract function applyEffect(
|
||||
HeraldAdapter $adapter,
|
||||
$object,
|
||||
HeraldEffect $effect);
|
||||
|
||||
}
|
||||
@@ -2197,7 +2197,9 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||
$this->setHeraldAdapter($adapter);
|
||||
$this->setHeraldTranscript($xscript);
|
||||
|
||||
return $this->didApplyHeraldRules($object, $adapter, $xscript);
|
||||
return array_merge(
|
||||
$this->didApplyHeraldRules($object, $adapter, $xscript),
|
||||
$adapter->getQueuedTransactions());
|
||||
}
|
||||
|
||||
protected function didApplyHeraldRules(
|
||||
|
||||
Reference in New Issue
Block a user