diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -20,6 +20,21 @@ 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(); } diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -27,10 +27,24 @@ 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; } diff --git a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php --- a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php @@ -25,6 +25,20 @@ 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(); } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -189,7 +189,6 @@ abstract public function getAdapterApplicationClass(); abstract public function getObject(); - /** * Return a new characteristic object for this adapter. * @@ -217,6 +216,23 @@ 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.'); } diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -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 @@ 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 @@ ->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 @@ return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); - + ->appendChild($view); } } diff --git a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php --- a/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php +++ b/src/applications/maniphest/herald/HeraldManiphestTaskAdapter.php @@ -16,6 +16,15 @@ 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 @@ $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; } diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php --- a/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php +++ b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php @@ -21,6 +21,17 @@ 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; } diff --git a/src/applications/pholio/herald/HeraldPholioMockAdapter.php b/src/applications/pholio/herald/HeraldPholioMockAdapter.php --- a/src/applications/pholio/herald/HeraldPholioMockAdapter.php +++ b/src/applications/pholio/herald/HeraldPholioMockAdapter.php @@ -20,6 +20,20 @@ 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 @@ $this->mock = $mock; return $this; } + public function getMock() { return $this->mock; } diff --git a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php --- a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php +++ b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php @@ -20,6 +20,20 @@ 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; } diff --git a/src/applications/ponder/herald/HeraldPonderQuestionAdapter.php b/src/applications/ponder/herald/HeraldPonderQuestionAdapter.php --- a/src/applications/ponder/herald/HeraldPonderQuestionAdapter.php +++ b/src/applications/ponder/herald/HeraldPonderQuestionAdapter.php @@ -20,6 +20,21 @@ $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; }