Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/harbormaster/controller/HarbormasterBuildLogDownloadController.php b/src/applications/harbormaster/controller/HarbormasterBuildLogDownloadController.php
index 40313cdafd..ee02b25636 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildLogDownloadController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildLogDownloadController.php
@@ -1,50 +1,54 @@
<?php
final class HarbormasterBuildLogDownloadController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $request->getURIData('id');
$log = id(new HarbormasterBuildLogQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$log) {
return new Aphront404Response();
}
$cancel_uri = $log->getURI();
$file_phid = $log->getFilePHID();
if (!$file_phid) {
return $this->newDialog()
->setTitle(pht('Log Not Finalized'))
->appendParagraph(
pht(
'Logs must be fully written and processed before they can be '.
'downloaded. This log is still being written or processed.'))
->addCancelButton($cancel_uri, pht('Wait Patiently'));
}
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
return $this->newDialog()
->setTitle(pht('Unable to Load File'))
->appendParagraph(
pht(
'Unable to load the file for this log. The file may have been '.
'destroyed.'))
->addCancelButton($cancel_uri);
}
return $file->newDownloadResponse();
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php b/src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
index 7a5050becf..0f1f2bc028 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
@@ -1,893 +1,897 @@
<?php
final class HarbormasterBuildLogRenderController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$log = id(new HarbormasterBuildLogQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$log) {
return new Aphront404Response();
}
$highlight_range = $request->getURILineRange('lines', 1000);
$log_size = $this->getTotalByteLength($log);
$head_lines = $request->getInt('head');
if ($head_lines === null) {
$head_lines = 8;
}
$head_lines = min($head_lines, 1024);
$head_lines = max($head_lines, 0);
$tail_lines = $request->getInt('tail');
if ($tail_lines === null) {
$tail_lines = 16;
}
$tail_lines = min($tail_lines, 1024);
$tail_lines = max($tail_lines, 0);
$head_offset = $request->getInt('headOffset');
if ($head_offset === null) {
$head_offset = 0;
}
$tail_offset = $request->getInt('tailOffset');
if ($tail_offset === null) {
$tail_offset = $log_size;
}
// Figure out which ranges we're actually going to read. We'll read either
// one range (either just at the head, or just at the tail) or two ranges
// (one at the head and one at the tail).
// This gets a little bit tricky because: the ranges may overlap; we just
// want to do one big read if there is only a little bit of text left
// between the ranges; we may not know where the tail range ends; and we
// can only read forward from line map markers, not from any arbitrary
// position in the file.
$bytes_per_line = 140;
$body_lines = 8;
$views = array();
if ($head_lines > 0) {
$views[] = array(
'offset' => $head_offset,
'lines' => $head_lines,
'direction' => 1,
'limit' => $tail_offset,
);
}
if ($highlight_range) {
$highlight_views = $this->getHighlightViews(
$log,
$highlight_range,
$log_size);
foreach ($highlight_views as $highlight_view) {
$views[] = $highlight_view;
}
}
if ($tail_lines > 0) {
$views[] = array(
'offset' => $tail_offset,
'lines' => $tail_lines,
'direction' => -1,
'limit' => $head_offset,
);
}
$reads = $views;
foreach ($reads as $key => $read) {
$offset = $read['offset'];
$lines = $read['lines'];
$read_length = 0;
$read_length += ($lines * $bytes_per_line);
$read_length += ($body_lines * $bytes_per_line);
$direction = $read['direction'];
if ($direction < 0) {
if ($offset > $read_length) {
$offset -= $read_length;
} else {
$read_length = $offset;
$offset = 0;
}
}
$position = $log->getReadPosition($offset);
list($position_offset, $position_line) = $position;
$read_length += ($offset - $position_offset);
$reads[$key]['fetchOffset'] = $position_offset;
$reads[$key]['fetchLength'] = $read_length;
$reads[$key]['fetchLine'] = $position_line;
}
$reads = $this->mergeOverlappingReads($reads);
foreach ($reads as $key => $read) {
$fetch_offset = $read['fetchOffset'];
$fetch_length = $read['fetchLength'];
if ($fetch_offset + $fetch_length > $log_size) {
$fetch_length = $log_size - $fetch_offset;
}
$data = $log->loadData($fetch_offset, $fetch_length);
$offset = $read['fetchOffset'];
$line = $read['fetchLine'];
$lines = $this->getLines($data);
$line_data = array();
foreach ($lines as $line_text) {
$length = strlen($line_text);
$line_data[] = array(
'offset' => $offset,
'length' => $length,
'line' => $line,
'data' => $line_text,
);
$line += 1;
$offset += $length;
}
$reads[$key]['data'] = $data;
$reads[$key]['lines'] = $line_data;
}
foreach ($views as $view_key => $view) {
$anchor_byte = $view['offset'];
if ($view['direction'] < 0) {
$anchor_byte = $anchor_byte - 1;
}
$data_key = null;
foreach ($reads as $read_key => $read) {
$s = $read['fetchOffset'];
$e = $s + $read['fetchLength'];
if (($s <= $anchor_byte) && ($e >= $anchor_byte)) {
$data_key = $read_key;
break;
}
}
if ($data_key === null) {
throw new Exception(
pht('Unable to find fetch!'));
}
$anchor_key = null;
foreach ($reads[$data_key]['lines'] as $line_key => $line) {
$s = $line['offset'];
$e = $s + $line['length'];
if (($s <= $anchor_byte) && ($e > $anchor_byte)) {
$anchor_key = $line_key;
break;
}
}
if ($anchor_key === null) {
throw new Exception(
pht(
'Unable to find lines.'));
}
if ($view['direction'] > 0) {
$slice_offset = $anchor_key;
} else {
$slice_offset = max(0, $anchor_key - ($view['lines'] - 1));
}
$slice_length = $view['lines'];
$views[$view_key] += array(
'sliceKey' => $data_key,
'sliceOffset' => $slice_offset,
'sliceLength' => $slice_length,
);
}
foreach ($views as $view_key => $view) {
$slice_key = $view['sliceKey'];
$lines = array_slice(
$reads[$slice_key]['lines'],
$view['sliceOffset'],
$view['sliceLength']);
$data_offset = null;
$data_length = null;
foreach ($lines as $line) {
if ($data_offset === null) {
$data_offset = $line['offset'];
}
$data_length += $line['length'];
}
// If the view cursor starts in the middle of a line, we're going to
// strip part of the line.
$direction = $view['direction'];
if ($direction > 0) {
$view_offset = $view['offset'];
$view_length = $data_length;
if ($data_offset < $view_offset) {
$trim = ($view_offset - $data_offset);
$view_length -= $trim;
}
$limit = $view['limit'];
if ($limit !== null) {
if ($limit < ($view_offset + $view_length)) {
$view_length = ($limit - $view_offset);
}
}
} else {
$view_offset = $data_offset;
$view_length = $data_length;
if ($data_offset + $data_length > $view['offset']) {
$view_length -= (($data_offset + $data_length) - $view['offset']);
}
$limit = $view['limit'];
if ($limit !== null) {
if ($limit > $view_offset) {
$view_length -= ($limit - $view_offset);
$view_offset = $limit;
}
}
}
$views[$view_key] += array(
'viewOffset' => $view_offset,
'viewLength' => $view_length,
);
}
$views = $this->mergeOverlappingViews($views);
foreach ($views as $view_key => $view) {
$slice_key = $view['sliceKey'];
$lines = array_slice(
$reads[$slice_key]['lines'],
$view['sliceOffset'],
$view['sliceLength']);
$view_offset = $view['viewOffset'];
foreach ($lines as $line_key => $line) {
$line_offset = $line['offset'];
if ($line_offset >= $view_offset) {
break;
}
$trim = ($view_offset - $line_offset);
if ($trim && ($trim >= strlen($line['data']))) {
unset($lines[$line_key]);
continue;
}
$line_data = substr($line['data'], $trim);
$lines[$line_key]['data'] = $line_data;
$lines[$line_key]['length'] = strlen($line_data);
$lines[$line_key]['offset'] += $trim;
break;
}
$view_end = $view['viewOffset'] + $view['viewLength'];
foreach ($lines as $line_key => $line) {
$line_end = $line['offset'] + $line['length'];
if ($line_end <= $view_end) {
continue;
}
$trim = ($line_end - $view_end);
if ($trim && ($trim >= strlen($line['data']))) {
unset($lines[$line_key]);
continue;
}
$line_data = substr($line['data'], -$trim);
$lines[$line_key]['data'] = $line_data;
$lines[$line_key]['length'] = strlen($line_data);
}
$views[$view_key]['viewData'] = $lines;
}
$spacer = null;
$render = array();
$head_view = head($views);
if ($head_view['viewOffset'] > $head_offset) {
$render[] = array(
'spacer' => true,
'head' => $head_offset,
'tail' => $head_view['viewOffset'],
);
}
foreach ($views as $view) {
if ($spacer) {
$spacer['tail'] = $view['viewOffset'];
$render[] = $spacer;
}
$render[] = $view;
$spacer = array(
'spacer' => true,
'head' => ($view['viewOffset'] + $view['viewLength']),
);
}
$tail_view = last($views);
if ($tail_view['viewOffset'] + $tail_view['viewLength'] < $tail_offset) {
$render[] = array(
'spacer' => true,
'head' => $tail_view['viewOffset'] + $tail_view['viewLength'],
'tail' => $tail_offset,
);
}
$uri = $log->getURI();
$rows = array();
foreach ($render as $range) {
if (isset($range['spacer'])) {
$rows[] = $this->renderExpandRow($range);
continue;
}
$lines = $range['viewData'];
foreach ($lines as $line) {
$display_line = ($line['line'] + 1);
$display_text = ($line['data']);
$row_attr = array();
if ($highlight_range) {
if (($display_line >= $highlight_range[0]) &&
($display_line <= $highlight_range[1])) {
$row_attr = array(
'class' => 'phabricator-source-highlight',
);
}
}
$display_line = phutil_tag(
'a',
array(
'href' => $uri.'$'.$display_line,
'data-n' => $display_line,
),
'');
$line_cell = phutil_tag('th', array(), $display_line);
$text_cell = phutil_tag('td', array(), $display_text);
$rows[] = phutil_tag(
'tr',
$row_attr,
array(
$line_cell,
$text_cell,
));
}
}
if ($log->getLive()) {
$last_view = last($views);
$last_line = last($last_view['viewData']);
if ($last_line) {
$last_offset = $last_line['offset'];
} else {
$last_offset = 0;
}
$last_tail = $last_view['viewOffset'] + $last_view['viewLength'];
$show_live = ($last_tail === $log_size);
if ($show_live) {
$rows[] = $this->renderLiveRow($last_offset);
}
}
$table = javelin_tag(
'table',
array(
'class' => 'harbormaster-log-table PhabricatorMonospaced',
'sigil' => 'phabricator-source',
'meta' => array(
'uri' => $log->getURI(),
),
),
$rows);
// When this is a normal AJAX request, return the rendered log fragment
// in an AJAX payload.
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'markup' => hsprintf('%s', $table),
));
}
// If the page is being accessed as a standalone page, present a
// readable version of the fragment for debugging.
require_celerity_resource('harbormaster-css');
$header = pht('Standalone Log Fragment');
$render_view = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeaderText($header)
->appendChild($table);
$page_view = id(new PHUITwoColumnView())
->setFooter($render_view);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Build Log %d', $log->getID()), $log->getURI())
->addTextCrumb(pht('Fragment'))
->setBorder(true);
return $this->newPage()
->setTitle(
array(
pht('Build Log %d', $log->getID()),
pht('Standalone Fragment'),
))
->setCrumbs($crumbs)
->appendChild($page_view);
}
private function getTotalByteLength(HarbormasterBuildLog $log) {
$total_bytes = $log->getByteLength();
if ($total_bytes) {
return (int)$total_bytes;
}
// TODO: Remove this after enough time has passed for installs to run
// log rebuilds or decide they don't care about older logs.
// Older logs don't have this data denormalized onto the log record unless
// an administrator has run `bin/harbormaster rebuild-log --all` or
// similar. Try to figure it out by summing up the size of each chunk.
// Note that the log may also be legitimately empty and have actual size
// zero.
$chunk = new HarbormasterBuildLogChunk();
$conn = $chunk->establishConnection('r');
$row = queryfx_one(
$conn,
'SELECT SUM(size) total FROM %T WHERE logID = %d',
$chunk->getTableName(),
$log->getID());
return (int)$row['total'];
}
private function getLines($data) {
$parts = preg_split("/(\r\n|\r|\n)/", $data, 0, PREG_SPLIT_DELIM_CAPTURE);
if (last($parts) === '') {
array_pop($parts);
}
$lines = array();
for ($ii = 0; $ii < count($parts); $ii += 2) {
$line = $parts[$ii];
if (isset($parts[$ii + 1])) {
$line .= $parts[$ii + 1];
}
$lines[] = $line;
}
return $lines;
}
private function mergeOverlappingReads(array $reads) {
// Find planned reads which will overlap and merge them into a single
// larger read.
$uk = array_keys($reads);
$vk = array_keys($reads);
foreach ($uk as $ukey) {
foreach ($vk as $vkey) {
// Don't merge a range into itself, even though they do technically
// overlap.
if ($ukey === $vkey) {
continue;
}
$uread = idx($reads, $ukey);
if ($uread === null) {
continue;
}
$vread = idx($reads, $vkey);
if ($vread === null) {
continue;
}
$us = $uread['fetchOffset'];
$ue = $us + $uread['fetchLength'];
$vs = $vread['fetchOffset'];
$ve = $vs + $vread['fetchLength'];
if (($vs > $ue) || ($ve < $us)) {
continue;
}
$min = min($us, $vs);
$max = max($ue, $ve);
$reads[$ukey]['fetchOffset'] = $min;
$reads[$ukey]['fetchLength'] = ($max - $min);
$reads[$ukey]['fetchLine'] = min(
$uread['fetchLine'],
$vread['fetchLine']);
unset($reads[$vkey]);
}
}
return $reads;
}
private function mergeOverlappingViews(array $views) {
$uk = array_keys($views);
$vk = array_keys($views);
$body_lines = 8;
$body_bytes = ($body_lines * 140);
foreach ($uk as $ukey) {
foreach ($vk as $vkey) {
if ($ukey === $vkey) {
continue;
}
$uview = idx($views, $ukey);
if ($uview === null) {
continue;
}
$vview = idx($views, $vkey);
if ($vview === null) {
continue;
}
// If these views don't use the same line data, don't try to
// merge them.
if ($uview['sliceKey'] != $vview['sliceKey']) {
continue;
}
// If these views are overlapping or separated by only a few bytes,
// merge them into a single view.
$us = $uview['viewOffset'];
$ue = $us + $uview['viewLength'];
$vs = $vview['viewOffset'];
$ve = $vs + $vview['viewLength'];
// Don't merge if one of the slices starts at a byte offset
// significantly after the other ends.
if (($vs > $ue + $body_bytes) || ($us > $ve + $body_bytes)) {
continue;
}
$uss = $uview['sliceOffset'];
$use = $uss + $uview['sliceLength'];
$vss = $vview['sliceOffset'];
$vse = $vss + $vview['sliceLength'];
// Don't merge if one of the slices starts at a line offset
// significantly after the other ends.
if ($uss > ($vse + $body_lines) || $vss > ($use + $body_lines)) {
continue;
}
// These views are overlapping or nearly overlapping, so we merge
// them. We merge views even if they aren't exactly adjacent since
// it's silly to render an "expand more" which only expands a couple
// of lines.
$offset = min($us, $vs);
$length = max($ue, $ve) - $offset;
$slice_offset = min($uss, $vss);
$slice_length = max($use, $vse) - $slice_offset;
$views[$ukey] = array(
'viewOffset' => $offset,
'viewLength' => $length,
'sliceOffset' => $slice_offset,
'sliceLength' => $slice_length,
) + $views[$ukey];
unset($views[$vkey]);
}
}
return $views;
}
private function renderExpandRow($range) {
$icon_up = id(new PHUIIconView())
->setIcon('fa-chevron-up');
$icon_down = id(new PHUIIconView())
->setIcon('fa-chevron-down');
$up_text = array(
pht('Show More Above'),
' ',
$icon_up,
);
$expand_up = javelin_tag(
'a',
array(
'sigil' => 'harbormaster-log-expand',
'meta' => array(
'headOffset' => $range['head'],
'tailOffset' => $range['tail'],
'head' => 128,
'tail' => 0,
),
),
$up_text);
$mid_text = pht(
'Show More (%s Bytes)',
new PhutilNumber($range['tail'] - $range['head']));
$expand_mid = javelin_tag(
'a',
array(
'sigil' => 'harbormaster-log-expand',
'meta' => array(
'headOffset' => $range['head'],
'tailOffset' => $range['tail'],
'head' => 128,
'tail' => 128,
),
),
$mid_text);
$down_text = array(
$icon_down,
' ',
pht('Show More Below'),
);
$expand_down = javelin_tag(
'a',
array(
'sigil' => 'harbormaster-log-expand',
'meta' => array(
'headOffset' => $range['head'],
'tailOffset' => $range['tail'],
'head' => 0,
'tail' => 128,
),
),
$down_text);
$expand_cells = array(
phutil_tag(
'td',
array(
'class' => 'harbormaster-log-expand-up',
),
$expand_up),
phutil_tag(
'td',
array(
'class' => 'harbormaster-log-expand-mid',
),
$expand_mid),
phutil_tag(
'td',
array(
'class' => 'harbormaster-log-expand-down',
),
$expand_down),
);
return $this->renderActionTable($expand_cells);
}
private function renderLiveRow($log_size) {
$icon_down = id(new PHUIIconView())
->setIcon('fa-angle-double-down');
$icon_pause = id(new PHUIIconView())
->setIcon('fa-pause');
$follow = javelin_tag(
'a',
array(
'sigil' => 'harbormaster-log-expand harbormaster-log-live',
'class' => 'harbormaster-log-follow-start',
'meta' => array(
'headOffset' => $log_size,
'head' => 0,
'tail' => 1024,
'live' => true,
),
),
array(
$icon_down,
' ',
pht('Follow Log'),
));
$stop_following = javelin_tag(
'a',
array(
'sigil' => 'harbormaster-log-expand',
'class' => 'harbormaster-log-follow-stop',
'meta' => array(
'stop' => true,
),
),
array(
$icon_pause,
' ',
pht('Stop Following Log'),
));
$expand_cells = array(
phutil_tag(
'td',
array(
'class' => 'harbormaster-log-follow',
),
array(
$follow,
$stop_following,
)),
);
return $this->renderActionTable($expand_cells);
}
private function renderActionTable(array $action_cells) {
$action_row = phutil_tag('tr', array(), $action_cells);
$action_table = phutil_tag(
'table',
array(
'class' => 'harbormaster-log-expand-table',
),
$action_row);
$format_cells = array(
phutil_tag('th', array()),
phutil_tag(
'td',
array(
'class' => 'harbormaster-log-expand-cell',
),
$action_table),
);
return phutil_tag('tr', array(), $format_cells);
}
private function getHighlightViews(
HarbormasterBuildLog $log,
array $range,
$log_size) {
// If we're highlighting a line range in the file, we first need to figure
// out the offsets for the lines we care about.
list($range_min, $range_max) = $range;
// Read the markers to find a range we can load which includes both lines.
$read_range = $log->getLineSpanningRange($range_min, $range_max);
list($min_pos, $max_pos, $min_line) = $read_range;
$length = ($max_pos - $min_pos);
// Reject to do the read if it requires us to examine a huge amount of
// data. For example, the user may request lines "$1-1000" of a file where
// each line has 100MB of text.
$limit = (1024 * 1024 * 16);
if ($length > $limit) {
return array();
}
$data = $log->loadData($min_pos, $length);
$offset = $min_pos;
$min_offset = null;
$max_offset = null;
$lines = $this->getLines($data);
$number = ($min_line + 1);
foreach ($lines as $line) {
if ($min_offset === null) {
if ($number === $range_min) {
$min_offset = $offset;
}
}
$offset += strlen($line);
if ($max_offset === null) {
if ($number === $range_max) {
$max_offset = $offset;
break;
}
}
$number += 1;
}
$context_lines = 8;
// Build views around the beginning and ends of the respective lines. We
// expect these views to overlap significantly in normal circumstances
// and be merged later.
$views = array();
if ($min_offset !== null) {
$views[] = array(
'offset' => $min_offset,
'lines' => $context_lines + ($range_max - $range_min) - 1,
'direction' => 1,
'limit' => null,
);
if ($min_offset > 0) {
$views[] = array(
'offset' => $min_offset,
'lines' => $context_lines,
'direction' => -1,
'limit' => null,
);
}
}
if ($max_offset !== null) {
$views[] = array(
'offset' => $max_offset,
'lines' => $context_lines + ($range_max - $range_min),
'direction' => -1,
'limit' => null,
);
if ($max_offset < $log_size) {
$views[] = array(
'offset' => $max_offset,
'lines' => $context_lines,
'direction' => 1,
'limit' => null,
);
}
}
return $views;
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php
index 888b3bddb2..0128dad0b6 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php
@@ -1,51 +1,55 @@
<?php
final class HarbormasterBuildLogViewController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$log = id(new HarbormasterBuildLogQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$log) {
return new Aphront404Response();
}
$target = $log->getBuildTarget();
$build = $target->getBuild();
$page_title = pht('Build Log %d', $log->getID());
$log_view = id(new HarbormasterBuildLogView())
->setViewer($viewer)
->setBuildLog($log)
->setHighlightedLineRange($request->getURIData('lines'))
->setEnableHighlighter(true);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Build Logs'))
->addTextCrumb(
pht('Build %d', $build->getID()),
$build->getURI())
->addTextCrumb($page_title)
->setBorder(true);
$page_header = id(new PHUIHeaderView())
->setHeader($page_title);
$page_view = id(new PHUITwoColumnView())
->setHeader($page_header)
->setFooter($log_view);
return $this->newPage()
->setTitle($page_title)
->setCrumbs($crumbs)
->appendChild($page_view);
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
index ac3a7101fd..b0f2654ea6 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
@@ -1,776 +1,780 @@
<?php
final class HarbormasterBuildViewController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $request->getURIData('id');
$build = id(new HarbormasterBuildQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$build) {
return new Aphront404Response();
}
require_celerity_resource('harbormaster-css');
$title = pht('Build %d', $id);
$warnings = array();
$page_header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($viewer)
->setPolicyObject($build)
->setHeaderIcon('fa-cubes');
$is_restarting = $build->isRestarting();
if ($is_restarting) {
$page_header->setStatus(
'fa-exclamation-triangle', 'red', pht('Restarting'));
} else if ($build->isPausing()) {
$page_header->setStatus(
'fa-exclamation-triangle', 'red', pht('Pausing'));
} else if ($build->isResuming()) {
$page_header->setStatus(
'fa-exclamation-triangle', 'red', pht('Resuming'));
} else if ($build->isAborting()) {
$page_header->setStatus(
'fa-exclamation-triangle', 'red', pht('Aborting'));
}
$max_generation = (int)$build->getBuildGeneration();
if ($max_generation === 0) {
$min_generation = 0;
} else {
$min_generation = 1;
}
if ($is_restarting) {
$max_generation = $max_generation + 1;
}
$generation = $request->getURIData('generation');
if ($generation === null) {
$generation = $max_generation;
} else {
$generation = (int)$generation;
}
if ($generation < $min_generation || $generation > $max_generation) {
return new Aphront404Response();
}
if ($generation < $max_generation) {
$warnings[] = pht(
'You are viewing an older run of this build. %s',
phutil_tag(
'a',
array(
'href' => $build->getURI(),
),
pht('View Current Build')));
}
$curtain = $this->buildCurtainView($build);
$properties = $this->buildPropertyList($build);
$history = $this->buildHistoryTable(
$build,
$generation,
$min_generation,
$max_generation);
$crumbs = $this->buildApplicationCrumbs();
$this->addBuildableCrumb($crumbs, $build->getBuildable());
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$build_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($viewer)
->needBuildSteps(true)
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($generation))
->execute();
if ($build_targets) {
$messages = id(new HarbormasterBuildMessageQuery())
->setViewer($viewer)
->withReceiverPHIDs(mpull($build_targets, 'getPHID'))
->execute();
$messages = mgroup($messages, 'getReceiverPHID');
} else {
$messages = array();
}
if ($build_targets) {
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer($viewer)
->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))
->execute();
$artifacts = msort($artifacts, 'getArtifactKey');
$artifacts = mgroup($artifacts, 'getBuildTargetPHID');
} else {
$artifacts = array();
}
$targets = array();
foreach ($build_targets as $build_target) {
$header = id(new PHUIHeaderView())
->setHeader($build_target->getName())
->setUser($viewer)
->setHeaderIcon('fa-bullseye');
$target_box = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeader($header);
$tab_group = new PHUITabGroupView();
$target_box->addTabGroup($tab_group);
$property_list = new PHUIPropertyListView();
$target_artifacts = idx($artifacts, $build_target->getPHID(), array());
$links = array();
$type_uri = HarbormasterURIArtifact::ARTIFACTCONST;
foreach ($target_artifacts as $artifact) {
if ($artifact->getArtifactType() == $type_uri) {
$impl = $artifact->getArtifactImplementation();
if ($impl->isExternalLink()) {
$links[] = $impl->renderLink();
}
}
}
if ($links) {
$links = phutil_implode_html(phutil_tag('br'), $links);
$property_list->addProperty(
pht('External Link'),
$links);
}
$status_view = new PHUIStatusListView();
$item = new PHUIStatusItemView();
$status = $build_target->getTargetStatus();
$status_name =
HarbormasterBuildTarget::getBuildTargetStatusName($status);
$icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
$color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
$item->setTarget($status_name);
$item->setIcon($icon, $color);
$status_view->addItem($item);
$when = array();
$started = $build_target->getDateStarted();
$now = PhabricatorTime::getNow();
if ($started) {
$ended = $build_target->getDateCompleted();
if ($ended) {
$when[] = pht(
'Completed at %s',
phabricator_datetime($ended, $viewer));
$duration = ($ended - $started);
if ($duration) {
$when[] = pht(
'Built for %s',
phutil_format_relative_time_detailed($duration));
} else {
$when[] = pht('Built instantly');
}
} else {
$when[] = pht(
'Started at %s',
phabricator_datetime($started, $viewer));
$duration = ($now - $started);
if ($duration) {
$when[] = pht(
'Running for %s',
phutil_format_relative_time_detailed($duration));
}
}
} else {
$created = $build_target->getDateCreated();
$when[] = pht(
'Queued at %s',
phabricator_datetime($started, $viewer));
$duration = ($now - $created);
if ($duration) {
$when[] = pht(
'Waiting for %s',
phutil_format_relative_time_detailed($duration));
}
}
$property_list->addProperty(
pht('When'),
phutil_implode_html(" \xC2\xB7 ", $when));
$property_list->addProperty(pht('Status'), $status_view);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Overview'))
->setKey('overview')
->appendChild($property_list));
$step = $build_target->getBuildStep();
if ($step) {
$description = $step->getDescription();
if ($description) {
$description = new PHUIRemarkupView($viewer, $description);
$property_list->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$property_list->addTextContent($description);
}
} else {
$target_box->setFormErrors(
array(
pht(
'This build step has since been deleted on the build plan. '.
'Some information may be omitted.'),
));
}
$details = $build_target->getDetails();
$property_list = new PHUIPropertyListView();
foreach ($details as $key => $value) {
$property_list->addProperty($key, $value);
}
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Configuration'))
->setKey('configuration')
->appendChild($property_list));
$variables = $build_target->getVariables();
$variables_tab = $this->buildProperties($variables);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Variables'))
->setKey('variables')
->appendChild($variables_tab));
$artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Artifacts'))
->setKey('artifacts')
->appendChild($artifacts_tab));
$build_messages = idx($messages, $build_target->getPHID(), array());
$messages_tab = $this->buildMessages($build_messages);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Messages'))
->setKey('messages')
->appendChild($messages_tab));
$property_list = new PHUIPropertyListView();
$property_list->addProperty(
pht('Build Target ID'),
$build_target->getID());
$property_list->addProperty(
pht('Build Target PHID'),
$build_target->getPHID());
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Metadata'))
->setKey('metadata')
->appendChild($property_list));
$targets[] = $target_box;
$targets[] = $this->buildLog($build, $build_target, $generation);
}
$timeline = $this->buildTransactionTimeline(
$build,
new HarbormasterBuildTransactionQuery());
$timeline->setShouldTerminate(true);
if ($warnings) {
$warnings = id(new PHUIInfoView())
->setErrors($warnings)
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
} else {
$warnings = null;
}
$view = id(new PHUITwoColumnView())
->setHeader($page_header)
->setCurtain($curtain)
->setMainColumn(
array(
$warnings,
$properties,
$history,
$targets,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildArtifacts(
HarbormasterBuildTarget $build_target,
array $artifacts) {
$viewer = $this->getViewer();
$rows = array();
foreach ($artifacts as $artifact) {
$impl = $artifact->getArtifactImplementation();
if ($impl) {
$summary = $impl->renderArtifactSummary($viewer);
$type_name = $impl->getArtifactTypeName();
} else {
$summary = pht('<Unknown Artifact Type>');
$type_name = $artifact->getType();
}
$rows[] = array(
$artifact->getArtifactKey(),
$type_name,
$summary,
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('This target has no associated artifacts.'))
->setHeaders(
array(
pht('Key'),
pht('Type'),
pht('Summary'),
))
->setColumnClasses(
array(
'pri',
'',
'wide',
));
return $table;
}
private function buildLog(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target,
$generation) {
$request = $this->getRequest();
$viewer = $request->getUser();
$limit = $request->getInt('l', 25);
$logs = id(new HarbormasterBuildLogQuery())
->setViewer($viewer)
->withBuildTargetPHIDs(array($build_target->getPHID()))
->execute();
$empty_logs = array();
$log_boxes = array();
foreach ($logs as $log) {
$start = 1;
$lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
if ($limit !== 0) {
$start = count($lines) - $limit;
if ($start >= 1) {
$lines = array_slice($lines, -$limit, $limit);
} else {
$start = 1;
}
}
$id = null;
$is_empty = false;
if (count($lines) === 1 && trim($lines[0]) === '') {
// Prevent Harbormaster from showing empty build logs.
$id = celerity_generate_unique_node_id();
$empty_logs[] = $id;
$is_empty = true;
}
$log_view = new ShellLogView();
$log_view->setLines($lines);
$log_view->setStart($start);
$subheader = $this->createLogHeader($build, $log, $limit, $generation);
$prototype_view = id(new PHUIButtonView())
->setTag('a')
->setHref($log->getURI())
->setIcon('fa-file-text-o')
->setText(pht('New View (Prototype)'));
$header = id(new PHUIHeaderView())
->setHeader(pht(
'Build Log %d (%s - %s)',
$log->getID(),
$log->getLogSource(),
$log->getLogType()))
->addActionLink($prototype_view)
->setSubheader($subheader)
->setUser($viewer);
$log_box = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($log_view);
if ($is_empty) {
$log_box = phutil_tag(
'div',
array(
'style' => 'display: none',
'id' => $id,
),
$log_box);
}
$log_boxes[] = $log_box;
}
if ($empty_logs) {
$hide_id = celerity_generate_unique_node_id();
Javelin::initBehavior('phabricator-reveal-content');
$expand = phutil_tag(
'div',
array(
'id' => $hide_id,
'class' => 'harbormaster-empty-logs-are-hidden',
),
array(
pht(
'%s empty logs are hidden.',
phutil_count($empty_logs)),
' ',
javelin_tag(
'a',
array(
'href' => '#',
'sigil' => 'reveal-content',
'meta' => array(
'showIDs' => $empty_logs,
'hideIDs' => array($hide_id),
),
),
pht('Show all logs.')),
));
array_unshift($log_boxes, $expand);
}
return $log_boxes;
}
private function createLogHeader($build, $log, $limit, $generation) {
$options = array(
array(
'n' => 25,
),
array(
'n' => 50,
),
array(
'n' => 100,
),
array(
'n' => 0,
'label' => pht('Unlimited'),
),
);
$base_uri = id(new PhutilURI($build->getURI().$generation.'/'));
$links = array();
foreach ($options as $option) {
$n = $option['n'];
$label = idx($option, 'label', $n);
$is_selected = ($limit == $n);
if ($is_selected) {
$links[] = phutil_tag(
'strong',
array(),
$label);
} else {
$links[] = phutil_tag(
'a',
array(
'href' => (string)$base_uri->alter('l', $n),
),
$label);
}
}
return phutil_tag(
'span',
array(),
array(
phutil_implode_html(' - ', $links),
' ',
pht('Lines'),
));
}
private function buildCurtainView(HarbormasterBuild $build) {
$viewer = $this->getViewer();
$id = $build->getID();
$curtain = $this->newCurtainView($build);
$can_restart =
$build->canRestartBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_RESTART);
$can_pause =
$build->canPauseBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_PAUSE);
$can_resume =
$build->canResumeBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_RESUME);
$can_abort =
$build->canAbortBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_ABORT);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Restart Build'))
->setIcon('fa-repeat')
->setHref($this->getApplicationURI('/build/restart/'.$id.'/'))
->setDisabled(!$can_restart)
->setWorkflow(true));
if ($build->canResumeBuild()) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Resume Build'))
->setIcon('fa-play')
->setHref($this->getApplicationURI('/build/resume/'.$id.'/'))
->setDisabled(!$can_resume)
->setWorkflow(true));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Pause Build'))
->setIcon('fa-pause')
->setHref($this->getApplicationURI('/build/pause/'.$id.'/'))
->setDisabled(!$can_pause)
->setWorkflow(true));
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Abort Build'))
->setIcon('fa-exclamation-triangle')
->setHref($this->getApplicationURI('/build/abort/'.$id.'/'))
->setDisabled(!$can_abort)
->setWorkflow(true));
return $curtain;
}
private function buildPropertyList(HarbormasterBuild $build) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array(
$build->getBuildablePHID(),
$build->getBuildPlanPHID(),
))
->execute();
$properties->addProperty(
pht('Buildable'),
$handles[$build->getBuildablePHID()]->renderLink());
$properties->addProperty(
pht('Build Plan'),
$handles[$build->getBuildPlanPHID()]->renderLink());
$properties->addProperty(
pht('Status'),
$this->getStatus($build));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
}
private function buildHistoryTable(
HarbormasterBuild $build,
$generation,
$min_generation,
$max_generation) {
if ($max_generation === $min_generation) {
return null;
}
$viewer = $this->getViewer();
$uri = $build->getURI();
$rows = array();
$rowc = array();
for ($ii = $max_generation; $ii >= $min_generation; $ii--) {
if ($generation == $ii) {
$rowc[] = 'highlighted';
} else {
$rowc[] = null;
}
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $uri.$ii.'/',
),
pht('Run %d', $ii)),
);
}
$table = id(new AphrontTableView($rows))
->setColumnClasses(
array(
'pri wide',
))
->setRowClasses($rowc);
return id(new PHUIObjectBoxView())
->setHeaderText(pht('History'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
private function getStatus(HarbormasterBuild $build) {
$status_view = new PHUIStatusListView();
$item = new PHUIStatusItemView();
if ($build->isPausing()) {
$status_name = pht('Pausing');
$icon = PHUIStatusItemView::ICON_RIGHT;
$color = 'dark';
} else {
$status = $build->getBuildStatus();
$status_name =
HarbormasterBuildStatus::getBuildStatusName($status);
$icon = HarbormasterBuildStatus::getBuildStatusIcon($status);
$color = HarbormasterBuildStatus::getBuildStatusColor($status);
}
$item->setTarget($status_name);
$item->setIcon($icon, $color);
$status_view->addItem($item);
return $status_view;
}
private function buildMessages(array $messages) {
$viewer = $this->getRequest()->getUser();
if ($messages) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(mpull($messages, 'getAuthorPHID'))
->execute();
} else {
$handles = array();
}
$rows = array();
foreach ($messages as $message) {
$rows[] = array(
$message->getID(),
$handles[$message->getAuthorPHID()]->renderLink(),
$message->getType(),
$message->getIsConsumed() ? pht('Consumed') : null,
phabricator_datetime($message->getDateCreated(), $viewer),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(pht('No messages for this build target.'));
$table->setHeaders(
array(
pht('ID'),
pht('From'),
pht('Type'),
pht('Consumed'),
pht('Received'),
));
$table->setColumnClasses(
array(
'',
'',
'wide',
'',
'date',
));
return $table;
}
private function buildProperties(array $properties) {
ksort($properties);
$rows = array();
foreach ($properties as $key => $value) {
$rows[] = array(
$key,
$value,
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Key'),
pht('Value'),
))
->setColumnClasses(
array(
'pri right',
'wide',
));
return $table;
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
index 58e5cf8c49..2ebc934605 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -1,355 +1,357 @@
<?php
final class HarbormasterBuildableViewController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$buildable) {
return new Aphront404Response();
}
$id = $buildable->getID();
// Pull builds and build targets.
$builds = id(new HarbormasterBuildQuery())
->setViewer($viewer)
->withBuildablePHIDs(array($buildable->getPHID()))
->needBuildTargets(true)
->execute();
list($lint, $unit) = $this->renderLintAndUnit($buildable, $builds);
$buildable->attachBuilds($builds);
$object = $buildable->getBuildableObject();
$build_list = $this->buildBuildList($buildable);
$title = pht('Buildable %d', $id);
$header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($viewer)
->setPolicyObject($buildable)
->setStatus(
$buildable->getStatusIcon(),
$buildable->getStatusColor(),
$buildable->getStatusDisplayName())
->setHeaderIcon('fa-recycle');
$timeline = $this->buildTransactionTimeline(
$buildable,
new HarbormasterBuildableTransactionQuery());
$timeline->setShouldTerminate(true);
$curtain = $this->buildCurtainView($buildable);
$properties = $this->buildPropertyList($buildable);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($buildable->getMonogram());
$crumbs->setBorder(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$properties,
$lint,
$unit,
$build_list,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildCurtainView(HarbormasterBuildable $buildable) {
$viewer = $this->getViewer();
$id = $buildable->getID();
$curtain = $this->newCurtainView($buildable);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$buildable,
PhabricatorPolicyCapability::CAN_EDIT);
$can_restart = false;
$can_resume = false;
$can_pause = false;
$can_abort = false;
$command_restart = HarbormasterBuildCommand::COMMAND_RESTART;
$command_resume = HarbormasterBuildCommand::COMMAND_RESUME;
$command_pause = HarbormasterBuildCommand::COMMAND_PAUSE;
$command_abort = HarbormasterBuildCommand::COMMAND_ABORT;
foreach ($buildable->getBuilds() as $build) {
if ($build->canRestartBuild()) {
if ($build->canIssueCommand($viewer, $command_restart)) {
$can_restart = true;
}
}
if ($build->canResumeBuild()) {
if ($build->canIssueCommand($viewer, $command_resume)) {
$can_resume = true;
}
}
if ($build->canPauseBuild()) {
if ($build->canIssueCommand($viewer, $command_pause)) {
$can_pause = true;
}
}
if ($build->canAbortBuild()) {
if ($build->canIssueCommand($viewer, $command_abort)) {
$can_abort = true;
}
}
}
$restart_uri = "buildable/{$id}/restart/";
$pause_uri = "buildable/{$id}/pause/";
$resume_uri = "buildable/{$id}/resume/";
$abort_uri = "buildable/{$id}/abort/";
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-repeat')
->setName(pht('Restart All Builds'))
->setHref($this->getApplicationURI($restart_uri))
->setWorkflow(true)
->setDisabled(!$can_restart || !$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pause')
->setName(pht('Pause All Builds'))
->setHref($this->getApplicationURI($pause_uri))
->setWorkflow(true)
->setDisabled(!$can_pause || !$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-play')
->setName(pht('Resume All Builds'))
->setHref($this->getApplicationURI($resume_uri))
->setWorkflow(true)
->setDisabled(!$can_resume || !$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-exclamation-triangle')
->setName(pht('Abort All Builds'))
->setHref($this->getApplicationURI($abort_uri))
->setWorkflow(true)
->setDisabled(!$can_abort || !$can_edit));
return $curtain;
}
private function buildPropertyList(HarbormasterBuildable $buildable) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$container_phid = $buildable->getContainerPHID();
$buildable_phid = $buildable->getBuildablePHID();
if ($container_phid) {
$properties->addProperty(
pht('Container'),
$viewer->renderHandle($container_phid));
}
$properties->addProperty(
pht('Buildable'),
$viewer->renderHandle($buildable_phid));
$properties->addProperty(
pht('Origin'),
$buildable->getIsManualBuildable()
? pht('Manual Buildable')
: pht('Automatic Buildable'));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
}
private function buildBuildList(HarbormasterBuildable $buildable) {
$viewer = $this->getRequest()->getUser();
$build_list = id(new PHUIObjectItemListView())
->setUser($viewer);
foreach ($buildable->getBuilds() as $build) {
$view_uri = $this->getApplicationURI('/build/'.$build->getID().'/');
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Build %d', $build->getID()))
->setHeader($build->getName())
->setHref($view_uri);
$status = $build->getBuildStatus();
$status_color = HarbormasterBuildStatus::getBuildStatusColor($status);
$status_name = HarbormasterBuildStatus::getBuildStatusName($status);
$item->setStatusIcon('fa-dot-circle-o '.$status_color, $status_name);
$item->addAttribute($status_name);
if ($build->isRestarting()) {
$item->addIcon('fa-repeat', pht('Restarting'));
} else if ($build->isPausing()) {
$item->addIcon('fa-pause', pht('Pausing'));
} else if ($build->isResuming()) {
$item->addIcon('fa-play', pht('Resuming'));
}
$build_id = $build->getID();
$restart_uri = "build/restart/{$build_id}/buildable/";
$resume_uri = "build/resume/{$build_id}/buildable/";
$pause_uri = "build/pause/{$build_id}/buildable/";
$abort_uri = "build/abort/{$build_id}/buildable/";
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-repeat')
->setName(pht('Restart'))
->setHref($this->getApplicationURI($restart_uri))
->setWorkflow(true)
->setDisabled(!$build->canRestartBuild()));
if ($build->canResumeBuild()) {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-play')
->setName(pht('Resume'))
->setHref($this->getApplicationURI($resume_uri))
->setWorkflow(true));
} else {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pause')
->setName(pht('Pause'))
->setHref($this->getApplicationURI($pause_uri))
->setWorkflow(true)
->setDisabled(!$build->canPauseBuild()));
}
$targets = $build->getBuildTargets();
if ($targets) {
$target_list = id(new PHUIStatusListView());
foreach ($targets as $target) {
$status = $target->getTargetStatus();
$icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
$color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
$status_name =
HarbormasterBuildTarget::getBuildTargetStatusName($status);
$name = $target->getName();
$target_list->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $color, $status_name)
->setTarget(pht('Target %d', $target->getID()))
->setNote($name));
}
$target_box = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_SMALL)
->appendChild($target_list);
$item->appendChild($target_box);
}
$build_list->addItem($item);
}
$build_list->setFlush(true);
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Builds'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($build_list);
return $box;
}
private function renderLintAndUnit(
HarbormasterBuildable $buildable,
array $builds) {
$viewer = $this->getViewer();
$targets = array();
foreach ($builds as $build) {
foreach ($build->getBuildTargets() as $target) {
$targets[] = $target;
}
}
if (!$targets) {
return;
}
$target_phids = mpull($targets, 'getPHID');
$lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$target_phids);
$unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$target_phids);
if ($lint_data) {
$lint_table = id(new HarbormasterLintPropertyView())
->setUser($viewer)
->setLimit(10)
->setLintMessages($lint_data);
$lint_href = $this->getApplicationURI('lint/'.$buildable->getID().'/');
$lint_header = id(new PHUIHeaderView())
->setHeader(pht('Lint Messages'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($lint_href)
->setIcon('fa-list-ul')
->setText('View All'));
$lint = id(new PHUIObjectBoxView())
->setHeader($lint_header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($lint_table);
} else {
$lint = null;
}
if ($unit_data) {
$unit = id(new HarbormasterUnitSummaryView())
->setBuildable($buildable)
->setUnitMessages($unit_data)
->setShowViewAll(true)
->setLimit(5);
} else {
$unit = null;
}
return array($lint, $unit);
}
-
-
}
diff --git a/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php b/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php
index e7636dbbd8..8fe1edbcaf 100644
--- a/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php
+++ b/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php
@@ -1,71 +1,75 @@
<?php
final class HarbormasterLintMessagesController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->needBuilds(true)
->needTargets(true)
->executeOne();
if (!$buildable) {
return new Aphront404Response();
}
$id = $buildable->getID();
$target_phids = array();
foreach ($buildable->getBuilds() as $build) {
foreach ($build->getBuildTargets() as $target) {
$target_phids[] = $target->getPHID();
}
}
$lint_data = array();
if ($target_phids) {
$lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$target_phids);
} else {
$lint_data = array();
}
$lint_table = id(new HarbormasterLintPropertyView())
->setUser($viewer)
->setLintMessages($lint_data);
$lint = id(new PHUIObjectBoxView())
->setHeaderText(pht('Lint Messages'))
->appendChild($lint_table);
$crumbs = $this->buildApplicationCrumbs();
$this->addBuildableCrumb($crumbs, $buildable);
$crumbs->addTextCrumb(pht('Lint'));
$crumbs->setBorder(true);
$title = array(
$buildable->getMonogram(),
pht('Lint'),
);
$header = id(new PHUIHeaderView())
->setHeader($title);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$lint,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
index 28bd16456d..6ebadf7a62 100644
--- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
@@ -1,440 +1,444 @@
<?php
final class HarbormasterPlanViewController extends HarbormasterPlanController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
$timeline = $this->buildTransactionTimeline(
$plan,
new HarbormasterBuildPlanTransactionQuery());
$timeline->setShouldTerminate(true);
$title = $plan->getName();
$header = id(new PHUIHeaderView())
->setHeader($plan->getName())
->setUser($viewer)
->setPolicyObject($plan)
->setHeaderIcon('fa-ship');
$curtain = $this->buildCurtainView($plan);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Plan %d', $id));
$crumbs->setBorder(true);
list($step_list, $has_any_conflicts, $would_deadlock) =
$this->buildStepList($plan);
$error = null;
if ($would_deadlock) {
$error = pht('This build plan will deadlock when executed, due to '.
'circular dependencies present in the build plan. '.
'Examine the step list and resolve the deadlock.');
} else if ($has_any_conflicts) {
// A deadlocking build will also cause all the artifacts to be
// invalid, so we just skip showing this message if that's the
// case.
$error = pht('This build plan has conflicts in one or more build steps. '.
'Examine the step list and resolve the listed errors.');
}
if ($error) {
$error = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->appendChild($error);
}
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$error,
$step_list,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildStepList(HarbormasterBuildPlan $plan) {
$viewer = $this->getViewer();
$run_order = HarbormasterBuildGraph::determineDependencyExecution($plan);
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($viewer)
->withBuildPlanPHIDs(array($plan->getPHID()))
->execute();
$steps = mpull($steps, null, 'getPHID');
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
$step_list = id(new PHUIObjectItemListView())
->setUser($viewer)
->setNoDataString(
pht('This build plan does not have any build steps yet.'));
$i = 1;
$last_depth = 0;
$has_any_conflicts = false;
$is_deadlocking = false;
foreach ($run_order as $run_ref) {
$step = $steps[$run_ref['node']->getPHID()];
$depth = $run_ref['depth'] + 1;
if ($last_depth !== $depth) {
$last_depth = $depth;
$i = 1;
} else {
$i++;
}
$step_id = $step->getID();
$view_uri = $this->getApplicationURI("step/view/{$step_id}/");
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Step %d.%d', $depth, $i))
->setHeader($step->getName())
->setHref($view_uri);
$step_list->addItem($item);
$implementation = null;
try {
$implementation = $step->getStepImplementation();
} catch (Exception $ex) {
// We can't initialize the implementation. This might be because
// it's been renamed or no longer exists.
$item
->setStatusIcon('fa-warning red')
->addAttribute(pht(
'This step has an invalid implementation (%s).',
$step->getClassName()));
continue;
}
$item->addAttribute($implementation->getDescription());
$item->setHref($view_uri);
$depends = $step->getStepImplementation()->getDependencies($step);
$inputs = $step->getStepImplementation()->getArtifactInputs();
$outputs = $step->getStepImplementation()->getArtifactOutputs();
$has_conflicts = false;
if ($depends || $inputs || $outputs) {
$available_artifacts =
HarbormasterBuildStepImplementation::getAvailableArtifacts(
$plan,
$step,
null);
$available_artifacts = ipull($available_artifacts, 'type');
list($depends_ui, $has_conflicts) = $this->buildDependsOnList(
$depends,
pht('Depends On'),
$steps);
list($inputs_ui, $has_conflicts) = $this->buildArtifactList(
$inputs,
'in',
pht('Input Artifacts'),
$available_artifacts);
list($outputs_ui) = $this->buildArtifactList(
$outputs,
'out',
pht('Output Artifacts'),
array());
$item->appendChild(
phutil_tag(
'div',
array(
'class' => 'harbormaster-artifact-io',
),
array(
$depends_ui,
$inputs_ui,
$outputs_ui,
)));
}
if ($has_conflicts) {
$has_any_conflicts = true;
$item->setStatusIcon('fa-warning red');
}
if ($run_ref['cycle']) {
$is_deadlocking = true;
}
if ($is_deadlocking) {
$item->setStatusIcon('fa-warning red');
}
}
$step_list->setFlush(true);
$plan_id = $plan->getID();
$header = id(new PHUIHeaderView())
->setHeader(pht('Build Steps'))
->addActionLink(
id(new PHUIButtonView())
->setText(pht('Add Build Step'))
->setHref($this->getApplicationURI("step/add/{$plan_id}/"))
->setTag('a')
->setIcon('fa-plus')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$step_box = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($step_list);
return array($step_box, $has_any_conflicts, $is_deadlocking);
}
private function buildCurtainView(HarbormasterBuildPlan $plan) {
$viewer = $this->getViewer();
$id = $plan->getID();
$curtain = $this->newCurtainView($plan);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Plan'))
->setHref($this->getApplicationURI("plan/edit/{$id}/"))
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit)
->setIcon('fa-pencil'));
if ($plan->isDisabled()) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Enable Plan'))
->setHref($this->getApplicationURI("plan/disable/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_edit)
->setIcon('fa-check'));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Disable Plan'))
->setHref($this->getApplicationURI("plan/disable/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_edit)
->setIcon('fa-ban'));
}
$can_run = ($can_edit && $plan->canRunManually());
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Run Plan Manually'))
->setHref($this->getApplicationURI("plan/run/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_run)
->setIcon('fa-play-circle'));
$curtain->addPanel(
id(new PHUICurtainPanelView())
->setHeaderText(pht('Created'))
->appendChild(phabricator_datetime($plan->getDateCreated(), $viewer)));
return $curtain;
}
private function buildArtifactList(
array $artifacts,
$kind,
$name,
array $available_artifacts) {
$has_conflicts = false;
if (!$artifacts) {
return array(null, $has_conflicts);
}
$this->requireResource('harbormaster-css');
$header = phutil_tag(
'div',
array(
'class' => 'harbormaster-artifact-summary-header',
),
$name);
$is_input = ($kind == 'in');
$list = new PHUIStatusListView();
foreach ($artifacts as $artifact) {
$error = null;
$key = idx($artifact, 'key');
if (!strlen($key)) {
$bound = phutil_tag('em', array(), pht('(null)'));
if ($is_input) {
// This is an unbound input. For now, all inputs are always required.
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'red';
$icon_label = pht('Required Input');
$has_conflicts = true;
$error = pht('This input is required, but not configured.');
} else {
// This is an unnamed output. Outputs do not necessarily need to be
// named.
$icon = PHUIStatusItemView::ICON_OPEN;
$color = 'bluegrey';
$icon_label = pht('Unused Output');
}
} else {
$bound = phutil_tag('strong', array(), $key);
if ($is_input) {
if (isset($available_artifacts[$key])) {
if ($available_artifacts[$key] == idx($artifact, 'type')) {
$icon = PHUIStatusItemView::ICON_ACCEPT;
$color = 'green';
$icon_label = pht('Valid Input');
} else {
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'red';
$icon_label = pht('Bad Input Type');
$has_conflicts = true;
$error = pht(
'This input is bound to the wrong artifact type. It is bound '.
'to a "%s" artifact, but should be bound to a "%s" artifact.',
$available_artifacts[$key],
idx($artifact, 'type'));
}
} else {
$icon = PHUIStatusItemView::ICON_QUESTION;
$color = 'red';
$icon_label = pht('Unknown Input');
$has_conflicts = true;
$error = pht(
'This input is bound to an artifact ("%s") which does not exist '.
'at this stage in the build process.',
$key);
}
} else {
$icon = PHUIStatusItemView::ICON_DOWN;
$color = 'green';
$icon_label = pht('Valid Output');
}
}
if ($error) {
$note = array(
phutil_tag('strong', array(), pht('ERROR:')),
' ',
$error,
);
} else {
$note = $bound;
}
$list->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $color, $icon_label)
->setTarget($artifact['name'])
->setNote($note));
}
$ui = array(
$header,
$list,
);
return array($ui, $has_conflicts);
}
private function buildDependsOnList(
array $step_phids,
$name,
array $steps) {
$has_conflicts = false;
if (count($step_phids) === 0) {
return null;
}
$this->requireResource('harbormaster-css');
$steps = mpull($steps, null, 'getPHID');
$header = phutil_tag(
'div',
array(
'class' => 'harbormaster-artifact-summary-header',
),
$name);
$list = new PHUIStatusListView();
foreach ($step_phids as $step_phid) {
$error = null;
if (idx($steps, $step_phid) === null) {
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'red';
$icon_label = pht('Missing Dependency');
$has_conflicts = true;
$error = pht(
"This dependency specifies a build step which doesn't exist.");
} else {
$bound = phutil_tag(
'strong',
array(),
idx($steps, $step_phid)->getName());
$icon = PHUIStatusItemView::ICON_ACCEPT;
$color = 'green';
$icon_label = pht('Valid Input');
}
if ($error) {
$note = array(
phutil_tag('strong', array(), pht('ERROR:')),
' ',
$error,
);
} else {
$note = $bound;
}
$list->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $color, $icon_label)
->setTarget(pht('Build Step'))
->setNote($note));
}
$ui = array(
$header,
$list,
);
return array($ui, $has_conflicts);
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterStepViewController.php b/src/applications/harbormaster/controller/HarbormasterStepViewController.php
index a404b48e88..d4f1592264 100644
--- a/src/applications/harbormaster/controller/HarbormasterStepViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterStepViewController.php
@@ -1,144 +1,148 @@
<?php
final class HarbormasterStepViewController
extends HarbormasterPlanController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$step = id(new HarbormasterBuildStepQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$step) {
return new Aphront404Response();
}
$plan = $step->getBuildPlan();
$plan_id = $plan->getID();
$plan_uri = $this->getApplicationURI("plan/{$plan_id}/");
$field_list = PhabricatorCustomField::getObjectFields(
$step,
PhabricatorCustomField::ROLE_VIEW);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($step);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Plan %d', $plan_id), $plan_uri);
$crumbs->addTextCrumb(pht('Step %d', $id));
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Build Step %d: %s', $id, $step->getName()))
->setHeaderIcon('fa-chevron-circle-right');
$properties = $this->buildPropertyList($step, $field_list);
$curtain = $this->buildCurtainView($step);
$timeline = $this->buildTransactionTimeline(
$step,
new HarbormasterBuildStepTransactionQuery());
$timeline->setShouldTerminate(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$properties,
$timeline,
));
return $this->newPage()
->setTitle(pht('Step %d', $id))
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildPropertyList(
HarbormasterBuildStep $step,
PhabricatorCustomFieldList $field_list) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
try {
$implementation = $step->getStepImplementation();
} catch (Exception $ex) {
$implementation = null;
}
if ($implementation) {
$type = $implementation->getName();
} else {
$type = phutil_tag(
'em',
array(),
pht(
'Invalid Implementation ("%s")!',
$step->getClassName()));
}
$view->addProperty(pht('Step Type'), $type);
$view->addProperty(
pht('Created'),
phabricator_datetime($step->getDateCreated(), $viewer));
$field_list->appendFieldsToPropertyList(
$step,
$viewer,
$view);
$description = $step->getDescription();
if (strlen($description)) {
$view->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent(
new PHUIRemarkupView($viewer, $description));
}
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($view);
}
private function buildCurtainView(HarbormasterBuildStep $step) {
$viewer = $this->getViewer();
$id = $step->getID();
$curtain = $this->newCurtainView($step);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$step,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Step'))
->setHref($this->getApplicationURI("step/edit/{$id}/"))
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit)
->setIcon('fa-pencil'));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete Step'))
->setHref($this->getApplicationURI("step/delete/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_edit)
->setIcon('fa-times'));
return $curtain;
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php
index 05f06a4e99..5f65bca86d 100644
--- a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php
+++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php
@@ -1,67 +1,71 @@
<?php
final class HarbormasterUnitMessageListController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->needBuilds(true)
->needTargets(true)
->executeOne();
if (!$buildable) {
return new Aphront404Response();
}
$id = $buildable->getID();
$target_phids = array();
foreach ($buildable->getBuilds() as $build) {
foreach ($build->getBuildTargets() as $target) {
$target_phids[] = $target->getPHID();
}
}
$unit_data = array();
if ($target_phids) {
$unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$target_phids);
} else {
$unit_data = array();
}
$unit = id(new HarbormasterUnitSummaryView())
->setBuildable($buildable)
->setUnitMessages($unit_data);
$crumbs = $this->buildApplicationCrumbs();
$this->addBuildableCrumb($crumbs, $buildable);
$crumbs->addTextCrumb(pht('Unit Tests'));
$crumbs->setBorder(true);
$title = array(
$buildable->getMonogram(),
pht('Unit Tests'),
);
$header = id(new PHUIHeaderView())
->setHeader($buildable->getMonogram().' '.pht('Unit Tests'));
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$unit,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php
index 631c332938..9881da06c6 100644
--- a/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php
@@ -1,128 +1,132 @@
<?php
final class HarbormasterUnitMessageViewController
extends HarbormasterController {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$message_id = $request->getURIData('id');
$message = id(new HarbormasterBuildUnitMessage())->load($message_id);
if (!$message) {
return new Aphront404Response();
}
$build_target = id(new HarbormasterBuildTargetQuery())
->setViewer($viewer)
->withPHIDs(array($message->getBuildTargetPHID()))
->executeOne();
if (!$build_target) {
return new Aphront404Response();
}
$build = $build_target->getBuild();
$buildable = $build->getBuildable();
$buildable_id = $buildable->getID();
$id = $message->getID();
$display_name = $message->getUnitMessageDisplayName();
$status = $message->getResult();
$status_icon = HarbormasterUnitStatus::getUnitStatusIcon($status);
$status_color = HarbormasterUnitStatus::getUnitStatusColor($status);
$status_label = HarbormasterUnitStatus::getUnitStatusLabel($status);
$header = id(new PHUIHeaderView())
->setHeader($display_name)
->setStatus($status_icon, $status_color, $status_label);
$properties = $this->buildPropertyListView($message);
$curtain = $this->buildCurtainView($message, $build);
$unit = id(new PHUIObjectBoxView())
->setHeaderText(pht('TEST RESULT'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($properties);
$crumbs = $this->buildApplicationCrumbs();
$this->addBuildableCrumb($crumbs, $buildable);
$crumbs->addTextCrumb(
pht('Unit Tests'),
"/harbormaster/unit/{$buildable_id}/");
$crumbs->addTextCrumb(pht('Unit %d', $id));
$crumbs->setBorder(true);
$title = array(
$display_name,
$buildable->getMonogram(),
);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$unit,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildPropertyListView(
HarbormasterBuildUnitMessage $message) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$view->addProperty(
pht('Run At'),
phabricator_datetime($message->getDateCreated(), $viewer));
$details = $message->getUnitMessageDetails();
if (strlen($details)) {
// TODO: Use the log view here, once it gets cleaned up.
// Shenanigans below.
$details = phutil_tag(
'div',
array(
'class' => 'PhabricatorMonospaced',
'style' =>
'white-space: pre-wrap; '.
'color: #666666; '.
'overflow-x: auto;',
),
$details);
} else {
$details = phutil_tag('em', array(), pht('No details provided.'));
}
$view->addSectionHeader(
pht('Details'),
PHUIPropertyListView::ICON_TESTPLAN);
$view->addTextContent($details);
return $view;
}
private function buildCurtainView(
HarbormasterBuildUnitMessage $message,
HarbormasterBuild $build) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($build);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('View Build'))
->setHref($build->getURI())
->setIcon('fa-wrench'));
return $curtain;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 15, 12:10 PM (20 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
337826
Default Alt Text
(85 KB)

Event Timeline