Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
12256 views
1
<?php
2
3
final class HarbormasterBuildViewController
4
extends HarbormasterController {
5
6
public function shouldAllowPublic() {
7
return true;
8
}
9
10
public function handleRequest(AphrontRequest $request) {
11
$request = $this->getRequest();
12
$viewer = $request->getUser();
13
14
$id = $request->getURIData('id');
15
16
$build = id(new HarbormasterBuildQuery())
17
->setViewer($viewer)
18
->withIDs(array($id))
19
->executeOne();
20
if (!$build) {
21
return new Aphront404Response();
22
}
23
24
require_celerity_resource('harbormaster-css');
25
26
$title = pht('Build %d', $id);
27
$warnings = array();
28
29
$page_header = id(new PHUIHeaderView())
30
->setHeader($title)
31
->setUser($viewer)
32
->setPolicyObject($build)
33
->setHeaderIcon('fa-cubes');
34
35
$status = $build->getBuildPendingStatusObject();
36
37
$status_icon = $status->getIconIcon();
38
$status_color = $status->getIconColor();
39
$status_name = $status->getName();
40
41
$page_header->setStatus($status_icon, $status_color, $status_name);
42
43
$max_generation = (int)$build->getBuildGeneration();
44
if ($max_generation === 0) {
45
$min_generation = 0;
46
} else {
47
$min_generation = 1;
48
}
49
50
if ($build->isRestarting()) {
51
$max_generation = $max_generation + 1;
52
}
53
54
$generation = $request->getURIData('generation');
55
if ($generation === null) {
56
$generation = $max_generation;
57
} else {
58
$generation = (int)$generation;
59
}
60
61
if ($generation < $min_generation || $generation > $max_generation) {
62
return new Aphront404Response();
63
}
64
65
if ($generation < $max_generation) {
66
$warnings[] = pht(
67
'You are viewing an older run of this build. %s',
68
phutil_tag(
69
'a',
70
array(
71
'href' => $build->getURI(),
72
),
73
pht('View Current Build')));
74
}
75
76
$curtain = $this->buildCurtainView($build);
77
$properties = $this->buildPropertyList($build);
78
$history = $this->buildHistoryTable(
79
$build,
80
$generation,
81
$min_generation,
82
$max_generation);
83
84
$crumbs = $this->buildApplicationCrumbs();
85
$this->addBuildableCrumb($crumbs, $build->getBuildable());
86
$crumbs->addTextCrumb($title);
87
$crumbs->setBorder(true);
88
89
$build_targets = id(new HarbormasterBuildTargetQuery())
90
->setViewer($viewer)
91
->needBuildSteps(true)
92
->withBuildPHIDs(array($build->getPHID()))
93
->withBuildGenerations(array($generation))
94
->execute();
95
96
if ($build_targets) {
97
$messages = id(new HarbormasterBuildMessageQuery())
98
->setViewer($viewer)
99
->withReceiverPHIDs(mpull($build_targets, 'getPHID'))
100
->execute();
101
$messages = mgroup($messages, 'getReceiverPHID');
102
} else {
103
$messages = array();
104
}
105
106
if ($build_targets) {
107
$artifacts = id(new HarbormasterBuildArtifactQuery())
108
->setViewer($viewer)
109
->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))
110
->execute();
111
$artifacts = msort($artifacts, 'getArtifactKey');
112
$artifacts = mgroup($artifacts, 'getBuildTargetPHID');
113
} else {
114
$artifacts = array();
115
}
116
117
118
$targets = array();
119
foreach ($build_targets as $build_target) {
120
$header = id(new PHUIHeaderView())
121
->setHeader($build_target->getName())
122
->setUser($viewer)
123
->setHeaderIcon('fa-bullseye');
124
125
$target_box = id(new PHUIObjectBoxView())
126
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
127
->setHeader($header);
128
129
$tab_group = new PHUITabGroupView();
130
$target_box->addTabGroup($tab_group);
131
132
$property_list = new PHUIPropertyListView();
133
134
$target_artifacts = idx($artifacts, $build_target->getPHID(), array());
135
136
$links = array();
137
$type_uri = HarbormasterURIArtifact::ARTIFACTCONST;
138
foreach ($target_artifacts as $artifact) {
139
if ($artifact->getArtifactType() == $type_uri) {
140
$impl = $artifact->getArtifactImplementation();
141
if ($impl->isExternalLink()) {
142
$links[] = $impl->renderLink();
143
}
144
}
145
}
146
147
if ($links) {
148
$links = phutil_implode_html(phutil_tag('br'), $links);
149
$property_list->addProperty(
150
pht('External Link'),
151
$links);
152
}
153
154
$status_view = new PHUIStatusListView();
155
$item = new PHUIStatusItemView();
156
157
$status = $build_target->getTargetStatus();
158
$status_name =
159
HarbormasterBuildTarget::getBuildTargetStatusName($status);
160
$icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
161
$color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
162
163
$item->setTarget($status_name);
164
$item->setIcon($icon, $color);
165
$status_view->addItem($item);
166
167
$when = array();
168
$started = $build_target->getDateStarted();
169
$now = PhabricatorTime::getNow();
170
if ($started) {
171
$ended = $build_target->getDateCompleted();
172
if ($ended) {
173
$when[] = pht(
174
'Completed at %s',
175
phabricator_datetime($ended, $viewer));
176
177
$duration = ($ended - $started);
178
if ($duration) {
179
$when[] = pht(
180
'Built for %s',
181
phutil_format_relative_time_detailed($duration));
182
} else {
183
$when[] = pht('Built instantly');
184
}
185
} else {
186
$when[] = pht(
187
'Started at %s',
188
phabricator_datetime($started, $viewer));
189
$duration = ($now - $started);
190
if ($duration) {
191
$when[] = pht(
192
'Running for %s',
193
phutil_format_relative_time_detailed($duration));
194
}
195
}
196
} else {
197
$created = $build_target->getDateCreated();
198
$when[] = pht(
199
'Queued at %s',
200
phabricator_datetime($started, $viewer));
201
$duration = ($now - $created);
202
if ($duration) {
203
$when[] = pht(
204
'Waiting for %s',
205
phutil_format_relative_time_detailed($duration));
206
}
207
}
208
209
$property_list->addProperty(
210
pht('When'),
211
phutil_implode_html(" \xC2\xB7 ", $when));
212
213
$property_list->addProperty(pht('Status'), $status_view);
214
215
$tab_group->addTab(
216
id(new PHUITabView())
217
->setName(pht('Overview'))
218
->setKey('overview')
219
->appendChild($property_list));
220
221
$step = $build_target->getBuildStep();
222
223
if ($step) {
224
$description = $step->getDescription();
225
if ($description) {
226
$description = new PHUIRemarkupView($viewer, $description);
227
$property_list->addSectionHeader(
228
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
229
$property_list->addTextContent($description);
230
}
231
} else {
232
$target_box->setFormErrors(
233
array(
234
pht(
235
'This build step has since been deleted on the build plan. '.
236
'Some information may be omitted.'),
237
));
238
}
239
240
$details = $build_target->getDetails();
241
$property_list = new PHUIPropertyListView();
242
foreach ($details as $key => $value) {
243
$property_list->addProperty($key, $value);
244
}
245
$tab_group->addTab(
246
id(new PHUITabView())
247
->setName(pht('Configuration'))
248
->setKey('configuration')
249
->appendChild($property_list));
250
251
$variables = $build_target->getVariables();
252
$variables_tab = $this->buildProperties($variables);
253
$tab_group->addTab(
254
id(new PHUITabView())
255
->setName(pht('Variables'))
256
->setKey('variables')
257
->appendChild($variables_tab));
258
259
$artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts);
260
$tab_group->addTab(
261
id(new PHUITabView())
262
->setName(pht('Artifacts'))
263
->setKey('artifacts')
264
->appendChild($artifacts_tab));
265
266
$build_messages = idx($messages, $build_target->getPHID(), array());
267
$messages_tab = $this->buildMessages($build_messages);
268
$tab_group->addTab(
269
id(new PHUITabView())
270
->setName(pht('Messages'))
271
->setKey('messages')
272
->appendChild($messages_tab));
273
274
$property_list = new PHUIPropertyListView();
275
$property_list->addProperty(
276
pht('Build Target ID'),
277
$build_target->getID());
278
$property_list->addProperty(
279
pht('Build Target PHID'),
280
$build_target->getPHID());
281
282
$tab_group->addTab(
283
id(new PHUITabView())
284
->setName(pht('Metadata'))
285
->setKey('metadata')
286
->appendChild($property_list));
287
288
$targets[] = $target_box;
289
290
$targets[] = $this->buildLog($build, $build_target, $generation);
291
}
292
293
$timeline = $this->buildTransactionTimeline(
294
$build,
295
new HarbormasterBuildTransactionQuery());
296
$timeline->setShouldTerminate(true);
297
298
if ($warnings) {
299
$warnings = id(new PHUIInfoView())
300
->setErrors($warnings)
301
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
302
} else {
303
$warnings = null;
304
}
305
306
$view = id(new PHUITwoColumnView())
307
->setHeader($page_header)
308
->setCurtain($curtain)
309
->setMainColumn(
310
array(
311
$warnings,
312
$properties,
313
$history,
314
$targets,
315
$timeline,
316
));
317
318
return $this->newPage()
319
->setTitle($title)
320
->setCrumbs($crumbs)
321
->appendChild($view);
322
323
}
324
325
private function buildArtifacts(
326
HarbormasterBuildTarget $build_target,
327
array $artifacts) {
328
$viewer = $this->getViewer();
329
330
$rows = array();
331
foreach ($artifacts as $artifact) {
332
$impl = $artifact->getArtifactImplementation();
333
334
if ($impl) {
335
$summary = $impl->renderArtifactSummary($viewer);
336
$type_name = $impl->getArtifactTypeName();
337
} else {
338
$summary = pht('<Unknown Artifact Type>');
339
$type_name = $artifact->getType();
340
}
341
342
$rows[] = array(
343
$artifact->getArtifactKey(),
344
$type_name,
345
$summary,
346
);
347
}
348
349
$table = id(new AphrontTableView($rows))
350
->setNoDataString(pht('This target has no associated artifacts.'))
351
->setHeaders(
352
array(
353
pht('Key'),
354
pht('Type'),
355
pht('Summary'),
356
))
357
->setColumnClasses(
358
array(
359
'pri',
360
'',
361
'wide',
362
));
363
364
return $table;
365
}
366
367
private function buildLog(
368
HarbormasterBuild $build,
369
HarbormasterBuildTarget $build_target,
370
$generation) {
371
372
$request = $this->getRequest();
373
$viewer = $request->getUser();
374
$limit = $request->getInt('l', 25);
375
376
$logs = id(new HarbormasterBuildLogQuery())
377
->setViewer($viewer)
378
->withBuildTargetPHIDs(array($build_target->getPHID()))
379
->execute();
380
381
$empty_logs = array();
382
383
$log_boxes = array();
384
foreach ($logs as $log) {
385
$start = 1;
386
$lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
387
if ($limit !== 0) {
388
$start = count($lines) - $limit;
389
if ($start >= 1) {
390
$lines = array_slice($lines, -$limit, $limit);
391
} else {
392
$start = 1;
393
}
394
}
395
396
$id = null;
397
$is_empty = false;
398
if (count($lines) === 1 && trim($lines[0]) === '') {
399
// Prevent Harbormaster from showing empty build logs.
400
$id = celerity_generate_unique_node_id();
401
$empty_logs[] = $id;
402
$is_empty = true;
403
}
404
405
$log_view = new ShellLogView();
406
$log_view->setLines($lines);
407
$log_view->setStart($start);
408
409
$subheader = $this->createLogHeader($build, $log, $limit, $generation);
410
411
$prototype_view = id(new PHUIButtonView())
412
->setTag('a')
413
->setHref($log->getURI())
414
->setIcon('fa-file-text-o')
415
->setText(pht('New View (Prototype)'));
416
417
$header = id(new PHUIHeaderView())
418
->setHeader(pht(
419
'Build Log %d (%s - %s)',
420
$log->getID(),
421
$log->getLogSource(),
422
$log->getLogType()))
423
->addActionLink($prototype_view)
424
->setSubheader($subheader)
425
->setUser($viewer);
426
427
$log_box = id(new PHUIObjectBoxView())
428
->setHeader($header)
429
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
430
->setForm($log_view);
431
432
if ($is_empty) {
433
$log_box = phutil_tag(
434
'div',
435
array(
436
'style' => 'display: none',
437
'id' => $id,
438
),
439
$log_box);
440
}
441
442
$log_boxes[] = $log_box;
443
}
444
445
if ($empty_logs) {
446
$hide_id = celerity_generate_unique_node_id();
447
448
Javelin::initBehavior('phabricator-reveal-content');
449
450
$expand = phutil_tag(
451
'div',
452
array(
453
'id' => $hide_id,
454
'class' => 'harbormaster-empty-logs-are-hidden',
455
),
456
array(
457
pht(
458
'%s empty logs are hidden.',
459
phutil_count($empty_logs)),
460
' ',
461
javelin_tag(
462
'a',
463
array(
464
'href' => '#',
465
'sigil' => 'reveal-content',
466
'meta' => array(
467
'showIDs' => $empty_logs,
468
'hideIDs' => array($hide_id),
469
),
470
),
471
pht('Show all logs.')),
472
));
473
474
array_unshift($log_boxes, $expand);
475
}
476
477
return $log_boxes;
478
}
479
480
private function createLogHeader($build, $log, $limit, $generation) {
481
$options = array(
482
array(
483
'n' => 25,
484
),
485
array(
486
'n' => 50,
487
),
488
array(
489
'n' => 100,
490
),
491
array(
492
'n' => 0,
493
'label' => pht('Unlimited'),
494
),
495
);
496
497
$base_uri = id(new PhutilURI($build->getURI().$generation.'/'));
498
499
$links = array();
500
foreach ($options as $option) {
501
$n = $option['n'];
502
$label = idx($option, 'label', $n);
503
504
$is_selected = ($limit == $n);
505
if ($is_selected) {
506
$links[] = phutil_tag(
507
'strong',
508
array(),
509
$label);
510
} else {
511
$links[] = phutil_tag(
512
'a',
513
array(
514
'href' => (string)$base_uri->alter('l', $n),
515
),
516
$label);
517
}
518
}
519
520
return phutil_tag(
521
'span',
522
array(),
523
array(
524
phutil_implode_html(' - ', $links),
525
' ',
526
pht('Lines'),
527
));
528
}
529
530
private function buildCurtainView(HarbormasterBuild $build) {
531
$viewer = $this->getViewer();
532
$id = $build->getID();
533
534
$curtain = $this->newCurtainView($build);
535
536
$messages = array(
537
new HarbormasterBuildMessageRestartTransaction(),
538
new HarbormasterBuildMessagePauseTransaction(),
539
new HarbormasterBuildMessageResumeTransaction(),
540
new HarbormasterBuildMessageAbortTransaction(),
541
);
542
543
foreach ($messages as $message) {
544
$can_send = $message->canSendMessage($viewer, $build);
545
546
$message_uri = urisprintf(
547
'/build/%s/%d/',
548
$message->getHarbormasterBuildMessageType(),
549
$id);
550
$message_uri = $this->getApplicationURI($message_uri);
551
552
$action = id(new PhabricatorActionView())
553
->setName($message->getHarbormasterBuildMessageName())
554
->setIcon($message->getIcon())
555
->setHref($message_uri)
556
->setDisabled(!$can_send)
557
->setWorkflow(true);
558
559
$curtain->addAction($action);
560
}
561
562
return $curtain;
563
}
564
565
private function buildPropertyList(HarbormasterBuild $build) {
566
$viewer = $this->getViewer();
567
568
$properties = id(new PHUIPropertyListView())
569
->setUser($viewer);
570
571
$handles = id(new PhabricatorHandleQuery())
572
->setViewer($viewer)
573
->withPHIDs(array(
574
$build->getBuildablePHID(),
575
$build->getBuildPlanPHID(),
576
))
577
->execute();
578
579
$properties->addProperty(
580
pht('Buildable'),
581
$handles[$build->getBuildablePHID()]->renderLink());
582
583
$properties->addProperty(
584
pht('Build Plan'),
585
$handles[$build->getBuildPlanPHID()]->renderLink());
586
587
return id(new PHUIObjectBoxView())
588
->setHeaderText(pht('Properties'))
589
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
590
->appendChild($properties);
591
592
}
593
594
private function buildHistoryTable(
595
HarbormasterBuild $build,
596
$generation,
597
$min_generation,
598
$max_generation) {
599
600
if ($max_generation === $min_generation) {
601
return null;
602
}
603
604
$viewer = $this->getViewer();
605
606
$uri = $build->getURI();
607
608
$rows = array();
609
$rowc = array();
610
for ($ii = $max_generation; $ii >= $min_generation; $ii--) {
611
if ($generation == $ii) {
612
$rowc[] = 'highlighted';
613
} else {
614
$rowc[] = null;
615
}
616
617
$rows[] = array(
618
phutil_tag(
619
'a',
620
array(
621
'href' => $uri.$ii.'/',
622
),
623
pht('Run %d', $ii)),
624
);
625
}
626
627
$table = id(new AphrontTableView($rows))
628
->setColumnClasses(
629
array(
630
'pri wide',
631
))
632
->setRowClasses($rowc);
633
634
return id(new PHUIObjectBoxView())
635
->setHeaderText(pht('History'))
636
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
637
->setTable($table);
638
}
639
640
private function buildMessages(array $messages) {
641
$viewer = $this->getRequest()->getUser();
642
643
if ($messages) {
644
$handles = id(new PhabricatorHandleQuery())
645
->setViewer($viewer)
646
->withPHIDs(mpull($messages, 'getAuthorPHID'))
647
->execute();
648
} else {
649
$handles = array();
650
}
651
652
$rows = array();
653
foreach ($messages as $message) {
654
$rows[] = array(
655
$message->getID(),
656
$handles[$message->getAuthorPHID()]->renderLink(),
657
$message->getType(),
658
$message->getIsConsumed() ? pht('Consumed') : null,
659
phabricator_datetime($message->getDateCreated(), $viewer),
660
);
661
}
662
663
$table = new AphrontTableView($rows);
664
$table->setNoDataString(pht('No messages for this build target.'));
665
$table->setHeaders(
666
array(
667
pht('ID'),
668
pht('From'),
669
pht('Type'),
670
pht('Consumed'),
671
pht('Received'),
672
));
673
$table->setColumnClasses(
674
array(
675
'',
676
'',
677
'wide',
678
'',
679
'date',
680
));
681
682
return $table;
683
}
684
685
private function buildProperties(array $properties) {
686
ksort($properties);
687
688
$rows = array();
689
foreach ($properties as $key => $value) {
690
$rows[] = array(
691
$key,
692
$value,
693
);
694
}
695
696
$table = id(new AphrontTableView($rows))
697
->setHeaders(
698
array(
699
pht('Key'),
700
pht('Value'),
701
))
702
->setColumnClasses(
703
array(
704
'pri right',
705
'wide',
706
));
707
708
return $table;
709
}
710
711
}
712
713