1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-19 16:58:48 +02:00

Support rendering arbitrary sections in the middle of a Harbormaster build log so links to line 3500 work

Summary:
Depends on D19162. Ref T13088. When a user links to `$1234`, we need to render a default view of the log with a piece at the head, a piece at the end, and a piece in the middle.

We also need to figure out the offset for line 1234, or multiple offsets for "1234-2345".

Since the logic views/reads mostly anticipated this it isn't too much of a mess, although there are a couple of bugs this exposes with view specifications that use combinations of parameters which were previously impossible.

Test Plan: Viewed a large log with no line marker. Viewed `$1`. Viewed `$end`. Viewed `$35-40`, etc. Expanded context around logs.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13088

Differential Revision: https://secure.phabricator.com/D19163
This commit is contained in:
epriestley 2018-03-01 06:43:24 -08:00
parent 4466402c5a
commit 49af4165bc
5 changed files with 202 additions and 33 deletions

View file

@ -416,7 +416,7 @@ return array(
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '796a8803',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'ab1173d1',
'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e',
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
@ -636,7 +636,7 @@ return array(
'javelin-behavior-event-all-day' => 'b41537c9',
'javelin-behavior-fancy-datepicker' => 'ecf4e799',
'javelin-behavior-global-drag-and-drop' => '960f6a39',
'javelin-behavior-harbormaster-log' => '796a8803',
'javelin-behavior-harbormaster-log' => 'ab1173d1',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => 'a464fe03',
'javelin-behavior-history-install' => '7ee2b591',
@ -1526,9 +1526,6 @@ return array(
'javelin-behavior',
'javelin-quicksand',
),
'796a8803' => array(
'javelin-behavior',
),
'7a68dda3' => array(
'owners-path-editor',
'javelin-behavior',
@ -1768,6 +1765,9 @@ return array(
'javelin-util',
'phabricator-prefab',
),
'ab1173d1' => array(
'javelin-behavior',
),
'ab2f381b' => array(
'javelin-request',
'javelin-behavior',

View file

@ -16,6 +16,8 @@ final class HarbormasterBuildLogRenderController
return new Aphront404Response();
}
$highlight_range = $request->getURILineRange('lines', 1000);
$log_size = $this->getTotalByteLength($log);
$head_lines = $request->getInt('head');
@ -65,6 +67,16 @@ final class HarbormasterBuildLogRenderController
);
}
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,
@ -86,10 +98,11 @@ final class HarbormasterBuildLogRenderController
$direction = $read['direction'];
if ($direction < 0) {
$offset -= $read_length;
if ($offset < 0) {
if ($offset > $read_length) {
$offset -= $read_length;
} else {
$read_length = $offset;
$offset = 0;
$read_length = $log_size;
}
}
@ -215,8 +228,10 @@ final class HarbormasterBuildLogRenderController
}
$limit = $view['limit'];
if ($limit < ($view_offset + $view_length)) {
$view_length = ($limit - $view_offset);
if ($limit !== null) {
if ($limit < ($view_offset + $view_length)) {
$view_length = ($limit - $view_offset);
}
}
} else {
$view_offset = $data_offset;
@ -226,9 +241,11 @@ final class HarbormasterBuildLogRenderController
}
$limit = $view['limit'];
if ($limit > $view_offset) {
$view_length -= ($limit - $view_offset);
$view_offset = $limit;
if ($limit !== null) {
if ($limit > $view_offset) {
$view_length -= ($limit - $view_offset);
$view_offset = $limit;
}
}
}
@ -325,7 +342,6 @@ final class HarbormasterBuildLogRenderController
}
$uri = $log->getURI();
$highlight_range = $request->getURIData('lines');
$rows = array();
foreach ($render as $range) {
@ -339,6 +355,16 @@ final class HarbormasterBuildLogRenderController
$display_line = ($line['line'] + 1);
$display_text = ($line['data']);
$cell_attr = array();
if ($highlight_range) {
if (($display_line >= $highlight_range[0]) &&
($display_line <= $highlight_range[1])) {
$cell_attr = array(
'class' => 'phabricator-source-highlight',
);
}
}
$display_line = phutil_tag(
'a',
array(
@ -347,7 +373,7 @@ final class HarbormasterBuildLogRenderController
$display_line);
$line_cell = phutil_tag('th', array(), $display_line);
$text_cell = phutil_tag('td', array(), $display_text);
$text_cell = phutil_tag('td', $cell_attr, $display_text);
$rows[] = phutil_tag(
'tr',
@ -557,25 +583,43 @@ final class HarbormasterBuildLogRenderController
$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'];
if ($ue <= $vs) {
if (($ue + $body_bytes) >= $vs) {
if (($use + $body_lines) >= $vss) {
$views[$ukey] = array(
'sliceLength' => ($vse - $uss),
'viewLength' => ($ve - $us),
) + $views[$ukey];
unset($views[$vkey]);
continue;
}
}
// 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]);
}
}
@ -603,7 +647,7 @@ final class HarbormasterBuildLogRenderController
'meta' => array(
'headOffset' => $range['head'],
'tailOffset' => $range['tail'],
'head' => 4,
'head' => 128,
'tail' => 0,
),
),
@ -620,8 +664,8 @@ final class HarbormasterBuildLogRenderController
'meta' => array(
'headOffset' => $range['head'],
'tailOffset' => $range['tail'],
'head' => 2,
'tail' => 2,
'head' => 128,
'tail' => 128,
),
),
$mid_text);
@ -640,7 +684,7 @@ final class HarbormasterBuildLogRenderController
'headOffset' => $range['head'],
'tailOffset' => $range['tail'],
'head' => 0,
'tail' => 4,
'tail' => 128,
),
),
$down_text);
@ -727,4 +771,98 @@ final class HarbormasterBuildLogRenderController
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;
}
}

View file

@ -212,6 +212,36 @@ final class HarbormasterBuildLog
return $parts;
}
public function getLineSpanningRange($min_line, $max_line) {
$map = $this->getLineMap();
if (!$map) {
throw new Exception(pht('No line map.'));
}
$min_pos = 0;
$min_line = 0;
$max_pos = $this->getByteLength();
list($map) = $map;
foreach ($map as $marker) {
list($offset, $count) = $marker;
if ($count < $min_line) {
if ($offset > $min_pos) {
$min_pos = $offset;
$min_line = $count;
}
}
if ($count > $max_line) {
$max_pos = min($max_pos, $offset);
break;
}
}
return array($min_pos, $max_pos, $min_line);
}
public function getReadPosition($read_offset) {
$position = array(0, 0);

View file

@ -67,7 +67,8 @@ final class HarbormasterBuildLogView extends AphrontView {
'harbormaster-log',
array(
'contentNodeID' => $content_id,
'renderURI' => $log->getRenderURI($this->getHighlightedLineRange()),
'initialURI' => $log->getRenderURI($this->getHighlightedLineRange()),
'renderURI' => $log->getRenderURI(null),
));
$box_view->appendChild($content_div);

View file

@ -79,7 +79,7 @@ JX.behavior('harbormaster-log', function(config) {
JX.DOM.setContent(contentNode, JX.$H(r.markup));
}
var uri = new JX.URI(config.renderURI);
var uri = new JX.URI(config.initialURI);
new JX.Request(uri, onresponse)
.send();