3 final class HarbormasterBuildViewController
4 extends HarbormasterController
{
6 public function shouldAllowPublic() {
10 public function handleRequest(AphrontRequest
$request) {
11 $request = $this->getRequest();
12 $viewer = $request->getUser();
14 $id = $request->getURIData('id');
16 $build = id(new HarbormasterBuildQuery())
21 return new Aphront404Response();
24 require_celerity_resource('harbormaster-css');
26 $title = pht('Build %d', $id);
29 $page_header = id(new PHUIHeaderView())
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) {
50 if ($build->isRestarting()) {
51 $max_generation = $max_generation +
1;
54 $generation = $request->getURIData('generation');
55 if ($generation === null) {
56 $generation = $max_generation;
58 $generation = (int)$generation;
61 if ($generation < $min_generation ||
$generation > $max_generation) {
62 return new Aphront404Response();
65 if ($generation < $max_generation) {
67 'You are viewing an older run of this build. %s',
71 'href' => $build->getURI(),
73 pht('View Current Build')));
76 $curtain = $this->buildCurtainView($build);
77 $properties = $this->buildPropertyList($build);
78 $history = $this->buildHistoryTable(
84 $crumbs = $this->buildApplicationCrumbs();
85 $this->addBuildableCrumb($crumbs, $build->getBuildable());
86 $crumbs->addTextCrumb($title);
87 $crumbs->setBorder(true);
89 $build_targets = id(new HarbormasterBuildTargetQuery())
91 ->needBuildSteps(true)
92 ->withBuildPHIDs(array($build->getPHID()))
93 ->withBuildGenerations(array($generation))
97 $messages = id(new HarbormasterBuildMessageQuery())
99 ->withReceiverPHIDs(mpull($build_targets, 'getPHID'))
101 $messages = mgroup($messages, 'getReceiverPHID');
106 if ($build_targets) {
107 $artifacts = id(new HarbormasterBuildArtifactQuery())
109 ->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))
111 $artifacts = msort($artifacts, 'getArtifactKey');
112 $artifacts = mgroup($artifacts, 'getBuildTargetPHID');
114 $artifacts = array();
119 foreach ($build_targets as $build_target) {
120 $header = id(new PHUIHeaderView())
121 ->setHeader($build_target->getName())
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());
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();
148 $links = phutil_implode_html(phutil_tag('br'), $links);
149 $property_list->addProperty(
150 pht('External Link'),
154 $status_view = new PHUIStatusListView();
155 $item = new PHUIStatusItemView();
157 $status = $build_target->getTargetStatus();
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);
168 $started = $build_target->getDateStarted();
169 $now = PhabricatorTime
::getNow();
171 $ended = $build_target->getDateCompleted();
175 phabricator_datetime($ended, $viewer));
177 $duration = ($ended - $started);
181 phutil_format_relative_time_detailed($duration));
183 $when[] = pht('Built instantly');
188 phabricator_datetime($started, $viewer));
189 $duration = ($now - $started);
193 phutil_format_relative_time_detailed($duration));
197 $created = $build_target->getDateCreated();
200 phabricator_datetime($started, $viewer));
201 $duration = ($now - $created);
205 phutil_format_relative_time_detailed($duration));
209 $property_list->addProperty(
211 phutil_implode_html(" \xC2\xB7 ", $when));
213 $property_list->addProperty(pht('Status'), $status_view);
216 id(new PHUITabView())
217 ->setName(pht('Overview'))
219 ->appendChild($property_list));
221 $step = $build_target->getBuildStep();
224 $description = $step->getDescription();
226 $description = new PHUIRemarkupView($viewer, $description);
227 $property_list->addSectionHeader(
228 pht('Description'), PHUIPropertyListView
::ICON_SUMMARY
);
229 $property_list->addTextContent($description);
232 $target_box->setFormErrors(
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);
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);
254 id(new PHUITabView())
255 ->setName(pht('Variables'))
256 ->setKey('variables')
257 ->appendChild($variables_tab));
259 $artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts);
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);
269 id(new PHUITabView())
270 ->setName(pht('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());
283 id(new PHUITabView())
284 ->setName(pht('Metadata'))
286 ->appendChild($property_list));
288 $targets[] = $target_box;
290 $targets[] = $this->buildLog($build, $build_target, $generation);
293 $timeline = $this->buildTransactionTimeline(
295 new HarbormasterBuildTransactionQuery());
296 $timeline->setShouldTerminate(true);
299 $warnings = id(new PHUIInfoView())
300 ->setErrors($warnings)
301 ->setSeverity(PHUIInfoView
::SEVERITY_WARNING
);
306 $view = id(new PHUITwoColumnView())
307 ->setHeader($page_header)
308 ->setCurtain($curtain)
318 return $this->newPage()
321 ->appendChild($view);
325 private function buildArtifacts(
326 HarbormasterBuildTarget
$build_target,
328 $viewer = $this->getViewer();
331 foreach ($artifacts as $artifact) {
332 $impl = $artifact->getArtifactImplementation();
335 $summary = $impl->renderArtifactSummary($viewer);
336 $type_name = $impl->getArtifactTypeName();
338 $summary = pht('<Unknown Artifact Type>');
339 $type_name = $artifact->getType();
343 $artifact->getArtifactKey(),
349 $table = id(new AphrontTableView($rows))
350 ->setNoDataString(pht('This target has no associated artifacts.'))
367 private function buildLog(
368 HarbormasterBuild
$build,
369 HarbormasterBuildTarget
$build_target,
372 $request = $this->getRequest();
373 $viewer = $request->getUser();
374 $limit = $request->getInt('l', 25);
376 $logs = id(new HarbormasterBuildLogQuery())
378 ->withBuildTargetPHIDs(array($build_target->getPHID()))
381 $empty_logs = array();
383 $log_boxes = array();
384 foreach ($logs as $log) {
386 $lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
388 $start = count($lines) - $limit;
390 $lines = array_slice($lines, -$limit, $limit);
398 if (count($lines) === 1 && trim($lines[0]) === '') {
399 // Prevent Harbormaster from showing empty build logs.
400 $id = celerity_generate_unique_node_id();
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())
413 ->setHref($log->getURI())
414 ->setIcon('fa-file-text-o')
415 ->setText(pht('New View (Prototype)'));
417 $header = id(new PHUIHeaderView())
419 'Build Log %d (%s - %s)',
421 $log->getLogSource(),
423 ->addActionLink($prototype_view)
424 ->setSubheader($subheader)
427 $log_box = id(new PHUIObjectBoxView())
429 ->setBackground(PHUIObjectBoxView
::BLUE_PROPERTY
)
430 ->setForm($log_view);
433 $log_box = phutil_tag(
436 'style' => 'display: none',
442 $log_boxes[] = $log_box;
446 $hide_id = celerity_generate_unique_node_id();
448 Javelin
::initBehavior('phabricator-reveal-content');
450 $expand = phutil_tag(
454 'class' => 'harbormaster-empty-logs-are-hidden',
458 '%s empty logs are hidden.',
459 phutil_count($empty_logs)),
465 'sigil' => 'reveal-content',
467 'showIDs' => $empty_logs,
468 'hideIDs' => array($hide_id),
471 pht('Show all logs.')),
474 array_unshift($log_boxes, $expand);
480 private function createLogHeader($build, $log, $limit, $generation) {
493 'label' => pht('Unlimited'),
497 $base_uri = id(new PhutilURI($build->getURI().$generation.'/'));
500 foreach ($options as $option) {
502 $label = idx($option, 'label', $n);
504 $is_selected = ($limit == $n);
506 $links[] = phutil_tag(
511 $links[] = phutil_tag(
514 'href' => (string)$base_uri->alter('l', $n),
524 phutil_implode_html(' - ', $links),
530 private function buildCurtainView(HarbormasterBuild
$build) {
531 $viewer = $this->getViewer();
532 $id = $build->getID();
534 $curtain = $this->newCurtainView($build);
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(
548 $message->getHarbormasterBuildMessageType(),
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)
559 $curtain->addAction($action);
565 private function buildPropertyList(HarbormasterBuild
$build) {
566 $viewer = $this->getViewer();
568 $properties = id(new PHUIPropertyListView())
571 $handles = id(new PhabricatorHandleQuery())
574 $build->getBuildablePHID(),
575 $build->getBuildPlanPHID(),
579 $properties->addProperty(
581 $handles[$build->getBuildablePHID()]->renderLink());
583 $properties->addProperty(
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,
600 if ($max_generation === $min_generation) {
604 $viewer = $this->getViewer();
606 $uri = $build->getURI();
610 for ($ii = $max_generation; $ii >= $min_generation; $ii--) {
611 if ($generation == $ii) {
612 $rowc[] = 'highlighted';
621 'href' => $uri.$ii.'/',
627 $table = id(new AphrontTableView($rows))
632 ->setRowClasses($rowc);
634 return id(new PHUIObjectBoxView())
635 ->setHeaderText(pht('History'))
636 ->setBackground(PHUIObjectBoxView
::BLUE_PROPERTY
)
640 private function buildMessages(array $messages) {
641 $viewer = $this->getRequest()->getUser();
644 $handles = id(new PhabricatorHandleQuery())
646 ->withPHIDs(mpull($messages, 'getAuthorPHID'))
653 foreach ($messages as $message) {
656 $handles[$message->getAuthorPHID()]->renderLink(),
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.'));
673 $table->setColumnClasses(
685 private function buildProperties(array $properties) {
689 foreach ($properties as $key => $value) {
696 $table = id(new AphrontTableView($rows))