Remove product literal strings in "pht()", part 6
[phabricator.git] / src / applications / harbormaster / controller / HarbormasterBuildViewController.php
bloba17948fc8d682b2b85bfad9fb3cf07ed7d31dfcc
1 <?php
3 final class HarbormasterBuildViewController
4 extends HarbormasterController {
6 public function shouldAllowPublic() {
7 return true;
10 public function handleRequest(AphrontRequest $request) {
11 $request = $this->getRequest();
12 $viewer = $request->getUser();
14 $id = $request->getURIData('id');
16 $build = id(new HarbormasterBuildQuery())
17 ->setViewer($viewer)
18 ->withIDs(array($id))
19 ->executeOne();
20 if (!$build) {
21 return new Aphront404Response();
24 require_celerity_resource('harbormaster-css');
26 $title = pht('Build %d', $id);
27 $warnings = array();
29 $page_header = id(new PHUIHeaderView())
30 ->setHeader($title)
31 ->setUser($viewer)
32 ->setPolicyObject($build)
33 ->setHeaderIcon('fa-cubes');
35 $status = $build->getBuildPendingStatusObject();
37 $status_icon = $status->getIconIcon();
38 $status_color = $status->getIconColor();
39 $status_name = $status->getName();
41 $page_header->setStatus($status_icon, $status_color, $status_name);
43 $max_generation = (int)$build->getBuildGeneration();
44 if ($max_generation === 0) {
45 $min_generation = 0;
46 } else {
47 $min_generation = 1;
50 if ($build->isRestarting()) {
51 $max_generation = $max_generation + 1;
54 $generation = $request->getURIData('generation');
55 if ($generation === null) {
56 $generation = $max_generation;
57 } else {
58 $generation = (int)$generation;
61 if ($generation < $min_generation || $generation > $max_generation) {
62 return new Aphront404Response();
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(),
73 pht('View Current Build')));
76 $curtain = $this->buildCurtainView($build);
77 $properties = $this->buildPropertyList($build);
78 $history = $this->buildHistoryTable(
79 $build,
80 $generation,
81 $min_generation,
82 $max_generation);
84 $crumbs = $this->buildApplicationCrumbs();
85 $this->addBuildableCrumb($crumbs, $build->getBuildable());
86 $crumbs->addTextCrumb($title);
87 $crumbs->setBorder(true);
89 $build_targets = id(new HarbormasterBuildTargetQuery())
90 ->setViewer($viewer)
91 ->needBuildSteps(true)
92 ->withBuildPHIDs(array($build->getPHID()))
93 ->withBuildGenerations(array($generation))
94 ->execute();
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();
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();
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');
125 $target_box = id(new PHUIObjectBoxView())
126 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
127 ->setHeader($header);
129 $tab_group = new PHUITabGroupView();
130 $target_box->addTabGroup($tab_group);
132 $property_list = new PHUIPropertyListView();
134 $target_artifacts = idx($artifacts, $build_target->getPHID(), array());
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();
147 if ($links) {
148 $links = phutil_implode_html(phutil_tag('br'), $links);
149 $property_list->addProperty(
150 pht('External Link'),
151 $links);
154 $status_view = new PHUIStatusListView();
155 $item = new PHUIStatusItemView();
157 $status = $build_target->getTargetStatus();
158 $status_name =
159 HarbormasterBuildTarget::getBuildTargetStatusName($status);
160 $icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
161 $color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
163 $item->setTarget($status_name);
164 $item->setIcon($icon, $color);
165 $status_view->addItem($item);
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));
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');
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));
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));
209 $property_list->addProperty(
210 pht('When'),
211 phutil_implode_html(" \xC2\xB7 ", $when));
213 $property_list->addProperty(pht('Status'), $status_view);
215 $tab_group->addTab(
216 id(new PHUITabView())
217 ->setName(pht('Overview'))
218 ->setKey('overview')
219 ->appendChild($property_list));
221 $step = $build_target->getBuildStep();
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);
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.'),
240 $details = $build_target->getDetails();
241 $property_list = new PHUIPropertyListView();
242 foreach ($details as $key => $value) {
243 $property_list->addProperty($key, $value);
245 $tab_group->addTab(
246 id(new PHUITabView())
247 ->setName(pht('Configuration'))
248 ->setKey('configuration')
249 ->appendChild($property_list));
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));
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));
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));
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());
282 $tab_group->addTab(
283 id(new PHUITabView())
284 ->setName(pht('Metadata'))
285 ->setKey('metadata')
286 ->appendChild($property_list));
288 $targets[] = $target_box;
290 $targets[] = $this->buildLog($build, $build_target, $generation);
293 $timeline = $this->buildTransactionTimeline(
294 $build,
295 new HarbormasterBuildTransactionQuery());
296 $timeline->setShouldTerminate(true);
298 if ($warnings) {
299 $warnings = id(new PHUIInfoView())
300 ->setErrors($warnings)
301 ->setSeverity(PHUIInfoView::SEVERITY_WARNING);
302 } else {
303 $warnings = null;
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,
318 return $this->newPage()
319 ->setTitle($title)
320 ->setCrumbs($crumbs)
321 ->appendChild($view);
325 private function buildArtifacts(
326 HarbormasterBuildTarget $build_target,
327 array $artifacts) {
328 $viewer = $this->getViewer();
330 $rows = array();
331 foreach ($artifacts as $artifact) {
332 $impl = $artifact->getArtifactImplementation();
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();
342 $rows[] = array(
343 $artifact->getArtifactKey(),
344 $type_name,
345 $summary,
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'),
357 ->setColumnClasses(
358 array(
359 'pri',
361 'wide',
364 return $table;
367 private function buildLog(
368 HarbormasterBuild $build,
369 HarbormasterBuildTarget $build_target,
370 $generation) {
372 $request = $this->getRequest();
373 $viewer = $request->getUser();
374 $limit = $request->getInt('l', 25);
376 $logs = id(new HarbormasterBuildLogQuery())
377 ->setViewer($viewer)
378 ->withBuildTargetPHIDs(array($build_target->getPHID()))
379 ->execute();
381 $empty_logs = array();
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;
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;
405 $log_view = new ShellLogView();
406 $log_view->setLines($lines);
407 $log_view->setStart($start);
409 $subheader = $this->createLogHeader($build, $log, $limit, $generation);
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)'));
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);
427 $log_box = id(new PHUIObjectBoxView())
428 ->setHeader($header)
429 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
430 ->setForm($log_view);
432 if ($is_empty) {
433 $log_box = phutil_tag(
434 'div',
435 array(
436 'style' => 'display: none',
437 'id' => $id,
439 $log_box);
442 $log_boxes[] = $log_box;
445 if ($empty_logs) {
446 $hide_id = celerity_generate_unique_node_id();
448 Javelin::initBehavior('phabricator-reveal-content');
450 $expand = phutil_tag(
451 'div',
452 array(
453 'id' => $hide_id,
454 'class' => 'harbormaster-empty-logs-are-hidden',
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),
471 pht('Show all logs.')),
474 array_unshift($log_boxes, $expand);
477 return $log_boxes;
480 private function createLogHeader($build, $log, $limit, $generation) {
481 $options = array(
482 array(
483 'n' => 25,
485 array(
486 'n' => 50,
488 array(
489 'n' => 100,
491 array(
492 'n' => 0,
493 'label' => pht('Unlimited'),
497 $base_uri = id(new PhutilURI($build->getURI().$generation.'/'));
499 $links = array();
500 foreach ($options as $option) {
501 $n = $option['n'];
502 $label = idx($option, 'label', $n);
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),
516 $label);
520 return phutil_tag(
521 'span',
522 array(),
523 array(
524 phutil_implode_html(' - ', $links),
525 ' ',
526 pht('Lines'),
530 private function buildCurtainView(HarbormasterBuild $build) {
531 $viewer = $this->getViewer();
532 $id = $build->getID();
534 $curtain = $this->newCurtainView($build);
536 $messages = array(
537 new HarbormasterBuildMessageRestartTransaction(),
538 new HarbormasterBuildMessagePauseTransaction(),
539 new HarbormasterBuildMessageResumeTransaction(),
540 new HarbormasterBuildMessageAbortTransaction(),
543 foreach ($messages as $message) {
544 $can_send = $message->canSendMessage($viewer, $build);
546 $message_uri = urisprintf(
547 '/build/%s/%d/',
548 $message->getHarbormasterBuildMessageType(),
549 $id);
550 $message_uri = $this->getApplicationURI($message_uri);
552 $action = id(new PhabricatorActionView())
553 ->setName($message->getHarbormasterBuildMessageName())
554 ->setIcon($message->getIcon())
555 ->setHref($message_uri)
556 ->setDisabled(!$can_send)
557 ->setWorkflow(true);
559 $curtain->addAction($action);
562 return $curtain;
565 private function buildPropertyList(HarbormasterBuild $build) {
566 $viewer = $this->getViewer();
568 $properties = id(new PHUIPropertyListView())
569 ->setUser($viewer);
571 $handles = id(new PhabricatorHandleQuery())
572 ->setViewer($viewer)
573 ->withPHIDs(array(
574 $build->getBuildablePHID(),
575 $build->getBuildPlanPHID(),
577 ->execute();
579 $properties->addProperty(
580 pht('Buildable'),
581 $handles[$build->getBuildablePHID()]->renderLink());
583 $properties->addProperty(
584 pht('Build Plan'),
585 $handles[$build->getBuildPlanPHID()]->renderLink());
587 return id(new PHUIObjectBoxView())
588 ->setHeaderText(pht('Properties'))
589 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
590 ->appendChild($properties);
594 private function buildHistoryTable(
595 HarbormasterBuild $build,
596 $generation,
597 $min_generation,
598 $max_generation) {
600 if ($max_generation === $min_generation) {
601 return null;
604 $viewer = $this->getViewer();
606 $uri = $build->getURI();
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;
617 $rows[] = array(
618 phutil_tag(
619 'a',
620 array(
621 'href' => $uri.$ii.'/',
623 pht('Run %d', $ii)),
627 $table = id(new AphrontTableView($rows))
628 ->setColumnClasses(
629 array(
630 'pri wide',
632 ->setRowClasses($rowc);
634 return id(new PHUIObjectBoxView())
635 ->setHeaderText(pht('History'))
636 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
637 ->setTable($table);
640 private function buildMessages(array $messages) {
641 $viewer = $this->getRequest()->getUser();
643 if ($messages) {
644 $handles = id(new PhabricatorHandleQuery())
645 ->setViewer($viewer)
646 ->withPHIDs(mpull($messages, 'getAuthorPHID'))
647 ->execute();
648 } else {
649 $handles = array();
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),
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'),
673 $table->setColumnClasses(
674 array(
677 'wide',
679 'date',
682 return $table;
685 private function buildProperties(array $properties) {
686 ksort($properties);
688 $rows = array();
689 foreach ($properties as $key => $value) {
690 $rows[] = array(
691 $key,
692 $value,
696 $table = id(new AphrontTableView($rows))
697 ->setHeaders(
698 array(
699 pht('Key'),
700 pht('Value'),
702 ->setColumnClasses(
703 array(
704 'pri right',
705 'wide',
708 return $table;