mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
Make Herald test workflow modular and more clear
Summary: Fixes T9719. Currently, the Herald "Test Console" has a big `instanceof` thing, so new adapters (like a Calendar adapter, or third-party adapters) aren't available automatically. Instead, do a standard modular thing: load the available adapters, ask which ones can test the object the user selected, then let the user pick which one they want to move forward with. Additionally, it isn't very clear that you can't test "commit hook" rules because they rely on push state which we don't really have a good way to simulate. When the user picks a commit, we now show them the "Hook" events, but the options are disabled and explain why they can not be selected. Test Plan: - Ran test rules for revisions, commits, mocks, tasks, wiki documents, questions, and outbound mail. - Plugged in a commit, got a more-helpful choice screen explaining why you do a test run of hook rules. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9719 Differential Revision: https://secure.phabricator.com/D16360
This commit is contained in:
parent
182a146280
commit
4d68c0ae04
10 changed files with 280 additions and 63 deletions
|
@ -20,6 +20,21 @@ final class HeraldDifferentialRevisionAdapter
|
|||
return new DifferentialRevision();
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof DifferentialRevision);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Test rules which run when a revision is created or updated.');
|
||||
}
|
||||
|
||||
public function newTestAdapter($object) {
|
||||
return self::newLegacyAdapter(
|
||||
$object,
|
||||
$object->loadActiveDiff());
|
||||
}
|
||||
|
||||
protected function initializeNewAdapter() {
|
||||
$this->revision = $this->newObject();
|
||||
}
|
||||
|
|
|
@ -27,10 +27,24 @@ final class HeraldCommitAdapter
|
|||
return new PhabricatorRepositoryCommit();
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof PhabricatorRepositoryCommit);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Test rules which run after a commit is discovered and imported.');
|
||||
}
|
||||
|
||||
protected function initializeNewAdapter() {
|
||||
$this->commit = $this->newObject();
|
||||
}
|
||||
|
||||
public function setObject($object) {
|
||||
$this->commit = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
return $this->commit;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,20 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter {
|
|||
return 'PhabricatorDiffusionApplication';
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof PhabricatorRepositoryCommit);
|
||||
}
|
||||
|
||||
public function canCreateTestAdapterForObject($object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Commit hook events depend on repository state which is only available '.
|
||||
'at push time, and can not be run in test mode.');
|
||||
}
|
||||
|
||||
protected function initializeNewAdapter() {
|
||||
$this->log = new PhabricatorRepositoryPushLog();
|
||||
}
|
||||
|
|
|
@ -189,7 +189,6 @@ abstract class HeraldAdapter extends Phobject {
|
|||
abstract public function getAdapterApplicationClass();
|
||||
abstract public function getObject();
|
||||
|
||||
|
||||
/**
|
||||
* Return a new characteristic object for this adapter.
|
||||
*
|
||||
|
@ -217,6 +216,23 @@ abstract class HeraldAdapter extends Phobject {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canCreateTestAdapterForObject($object) {
|
||||
return $this->isTestAdapterForObject($object);
|
||||
}
|
||||
|
||||
public function newTestAdapter($object) {
|
||||
return id(clone $this)
|
||||
->setObject($object);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function explainValidTriggerObjects() {
|
||||
return pht('This adapter can not trigger on objects.');
|
||||
}
|
||||
|
|
|
@ -2,14 +2,76 @@
|
|||
|
||||
final class HeraldTestConsoleController extends HeraldController {
|
||||
|
||||
private $testObject;
|
||||
private $testAdapter;
|
||||
|
||||
public function setTestObject($test_object) {
|
||||
$this->testObject = $test_object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTestObject() {
|
||||
return $this->testObject;
|
||||
}
|
||||
|
||||
public function setTestAdapter(HeraldAdapter $test_adapter) {
|
||||
$this->testAdapter = $test_adapter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTestAdapter() {
|
||||
return $this->testAdapter;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$object_name = trim($request->getStr('object_name'));
|
||||
|
||||
$response = $this->loadTestObject($request);
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response = $this->loadAdapter($request);
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$object = $this->getTestObject();
|
||||
$adapter = $this->getTestAdapter();
|
||||
|
||||
$adapter->setIsNewObject(false);
|
||||
|
||||
$rules = id(new HeraldRuleQuery())
|
||||
->setViewer($viewer)
|
||||
->withContentTypes(array($adapter->getAdapterContentType()))
|
||||
->withDisabled(false)
|
||||
->needConditionsAndActions(true)
|
||||
->needAppliedToPHIDs(array($object->getPHID()))
|
||||
->needValidateAuthors(true)
|
||||
->execute();
|
||||
|
||||
$engine = id(new HeraldEngine())
|
||||
->setDryRun(true);
|
||||
|
||||
$effects = $engine->applyRules($rules, $adapter);
|
||||
$engine->applyEffects($effects, $adapter, $rules);
|
||||
|
||||
$xscript = $engine->getTranscript();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/herald/transcript/'.$xscript->getID().'/');
|
||||
}
|
||||
|
||||
private function loadTestObject(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$e_name = true;
|
||||
$v_name = null;
|
||||
$errors = array();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if (!$object_name) {
|
||||
$v_name = trim($request->getStr('object_name'));
|
||||
if (!$v_name) {
|
||||
$e_name = pht('Required');
|
||||
$errors[] = pht('An object name is required.');
|
||||
}
|
||||
|
@ -17,66 +79,18 @@ final class HeraldTestConsoleController extends HeraldController {
|
|||
if (!$errors) {
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($object_name))
|
||||
->withNames(array($v_name))
|
||||
->executeOne();
|
||||
|
||||
if (!$object) {
|
||||
$e_name = pht('Invalid');
|
||||
$errors[] = pht('No object exists with that name.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
|
||||
// TODO: Let the adapters claim objects instead.
|
||||
|
||||
if ($object instanceof DifferentialRevision) {
|
||||
$adapter = HeraldDifferentialRevisionAdapter::newLegacyAdapter(
|
||||
$object,
|
||||
$object->loadActiveDiff());
|
||||
} else if ($object instanceof PhabricatorRepositoryCommit) {
|
||||
$adapter = id(new HeraldCommitAdapter())
|
||||
->setCommit($object);
|
||||
} else if ($object instanceof ManiphestTask) {
|
||||
$adapter = id(new HeraldManiphestTaskAdapter())
|
||||
->setTask($object);
|
||||
} else if ($object instanceof PholioMock) {
|
||||
$adapter = id(new HeraldPholioMockAdapter())
|
||||
->setMock($object);
|
||||
} else if ($object instanceof PhrictionDocument) {
|
||||
$adapter = id(new PhrictionDocumentHeraldAdapter())
|
||||
->setDocument($object);
|
||||
} else if ($object instanceof PonderQuestion) {
|
||||
$adapter = id(new HeraldPonderQuestionAdapter())
|
||||
->setQuestion($object);
|
||||
} else if ($object instanceof PhabricatorMetaMTAMail) {
|
||||
$adapter = id(new PhabricatorMailOutboundMailHeraldAdapter())
|
||||
->setObject($object);
|
||||
} else {
|
||||
throw new Exception(pht('Can not build adapter for object!'));
|
||||
}
|
||||
|
||||
$adapter->setIsNewObject(false);
|
||||
|
||||
$rules = id(new HeraldRuleQuery())
|
||||
->setViewer($viewer)
|
||||
->withContentTypes(array($adapter->getAdapterContentType()))
|
||||
->withDisabled(false)
|
||||
->needConditionsAndActions(true)
|
||||
->needAppliedToPHIDs(array($object->getPHID()))
|
||||
->needValidateAuthors(true)
|
||||
->execute();
|
||||
|
||||
$engine = id(new HeraldEngine())
|
||||
->setDryRun(true);
|
||||
|
||||
$effects = $engine->applyRules($rules, $adapter);
|
||||
$engine->applyEffects($effects, $adapter, $rules);
|
||||
|
||||
$xscript = $engine->getTranscript();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/herald/transcript/'.$xscript->getID().'/');
|
||||
}
|
||||
if (!$errors) {
|
||||
$this->setTestObject($object);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,11 +106,89 @@ final class HeraldTestConsoleController extends HeraldController {
|
|||
->setLabel(pht('Object Name'))
|
||||
->setName('object_name')
|
||||
->setError($e_name)
|
||||
->setValue($object_name))
|
||||
->setValue($v_name))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Test Rules')));
|
||||
->setValue(pht('Continue')));
|
||||
|
||||
return $this->buildTestConsoleResponse($form, $errors);
|
||||
}
|
||||
|
||||
private function loadAdapter(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$object = $this->getTestObject();
|
||||
|
||||
$adapter_key = $request->getStr('adapter');
|
||||
|
||||
$adapters = HeraldAdapter::getAllAdapters();
|
||||
|
||||
$can_select = array();
|
||||
$display_adapters = array();
|
||||
foreach ($adapters as $key => $adapter) {
|
||||
if (!$adapter->isTestAdapterForObject($object)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$adapter->isAvailableToUser($viewer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display_adapters[$key] = $adapter;
|
||||
|
||||
if ($adapter->canCreateTestAdapterForObject($object)) {
|
||||
$can_select[$key] = $adapter;
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->isFormPost() && $adapter_key) {
|
||||
if (isset($can_select[$adapter_key])) {
|
||||
$adapter = $can_select[$adapter_key]->newTestAdapter($object);
|
||||
$this->setTestAdapter($adapter);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->addHiddenInput('object_name', $request->getStr('object_name'))
|
||||
->setViewer($viewer);
|
||||
|
||||
$cancel_uri = $this->getApplicationURI();
|
||||
|
||||
if (!$display_adapters) {
|
||||
$form
|
||||
->appendRemarkupInstructions(
|
||||
pht('//There are no available Herald events for this object.//'))
|
||||
->appendControl(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton($cancel_uri));
|
||||
} else {
|
||||
$adapter_control = id(new AphrontFormRadioButtonControl())
|
||||
->setLabel(pht('Event'))
|
||||
->setName('adapter')
|
||||
->setValue(head_key($can_select));
|
||||
|
||||
foreach ($display_adapters as $adapter_key => $adapter) {
|
||||
$is_disabled = empty($can_select[$adapter_key]);
|
||||
|
||||
$adapter_control->addButton(
|
||||
$adapter_key,
|
||||
$adapter->getAdapterContentName(),
|
||||
$adapter->getAdapterTestDescription(),
|
||||
null,
|
||||
$is_disabled);
|
||||
}
|
||||
|
||||
$form
|
||||
->appendControl($adapter_control)
|
||||
->appendControl(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Run Test')));
|
||||
}
|
||||
|
||||
return $this->buildTestConsoleResponse($form, array());
|
||||
}
|
||||
|
||||
private function buildTestConsoleResponse($form, array $errors) {
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setFormErrors($errors)
|
||||
->setForm($form);
|
||||
|
@ -118,11 +210,7 @@ final class HeraldTestConsoleController extends HeraldController {
|
|||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$view,
|
||||
));
|
||||
|
||||
->appendChild($view);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,15 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
|||
return pht('React to tasks being created or updated.');
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof ManiphestTask);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Test rules which run when a task is created or updated.');
|
||||
}
|
||||
|
||||
protected function initializeNewAdapter() {
|
||||
$this->task = $this->newObject();
|
||||
}
|
||||
|
@ -46,10 +55,16 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
|
|||
$this->task = $task;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTask() {
|
||||
return $this->task;
|
||||
}
|
||||
|
||||
public function setObject($object) {
|
||||
$this->task = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
return $this->task;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,17 @@ final class PhabricatorMailOutboundMailHeraldAdapter
|
|||
return new PhabricatorMetaMTAMail();
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof PhabricatorMetaMTAMail);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Test rules which run when outbound mail is being prepared for '.
|
||||
'delivery.');
|
||||
}
|
||||
|
||||
|
||||
public function getObject() {
|
||||
return $this->mail;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,20 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
|
|||
return new PholioMock();
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof PholioMock);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Test rules which run when a mock is created or updated.');
|
||||
}
|
||||
|
||||
public function setObject($object) {
|
||||
$this->mock = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
return $this->mock;
|
||||
}
|
||||
|
@ -28,6 +42,7 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
|
|||
$this->mock = $mock;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMock() {
|
||||
return $this->mock;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,20 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter {
|
|||
return new PhrictionDocument();
|
||||
}
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof PhrictionDocument);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Test rules which run when a wiki document is created or updated.');
|
||||
}
|
||||
|
||||
public function setObject($object) {
|
||||
$this->document = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
return $this->document;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,21 @@ final class HeraldPonderQuestionAdapter extends HeraldAdapter {
|
|||
$this->question = $this->newObject();
|
||||
}
|
||||
|
||||
|
||||
public function isTestAdapterForObject($object) {
|
||||
return ($object instanceof PonderQuestion);
|
||||
}
|
||||
|
||||
public function getAdapterTestDescription() {
|
||||
return pht(
|
||||
'Test rules which run when a question is created or updated.');
|
||||
}
|
||||
|
||||
public function setObject($object) {
|
||||
$this->question = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function supportsApplicationEmail() {
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue