1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-24 22:40:55 +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:
epriestley 2016-08-01 13:29:46 -07:00
parent 182a146280
commit 4d68c0ae04
10 changed files with 280 additions and 63 deletions

View file

@ -20,6 +20,21 @@ final class HeraldDifferentialRevisionAdapter
return new DifferentialRevision(); 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() { protected function initializeNewAdapter() {
$this->revision = $this->newObject(); $this->revision = $this->newObject();
} }

View file

@ -27,10 +27,24 @@ final class HeraldCommitAdapter
return new PhabricatorRepositoryCommit(); 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() { protected function initializeNewAdapter() {
$this->commit = $this->newObject(); $this->commit = $this->newObject();
} }
public function setObject($object) {
$this->commit = $object;
return $this;
}
public function getObject() { public function getObject() {
return $this->commit; return $this->commit;
} }

View file

@ -25,6 +25,20 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter {
return 'PhabricatorDiffusionApplication'; 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() { protected function initializeNewAdapter() {
$this->log = new PhabricatorRepositoryPushLog(); $this->log = new PhabricatorRepositoryPushLog();
} }

View file

@ -189,7 +189,6 @@ abstract class HeraldAdapter extends Phobject {
abstract public function getAdapterApplicationClass(); abstract public function getAdapterApplicationClass();
abstract public function getObject(); abstract public function getObject();
/** /**
* Return a new characteristic object for this adapter. * Return a new characteristic object for this adapter.
* *
@ -217,6 +216,23 @@ abstract class HeraldAdapter extends Phobject {
return false; 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() { public function explainValidTriggerObjects() {
return pht('This adapter can not trigger on objects.'); return pht('This adapter can not trigger on objects.');
} }

View file

@ -2,58 +2,42 @@
final class HeraldTestConsoleController extends HeraldController { 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) { public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer(); $viewer = $request->getViewer();
$object_name = trim($request->getStr('object_name'));
$e_name = true; $response = $this->loadTestObject($request);
$errors = array(); if ($response) {
if ($request->isFormPost()) { return $response;
if (!$object_name) {
$e_name = pht('Required');
$errors[] = pht('An object name is required.');
} }
if (!$errors) { $response = $this->loadAdapter($request);
$object = id(new PhabricatorObjectQuery()) if ($response) {
->setViewer($viewer) return $response;
->withNames(array($object_name))
->executeOne();
if (!$object) {
$e_name = pht('Invalid');
$errors[] = pht('No object exists with that name.');
} }
if (!$errors) { $object = $this->getTestObject();
$adapter = $this->getTestAdapter();
// 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); $adapter->setIsNewObject(false);
@ -77,6 +61,36 @@ final class HeraldTestConsoleController extends HeraldController {
return id(new AphrontRedirectResponse()) return id(new AphrontRedirectResponse())
->setURI('/herald/transcript/'.$xscript->getID().'/'); ->setURI('/herald/transcript/'.$xscript->getID().'/');
} }
private function loadTestObject(AphrontRequest $request) {
$viewer = $this->getViewer();
$e_name = true;
$v_name = null;
$errors = array();
if ($request->isFormPost()) {
$v_name = trim($request->getStr('object_name'));
if (!$v_name) {
$e_name = pht('Required');
$errors[] = pht('An object name is required.');
}
if (!$errors) {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($v_name))
->executeOne();
if (!$object) {
$e_name = pht('Invalid');
$errors[] = pht('No object exists with that name.');
}
}
if (!$errors) {
$this->setTestObject($object);
return null;
} }
} }
@ -92,11 +106,89 @@ final class HeraldTestConsoleController extends HeraldController {
->setLabel(pht('Object Name')) ->setLabel(pht('Object Name'))
->setName('object_name') ->setName('object_name')
->setError($e_name) ->setError($e_name)
->setValue($object_name)) ->setValue($v_name))
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) 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()) $box = id(new PHUIObjectBoxView())
->setFormErrors($errors) ->setFormErrors($errors)
->setForm($form); ->setForm($form);
@ -118,11 +210,7 @@ final class HeraldTestConsoleController extends HeraldController {
return $this->newPage() return $this->newPage()
->setTitle($title) ->setTitle($title)
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->appendChild( ->appendChild($view);
array(
$view,
));
} }
} }

View file

@ -16,6 +16,15 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
return pht('React to tasks being created or updated.'); 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() { protected function initializeNewAdapter() {
$this->task = $this->newObject(); $this->task = $this->newObject();
} }
@ -46,10 +55,16 @@ final class HeraldManiphestTaskAdapter extends HeraldAdapter {
$this->task = $task; $this->task = $task;
return $this; return $this;
} }
public function getTask() { public function getTask() {
return $this->task; return $this->task;
} }
public function setObject($object) {
$this->task = $object;
return $this;
}
public function getObject() { public function getObject() {
return $this->task; return $this->task;
} }

View file

@ -21,6 +21,17 @@ final class PhabricatorMailOutboundMailHeraldAdapter
return new PhabricatorMetaMTAMail(); 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() { public function getObject() {
return $this->mail; return $this->mail;
} }

View file

@ -20,6 +20,20 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
return new PholioMock(); 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() { public function getObject() {
return $this->mock; return $this->mock;
} }
@ -28,6 +42,7 @@ final class HeraldPholioMockAdapter extends HeraldAdapter {
$this->mock = $mock; $this->mock = $mock;
return $this; return $this;
} }
public function getMock() { public function getMock() {
return $this->mock; return $this->mock;
} }

View file

@ -20,6 +20,20 @@ final class PhrictionDocumentHeraldAdapter extends HeraldAdapter {
return new PhrictionDocument(); 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() { public function getObject() {
return $this->document; return $this->document;
} }

View file

@ -20,6 +20,21 @@ final class HeraldPonderQuestionAdapter extends HeraldAdapter {
$this->question = $this->newObject(); $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() { public function supportsApplicationEmail() {
return true; return true;
} }