Path: blob/master/src/applications/herald/controller/HeraldTestConsoleController.php
12262 views
<?php12final class HeraldTestConsoleController extends HeraldController {34private $testObject;5private $testAdapter;67public function setTestObject($test_object) {8$this->testObject = $test_object;9return $this;10}1112public function getTestObject() {13return $this->testObject;14}1516public function setTestAdapter(HeraldAdapter $test_adapter) {17$this->testAdapter = $test_adapter;18return $this;19}2021public function getTestAdapter() {22return $this->testAdapter;23}2425public function handleRequest(AphrontRequest $request) {26$viewer = $request->getViewer();2728$response = $this->loadTestObject($request);29if ($response) {30return $response;31}3233$response = $this->loadAdapter($request);34if ($response) {35return $response;36}3738$object = $this->getTestObject();39$adapter = $this->getTestAdapter();40$source = $this->newContentSource($object);4142$adapter43->setContentSource($source)44->setIsNewObject(false)45->setActingAsPHID($viewer->getPHID())46->setViewer($viewer);4748$applied_xactions = $this->loadAppliedTransactions($object);49if ($applied_xactions !== null) {50$adapter->setAppliedTransactions($applied_xactions);51}5253$rules = id(new HeraldRuleQuery())54->setViewer($viewer)55->withContentTypes(array($adapter->getAdapterContentType()))56->withDisabled(false)57->needConditionsAndActions(true)58->needAppliedToPHIDs(array($object->getPHID()))59->needValidateAuthors(true)60->execute();6162$engine = id(new HeraldEngine())63->setDryRun(true);6465$effects = $engine->applyRules($rules, $adapter);66$engine->applyEffects($effects, $adapter, $rules);6768$xscript = $engine->getTranscript();6970return id(new AphrontRedirectResponse())71->setURI('/herald/transcript/'.$xscript->getID().'/');72}7374private function loadTestObject(AphrontRequest $request) {75$viewer = $this->getViewer();7677$e_name = true;78$v_name = null;79$errors = array();8081if ($request->isFormPost()) {82$v_name = trim($request->getStr('object_name'));83if (!$v_name) {84$e_name = pht('Required');85$errors[] = pht('An object name is required.');86}8788if (!$errors) {89$object = id(new PhabricatorObjectQuery())90->setViewer($viewer)91->withNames(array($v_name))92->executeOne();9394if (!$object) {95$e_name = pht('Invalid');96$errors[] = pht('No object exists with that name.');97}98}99100if (!$errors) {101$this->setTestObject($object);102return null;103}104}105106$form = id(new AphrontFormView())107->setUser($viewer)108->appendRemarkupInstructions(109pht(110'Enter an object to test rules for, like a Diffusion commit (e.g., '.111'`rX123`) or a Differential revision (e.g., `D123`). You will be '.112'shown the results of a dry run on the object.'))113->appendChild(114id(new AphrontFormTextControl())115->setLabel(pht('Object Name'))116->setName('object_name')117->setError($e_name)118->setValue($v_name))119->appendChild(120id(new AphrontFormSubmitControl())121->setValue(pht('Continue')));122123return $this->buildTestConsoleResponse($form, $errors);124}125126private function loadAdapter(AphrontRequest $request) {127$viewer = $this->getViewer();128$object = $this->getTestObject();129130$adapter_key = $request->getStr('adapter');131132$adapters = HeraldAdapter::getAllAdapters();133134$can_select = array();135$display_adapters = array();136foreach ($adapters as $key => $adapter) {137if (!$adapter->isTestAdapterForObject($object)) {138continue;139}140141if (!$adapter->isAvailableToUser($viewer)) {142continue;143}144145$display_adapters[$key] = $adapter;146147if ($adapter->canCreateTestAdapterForObject($object)) {148$can_select[$key] = $adapter;149}150}151152if ($request->isFormPost() && $adapter_key) {153if (isset($can_select[$adapter_key])) {154$adapter = $can_select[$adapter_key]->newTestAdapter(155$viewer,156$object);157$this->setTestAdapter($adapter);158return null;159}160}161162$form = id(new AphrontFormView())163->addHiddenInput('object_name', $request->getStr('object_name'))164->setViewer($viewer);165166$cancel_uri = $this->getApplicationURI();167168if (!$display_adapters) {169$form170->appendRemarkupInstructions(171pht('//There are no available Herald events for this object.//'))172->appendControl(173id(new AphrontFormSubmitControl())174->addCancelButton($cancel_uri));175} else {176$adapter_control = id(new AphrontFormRadioButtonControl())177->setLabel(pht('Event'))178->setName('adapter')179->setValue(head_key($can_select));180181foreach ($display_adapters as $adapter_key => $adapter) {182$is_disabled = empty($can_select[$adapter_key]);183184$adapter_control->addButton(185$adapter_key,186$adapter->getAdapterContentName(),187$adapter->getAdapterTestDescription(),188null,189$is_disabled);190}191192$form193->appendControl($adapter_control)194->appendControl(195id(new AphrontFormSubmitControl())196->setValue(pht('Run Test')));197}198199return $this->buildTestConsoleResponse($form, array());200}201202private function buildTestConsoleResponse($form, array $errors) {203$box = id(new PHUIObjectBoxView())204->setFormErrors($errors)205->setForm($form);206207$crumbs = id($this->buildApplicationCrumbs())208->addTextCrumb(pht('Test Console'))209->setBorder(true);210211$title = pht('Test Console');212213$header = id(new PHUIHeaderView())214->setHeader($title)215->setHeaderIcon('fa-desktop');216217$view = id(new PHUITwoColumnView())218->setHeader($header)219->setFooter($box);220221return $this->newPage()222->setTitle($title)223->setCrumbs($crumbs)224->appendChild($view);225}226227private function newContentSource($object) {228$viewer = $this->getViewer();229230// Try using the content source associated with the most recent transaction231// on the object.232233$query = PhabricatorApplicationTransactionQuery::newQueryForObject($object);234235$xaction = $query236->setViewer($viewer)237->withObjectPHIDs(array($object->getPHID()))238->setLimit(1)239->setOrder('newest')240->executeOne();241if ($xaction) {242return $xaction->getContentSource();243}244245// If we couldn't find a transaction (which should be rare), fall back to246// building a new content source from the test console request itself.247248$request = $this->getRequest();249return PhabricatorContentSource::newFromRequest($request);250}251252private function loadAppliedTransactions($object) {253$viewer = $this->getViewer();254255if (!($object instanceof PhabricatorApplicationTransactionInterface)) {256return null;257}258259$query = PhabricatorApplicationTransactionQuery::newQueryForObject(260$object);261262$query263->withObjectPHIDs(array($object->getPHID()))264->setViewer($viewer);265266$xactions = new PhabricatorQueryIterator($query);267268$applied = array();269270$recent_id = null;271$hard_limit = 1000;272foreach ($xactions as $xaction) {273274// If this transaction has Herald transcript metadata, it was applied by275// Herald. Exclude it from the list because the Herald rule engine always276// runs before Herald transactions apply, so there's no way that real277// rules would have seen this transaction.278$transcript_id = $xaction->getMetadataValue('herald:transcriptID');279if ($transcript_id !== null) {280continue;281}282283$group_id = $xaction->getTransactionGroupID();284285// If this is the first transaction, save the group ID: we want to286// select all transactions in the same group.287if (!$applied) {288$recent_id = $group_id;289if ($recent_id === null) {290// If the first transaction has no group ID, it is likely an older291// transaction from before the introduction of group IDs. In this292// case, select only the most recent transaction and bail out.293$applied[] = $xaction;294break;295}296}297298// If this transaction is from a different transaction group, we've299// found all the transactions applied in the most recent group.300if ($group_id !== $recent_id) {301break;302}303304$applied[] = $xaction;305306if (count($applied) > $hard_limit) {307throw new Exception(308pht(309'This object ("%s") has more than %s transactions in its most '.310'recent transaction group; this is too many.',311$object->getPHID(),312new PhutilNumber($hard_limit)));313}314}315316return $applied;317318}319320}321322323