@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at recaptime-dev/main 321 lines 9.0 kB view raw
1<?php 2 3final class HeraldTestConsoleController extends HeraldController { 4 5 private $testObject; 6 private $testAdapter; 7 8 public function setTestObject($test_object) { 9 $this->testObject = $test_object; 10 return $this; 11 } 12 13 public function getTestObject() { 14 return $this->testObject; 15 } 16 17 public function setTestAdapter(HeraldAdapter $test_adapter) { 18 $this->testAdapter = $test_adapter; 19 return $this; 20 } 21 22 public function getTestAdapter() { 23 return $this->testAdapter; 24 } 25 26 public function handleRequest(AphrontRequest $request) { 27 $viewer = $request->getViewer(); 28 29 $response = $this->loadTestObject($request); 30 if ($response) { 31 return $response; 32 } 33 34 $response = $this->loadAdapter($request); 35 if ($response) { 36 return $response; 37 } 38 39 $object = $this->getTestObject(); 40 $adapter = $this->getTestAdapter(); 41 $source = $this->newContentSource($object); 42 43 $adapter 44 ->setContentSource($source) 45 ->setIsNewObject(false) 46 ->setActingAsPHID($viewer->getPHID()) 47 ->setViewer($viewer); 48 49 $applied_xactions = $this->loadAppliedTransactions($object); 50 if ($applied_xactions !== null) { 51 $adapter->setAppliedTransactions($applied_xactions); 52 } 53 54 $rules = id(new HeraldRuleQuery()) 55 ->setViewer($viewer) 56 ->withContentTypes(array($adapter->getAdapterContentType())) 57 ->withDisabled(false) 58 ->needConditionsAndActions(true) 59 ->needAppliedToPHIDs(array($object->getPHID())) 60 ->needValidateAuthors(true) 61 ->execute(); 62 63 $engine = id(new HeraldEngine()) 64 ->setDryRun(true); 65 66 $effects = $engine->applyRules($rules, $adapter); 67 $engine->applyEffects($effects, $adapter, $rules); 68 69 $xscript = $engine->getTranscript(); 70 71 return id(new AphrontRedirectResponse()) 72 ->setURI('/herald/transcript/'.$xscript->getID().'/'); 73 } 74 75 private function loadTestObject(AphrontRequest $request) { 76 $viewer = $this->getViewer(); 77 78 $e_name = true; 79 $v_name = null; 80 $errors = array(); 81 82 if ($request->isFormPost()) { 83 $v_name = trim($request->getStr('object_name')); 84 if (!$v_name) { 85 $e_name = pht('Required'); 86 $errors[] = pht('An object name is required.'); 87 } 88 89 if (!$errors) { 90 $object = id(new PhabricatorObjectQuery()) 91 ->setViewer($viewer) 92 ->withNames(array($v_name)) 93 ->executeOne(); 94 95 if (!$object) { 96 $e_name = pht('Invalid'); 97 $errors[] = pht('No object exists with that name.'); 98 } 99 } 100 101 if (!$errors) { 102 $this->setTestObject($object); 103 return null; 104 } 105 } 106 107 $form = id(new AphrontFormView()) 108 ->setUser($viewer) 109 ->appendRemarkupInstructions( 110 pht( 111 'Enter an object to test rules for, like a Diffusion commit (e.g., '. 112 '`rX123`) or a Differential revision (e.g., `D123`). You will be '. 113 'shown the results of a dry run on the object.')) 114 ->appendChild( 115 id(new AphrontFormTextControl()) 116 ->setLabel(pht('Object Name')) 117 ->setName('object_name') 118 ->setError($e_name) 119 ->setValue($v_name)) 120 ->appendChild( 121 id(new AphrontFormSubmitControl()) 122 ->setValue(pht('Continue'))); 123 124 return $this->buildTestConsoleResponse($form, $errors); 125 } 126 127 private function loadAdapter(AphrontRequest $request) { 128 $viewer = $this->getViewer(); 129 $object = $this->getTestObject(); 130 131 $adapter_key = $request->getStr('adapter'); 132 133 $adapters = HeraldAdapter::getAllAdapters(); 134 135 $can_select = array(); 136 $display_adapters = array(); 137 foreach ($adapters as $key => $adapter) { 138 if (!$adapter->isTestAdapterForObject($object)) { 139 continue; 140 } 141 142 if (!$adapter->isAvailableToUser($viewer)) { 143 continue; 144 } 145 146 $display_adapters[$key] = $adapter; 147 148 if ($adapter->canCreateTestAdapterForObject($object)) { 149 $can_select[$key] = $adapter; 150 } 151 } 152 153 if ($request->isFormPost() && $adapter_key) { 154 if (isset($can_select[$adapter_key])) { 155 $adapter = $can_select[$adapter_key]->newTestAdapter( 156 $viewer, 157 $object); 158 $this->setTestAdapter($adapter); 159 return null; 160 } 161 } 162 163 $form = id(new AphrontFormView()) 164 ->addHiddenInput('object_name', $request->getStr('object_name')) 165 ->setViewer($viewer); 166 167 $cancel_uri = $this->getApplicationURI(); 168 169 if (!$display_adapters) { 170 $form 171 ->appendRemarkupInstructions( 172 pht('//There are no available Herald events for this object.//')) 173 ->appendControl( 174 id(new AphrontFormSubmitControl()) 175 ->addCancelButton($cancel_uri)); 176 } else { 177 $adapter_control = id(new AphrontFormRadioButtonControl()) 178 ->setLabel(pht('Event')) 179 ->setName('adapter') 180 ->setValue(head_key($can_select)); 181 182 foreach ($display_adapters as $adapter_key => $adapter) { 183 $is_disabled = empty($can_select[$adapter_key]); 184 185 $adapter_control->addButton( 186 $adapter_key, 187 $adapter->getAdapterContentName(), 188 $adapter->getAdapterTestDescription(), 189 null, 190 $is_disabled); 191 } 192 193 $form 194 ->appendControl($adapter_control) 195 ->appendControl( 196 id(new AphrontFormSubmitControl()) 197 ->setValue(pht('Run Test'))); 198 } 199 200 return $this->buildTestConsoleResponse($form, array()); 201 } 202 203 private function buildTestConsoleResponse($form, array $errors) { 204 $box = id(new PHUIObjectBoxView()) 205 ->setFormErrors($errors) 206 ->setForm($form); 207 208 $crumbs = id($this->buildApplicationCrumbs()) 209 ->addTextCrumb(pht('Test Console')) 210 ->setBorder(true); 211 212 $title = pht('Test Console'); 213 214 $header = id(new PHUIHeaderView()) 215 ->setHeader($title) 216 ->setHeaderIcon('fa-desktop'); 217 218 $view = id(new PHUITwoColumnView()) 219 ->setHeader($header) 220 ->setFooter($box); 221 222 return $this->newPage() 223 ->setTitle($title) 224 ->setCrumbs($crumbs) 225 ->appendChild($view); 226 } 227 228 private function newContentSource($object) { 229 $viewer = $this->getViewer(); 230 231 // Try using the content source associated with the most recent transaction 232 // on the object. 233 234 $query = PhabricatorApplicationTransactionQuery::newQueryForObject($object); 235 236 $xaction = $query 237 ->setViewer($viewer) 238 ->withObjectPHIDs(array($object->getPHID())) 239 ->setLimit(1) 240 ->setOrder('newest') 241 ->executeOne(); 242 if ($xaction) { 243 return $xaction->getContentSource(); 244 } 245 246 // If we couldn't find a transaction (which should be rare), fall back to 247 // building a new content source from the test console request itself. 248 249 $request = $this->getRequest(); 250 return PhabricatorContentSource::newFromRequest($request); 251 } 252 253 private function loadAppliedTransactions($object) { 254 $viewer = $this->getViewer(); 255 256 if (!($object instanceof PhabricatorApplicationTransactionInterface)) { 257 return null; 258 } 259 260 $query = PhabricatorApplicationTransactionQuery::newQueryForObject( 261 $object); 262 263 $query 264 ->withObjectPHIDs(array($object->getPHID())) 265 ->setViewer($viewer); 266 267 $xactions = new PhabricatorQueryIterator($query); 268 269 $applied = array(); 270 271 $recent_id = null; 272 $hard_limit = 1000; 273 foreach ($xactions as $xaction) { 274 275 // If this transaction has Herald transcript metadata, it was applied by 276 // Herald. Exclude it from the list because the Herald rule engine always 277 // runs before Herald transactions apply, so there's no way that real 278 // rules would have seen this transaction. 279 $transcript_id = $xaction->getMetadataValue('herald:transcriptID'); 280 if ($transcript_id !== null) { 281 continue; 282 } 283 284 $group_id = $xaction->getTransactionGroupID(); 285 286 // If this is the first transaction, save the group ID: we want to 287 // select all transactions in the same group. 288 if (!$applied) { 289 $recent_id = $group_id; 290 if ($recent_id === null) { 291 // If the first transaction has no group ID, it is likely an older 292 // transaction from before the introduction of group IDs. In this 293 // case, select only the most recent transaction and bail out. 294 $applied[] = $xaction; 295 break; 296 } 297 } 298 299 // If this transaction is from a different transaction group, we've 300 // found all the transactions applied in the most recent group. 301 if ($group_id !== $recent_id) { 302 break; 303 } 304 305 $applied[] = $xaction; 306 307 if (count($applied) > $hard_limit) { 308 throw new Exception( 309 pht( 310 'This object ("%s") has more than %s transactions in its most '. 311 'recent transaction group; this is too many.', 312 $object->getPHID(), 313 new PhutilNumber($hard_limit))); 314 } 315 } 316 317 return $applied; 318 319 } 320 321}