mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-22 12:41:19 +01:00
Write workboard trigger rules to the database
Summary: Ref T5474. Read and write trigger rules so users can actually edit them. Test Plan: Added, modified, and removed trigger rules. Saved changes, used "Show Details" to review edits. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T5474 Differential Revision: https://secure.phabricator.com/D20302
This commit is contained in:
parent
567dea5449
commit
ff128e1b32
4 changed files with 196 additions and 103 deletions
|
@ -4186,6 +4186,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php',
|
'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php',
|
||||||
'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php',
|
'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php',
|
||||||
'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php',
|
'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php',
|
||||||
|
'PhabricatorProjectTriggerRulesetTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php',
|
||||||
'PhabricatorProjectTriggerSearchEngine' => 'applications/project/query/PhabricatorProjectTriggerSearchEngine.php',
|
'PhabricatorProjectTriggerSearchEngine' => 'applications/project/query/PhabricatorProjectTriggerSearchEngine.php',
|
||||||
'PhabricatorProjectTriggerTransaction' => 'applications/project/storage/PhabricatorProjectTriggerTransaction.php',
|
'PhabricatorProjectTriggerTransaction' => 'applications/project/storage/PhabricatorProjectTriggerTransaction.php',
|
||||||
'PhabricatorProjectTriggerTransactionQuery' => 'applications/project/query/PhabricatorProjectTriggerTransactionQuery.php',
|
'PhabricatorProjectTriggerTransactionQuery' => 'applications/project/query/PhabricatorProjectTriggerTransactionQuery.php',
|
||||||
|
@ -10319,6 +10320,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorProjectTriggerRule' => 'Phobject',
|
'PhabricatorProjectTriggerRule' => 'Phobject',
|
||||||
'PhabricatorProjectTriggerRuleRecord' => 'Phobject',
|
'PhabricatorProjectTriggerRuleRecord' => 'Phobject',
|
||||||
|
'PhabricatorProjectTriggerRulesetTransaction' => 'PhabricatorProjectTriggerTransactionType',
|
||||||
'PhabricatorProjectTriggerSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'PhabricatorProjectTriggerSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'PhabricatorProjectTriggerTransaction' => 'PhabricatorModularTransaction',
|
'PhabricatorProjectTriggerTransaction' => 'PhabricatorModularTransaction',
|
||||||
'PhabricatorProjectTriggerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorProjectTriggerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
|
|
|
@ -55,6 +55,7 @@ final class PhabricatorProjectTriggerEditController
|
||||||
|
|
||||||
$v_name = $trigger->getName();
|
$v_name = $trigger->getName();
|
||||||
$v_edit = $trigger->getEditPolicy();
|
$v_edit = $trigger->getEditPolicy();
|
||||||
|
$v_rules = $trigger->getTriggerRules();
|
||||||
|
|
||||||
$e_name = null;
|
$e_name = null;
|
||||||
$e_edit = null;
|
$e_edit = null;
|
||||||
|
@ -65,8 +66,15 @@ final class PhabricatorProjectTriggerEditController
|
||||||
$v_name = $request->getStr('name');
|
$v_name = $request->getStr('name');
|
||||||
$v_edit = $request->getStr('editPolicy');
|
$v_edit = $request->getStr('editPolicy');
|
||||||
|
|
||||||
$v_rules = $request->getStr('rules');
|
// Read the JSON rules from the request and convert them back into
|
||||||
$v_rules = phutil_json_decode($v_rules);
|
// "TriggerRule" objects so we can render the correct form state
|
||||||
|
// if the user is modifying the rules
|
||||||
|
$raw_rules = $request->getStr('rules');
|
||||||
|
$raw_rules = phutil_json_decode($raw_rules);
|
||||||
|
|
||||||
|
$copy = clone $trigger;
|
||||||
|
$copy->setRuleset($raw_rules);
|
||||||
|
$v_rules = $copy->getTriggerRules();
|
||||||
|
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
if (!$trigger->getID()) {
|
if (!$trigger->getID()) {
|
||||||
|
@ -84,7 +92,10 @@ final class PhabricatorProjectTriggerEditController
|
||||||
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
|
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
|
||||||
->setNewValue($v_edit);
|
->setNewValue($v_edit);
|
||||||
|
|
||||||
// TODO: Actually write the new rules to the database.
|
$xactions[] = $trigger->getApplicationTransactionTemplate()
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorProjectTriggerRulesetTransaction::TRANSACTIONTYPE)
|
||||||
|
->setNewValue($raw_rules);
|
||||||
|
|
||||||
$editor = $trigger->getApplicationTransactionEditor()
|
$editor = $trigger->getApplicationTransactionEditor()
|
||||||
->setActor($viewer)
|
->setActor($viewer)
|
||||||
|
@ -207,6 +218,7 @@ final class PhabricatorProjectTriggerEditController
|
||||||
|
|
||||||
$this->setupEditorBehavior(
|
$this->setupEditorBehavior(
|
||||||
$trigger,
|
$trigger,
|
||||||
|
$v_rules,
|
||||||
$form_id,
|
$form_id,
|
||||||
$table_id,
|
$table_id,
|
||||||
$create_id,
|
$create_id,
|
||||||
|
@ -250,12 +262,12 @@ final class PhabricatorProjectTriggerEditController
|
||||||
|
|
||||||
private function setupEditorBehavior(
|
private function setupEditorBehavior(
|
||||||
PhabricatorProjectTrigger $trigger,
|
PhabricatorProjectTrigger $trigger,
|
||||||
|
array $rule_list,
|
||||||
$form_id,
|
$form_id,
|
||||||
$table_id,
|
$table_id,
|
||||||
$create_id,
|
$create_id,
|
||||||
$input_id) {
|
$input_id) {
|
||||||
|
|
||||||
$rule_list = $trigger->getTriggerRules();
|
|
||||||
$rule_list = mpull($rule_list, 'toDictionary');
|
$rule_list = mpull($rule_list, 'toDictionary');
|
||||||
$rule_list = array_values($rule_list);
|
$rule_list = array_values($rule_list);
|
||||||
|
|
||||||
|
|
|
@ -62,107 +62,19 @@ final class PhabricatorProjectTrigger
|
||||||
return pht('Trigger %d', $this->getID());
|
return pht('Trigger %d', $this->getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setRuleset(array $ruleset) {
|
||||||
|
// Clear any cached trigger rules, since we're changing the ruleset
|
||||||
|
// for the trigger.
|
||||||
|
$this->triggerRules = null;
|
||||||
|
|
||||||
|
parent::setRuleset($ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
public function getTriggerRules() {
|
public function getTriggerRules() {
|
||||||
if ($this->triggerRules === null) {
|
if ($this->triggerRules === null) {
|
||||||
|
$trigger_rules = self::newTriggerRulesFromRuleSpecifications(
|
||||||
// TODO: Temporary hard-coded rule specification.
|
$this->getRuleset(),
|
||||||
$rule_specifications = array(
|
$allow_invalid = true);
|
||||||
array(
|
|
||||||
'type' => 'status',
|
|
||||||
'value' => ManiphestTaskStatus::getDefaultClosedStatus(),
|
|
||||||
),
|
|
||||||
// This is an intentionally unknown rule.
|
|
||||||
array(
|
|
||||||
'type' => 'quack',
|
|
||||||
'value' => 'aaa',
|
|
||||||
),
|
|
||||||
// This is an intentionally invalid rule.
|
|
||||||
array(
|
|
||||||
'type' => 'status',
|
|
||||||
'value' => 'quack',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// NOTE: We're trying to preserve the database state in the rule
|
|
||||||
// structure, even if it includes rule types we don't have implementations
|
|
||||||
// for, or rules with invalid rule values.
|
|
||||||
|
|
||||||
// If an administrator adds or removes extensions which add rules, or
|
|
||||||
// an upgrade affects rule validity, existing rules may become invalid.
|
|
||||||
// When they do, we still want the UI to reflect the ruleset state
|
|
||||||
// accurately and "Edit" + "Save" shouldn't destroy data unless the
|
|
||||||
// user explicitly modifies the ruleset.
|
|
||||||
|
|
||||||
// When we run into rules which are structured correctly but which have
|
|
||||||
// types we don't know about, we replace them with "Unknown Rules". If
|
|
||||||
// we know about the type of a rule but the value doesn't validate, we
|
|
||||||
// replace it with "Invalid Rules". These two rule types don't take any
|
|
||||||
// actions when a card is dropped into the column, but they show the user
|
|
||||||
// what's wrong with the ruleset and can be saved without causing any
|
|
||||||
// collateral damage.
|
|
||||||
|
|
||||||
$rule_map = PhabricatorProjectTriggerRule::getAllTriggerRules();
|
|
||||||
|
|
||||||
// If the stored rule data isn't a list of rules (or we encounter other
|
|
||||||
// fundamental structural problems, below), there isn't much we can do
|
|
||||||
// to try to represent the state.
|
|
||||||
if (!is_array($rule_specifications)) {
|
|
||||||
throw new PhabricatorProjectTriggerCorruptionException(
|
|
||||||
pht(
|
|
||||||
'Trigger ("%s") has a corrupt ruleset: expected a list of '.
|
|
||||||
'rule specifications, found "%s".',
|
|
||||||
$this->getPHID(),
|
|
||||||
phutil_describe_type($rule_specifications)));
|
|
||||||
}
|
|
||||||
|
|
||||||
$trigger_rules = array();
|
|
||||||
foreach ($rule_specifications as $key => $rule) {
|
|
||||||
if (!is_array($rule)) {
|
|
||||||
throw new PhabricatorProjectTriggerCorruptionException(
|
|
||||||
pht(
|
|
||||||
'Trigger ("%s") has a corrupt ruleset: rule (with key "%s") '.
|
|
||||||
'should be a rule specification, but is actually "%s".',
|
|
||||||
$this->getPHID(),
|
|
||||||
$key,
|
|
||||||
phutil_describe_type($rule)));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
PhutilTypeSpec::checkMap(
|
|
||||||
$rule,
|
|
||||||
array(
|
|
||||||
'type' => 'string',
|
|
||||||
'value' => 'wild',
|
|
||||||
));
|
|
||||||
} catch (PhutilTypeCheckException $ex) {
|
|
||||||
throw new PhabricatorProjectTriggerCorruptionException(
|
|
||||||
pht(
|
|
||||||
'Trigger ("%s") has a corrupt ruleset: rule (with key "%s") '.
|
|
||||||
'is not a valid rule specification: %s',
|
|
||||||
$this->getPHID(),
|
|
||||||
$key,
|
|
||||||
$ex->getMessage()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$record = id(new PhabricatorProjectTriggerRuleRecord())
|
|
||||||
->setType(idx($rule, 'type'))
|
|
||||||
->setValue(idx($rule, 'value'));
|
|
||||||
|
|
||||||
if (!isset($rule_map[$record->getType()])) {
|
|
||||||
$rule = new PhabricatorProjectTriggerUnknownRule();
|
|
||||||
} else {
|
|
||||||
$rule = clone $rule_map[$record->getType()];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$rule->setRecord($record);
|
|
||||||
} catch (Exception $ex) {
|
|
||||||
$rule = id(new PhabricatorProjectTriggerInvalidRule())
|
|
||||||
->setRecord($record);
|
|
||||||
}
|
|
||||||
|
|
||||||
$trigger_rules[] = $rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->triggerRules = $trigger_rules;
|
$this->triggerRules = $trigger_rules;
|
||||||
}
|
}
|
||||||
|
@ -170,6 +82,108 @@ final class PhabricatorProjectTrigger
|
||||||
return $this->triggerRules;
|
return $this->triggerRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function newTriggerRulesFromRuleSpecifications(
|
||||||
|
array $list,
|
||||||
|
$allow_invalid) {
|
||||||
|
|
||||||
|
// NOTE: With "$allow_invalid" set, we're trying to preserve the database
|
||||||
|
// state in the rule structure, even if it includes rule types we don't
|
||||||
|
// ha ve implementations for, or rules with invalid rule values.
|
||||||
|
|
||||||
|
// If an administrator adds or removes extensions which add rules, or
|
||||||
|
// an upgrade affects rule validity, existing rules may become invalid.
|
||||||
|
// When they do, we still want the UI to reflect the ruleset state
|
||||||
|
// accurately and "Edit" + "Save" shouldn't destroy data unless the
|
||||||
|
// user explicitly modifies the ruleset.
|
||||||
|
|
||||||
|
// In this mode, when we run into rules which are structured correctly but
|
||||||
|
// which have types we don't know about, we replace them with "Unknown
|
||||||
|
// Rules". If we know about the type of a rule but the value doesn't
|
||||||
|
// validate, we replace it with "Invalid Rules". These two rule types don't
|
||||||
|
// take any actions when a card is dropped into the column, but they show
|
||||||
|
// the user what's wrong with the ruleset and can be saved without causing
|
||||||
|
// any collateral damage.
|
||||||
|
|
||||||
|
$rule_map = PhabricatorProjectTriggerRule::getAllTriggerRules();
|
||||||
|
|
||||||
|
// If the stored rule data isn't a list of rules (or we encounter other
|
||||||
|
// fundamental structural problems, below), there isn't much we can do
|
||||||
|
// to try to represent the state.
|
||||||
|
if (!is_array($list)) {
|
||||||
|
throw new PhabricatorProjectTriggerCorruptionException(
|
||||||
|
pht(
|
||||||
|
'Trigger ruleset is corrupt: expected a list of rule '.
|
||||||
|
'specifications, found "%s".',
|
||||||
|
phutil_describe_type($list)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$trigger_rules = array();
|
||||||
|
foreach ($list as $key => $rule) {
|
||||||
|
if (!is_array($rule)) {
|
||||||
|
throw new PhabricatorProjectTriggerCorruptionException(
|
||||||
|
pht(
|
||||||
|
'Trigger ruleset is corrupt: rule (with key "%s") should be a '.
|
||||||
|
'rule specification, but is actually "%s".',
|
||||||
|
$key,
|
||||||
|
phutil_describe_type($rule)));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PhutilTypeSpec::checkMap(
|
||||||
|
$rule,
|
||||||
|
array(
|
||||||
|
'type' => 'string',
|
||||||
|
'value' => 'wild',
|
||||||
|
));
|
||||||
|
} catch (PhutilTypeCheckException $ex) {
|
||||||
|
throw new PhabricatorProjectTriggerCorruptionException(
|
||||||
|
pht(
|
||||||
|
'Trigger ruleset is corrupt: rule (with key "%s") is not a '.
|
||||||
|
'valid rule specification: %s',
|
||||||
|
$key,
|
||||||
|
$ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = id(new PhabricatorProjectTriggerRuleRecord())
|
||||||
|
->setType(idx($rule, 'type'))
|
||||||
|
->setValue(idx($rule, 'value'));
|
||||||
|
|
||||||
|
if (!isset($rule_map[$record->getType()])) {
|
||||||
|
if (!$allow_invalid) {
|
||||||
|
throw new PhabricatorProjectTriggerCorruptionException(
|
||||||
|
pht(
|
||||||
|
'Trigger ruleset is corrupt: rule type "%s" is unknown.',
|
||||||
|
$record->getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$rule = new PhabricatorProjectTriggerUnknownRule();
|
||||||
|
} else {
|
||||||
|
$rule = clone $rule_map[$record->getType()];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rule->setRecord($record);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
if (!$allow_invalid) {
|
||||||
|
throw new PhabricatorProjectTriggerCorruptionException(
|
||||||
|
pht(
|
||||||
|
'Trigger ruleset is corrupt, rule (of type "%s") does not '.
|
||||||
|
'validate: %s',
|
||||||
|
$record->getType(),
|
||||||
|
$ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$rule = id(new PhabricatorProjectTriggerInvalidRule())
|
||||||
|
->setRecord($record);
|
||||||
|
}
|
||||||
|
|
||||||
|
$trigger_rules[] = $rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $trigger_rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getDropEffects() {
|
public function getDropEffects() {
|
||||||
$effects = array();
|
$effects = array();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectTriggerRulesetTransaction
|
||||||
|
extends PhabricatorProjectTriggerTransactionType {
|
||||||
|
|
||||||
|
const TRANSACTIONTYPE = 'ruleset';
|
||||||
|
|
||||||
|
public function generateOldValue($object) {
|
||||||
|
return $object->getRuleset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyInternalEffects($object, $value) {
|
||||||
|
$object->setRuleset($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
return pht(
|
||||||
|
'%s updated the ruleset for this trigger.',
|
||||||
|
$this->renderAuthor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateTransactions($object, array $xactions) {
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$ruleset = $xaction->getNewValue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
PhabricatorProjectTrigger::newTriggerRulesFromRuleSpecifications(
|
||||||
|
$ruleset,
|
||||||
|
$allow_invalid = false);
|
||||||
|
} catch (PhabricatorProjectTriggerCorruptionException $ex) {
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht(
|
||||||
|
'Ruleset specification is not valid. %s',
|
||||||
|
$ex->getMessage()),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasChangeDetailView() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newChangeDetailView() {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$old = $this->getOldValue();
|
||||||
|
$new = $this->getNewValue();
|
||||||
|
|
||||||
|
$json = new PhutilJSON();
|
||||||
|
$old_json = $json->encodeAsList($old);
|
||||||
|
$new_json = $json->encodeAsList($new);
|
||||||
|
|
||||||
|
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setOldText($old_json)
|
||||||
|
->setNewText($new_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue