Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/herald/controller/HeraldTestConsoleController.php
12262 views
1
<?php
2
3
final 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
}
322
323