1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 09:18:48 +02:00

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:
James Rhodes 2014-07-02 14:29:46 +10:00
parent c9366acbec
commit 88aba65d54
10 changed files with 212 additions and 75 deletions

View file

@ -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',

View file

@ -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."'.");
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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;
}

View file

@ -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>';
}

View 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);
}

View file

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