mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01: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:
parent
4466402c5a
commit
49af4165bc
5 changed files with 202 additions and 33 deletions
|
@ -416,7 +416,7 @@ return array(
|
||||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
|
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
|
||||||
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
|
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
|
||||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
'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/HeraldRuleEditor.js' => 'dca75c0e',
|
||||||
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
|
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
|
||||||
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
|
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
|
||||||
|
@ -636,7 +636,7 @@ return array(
|
||||||
'javelin-behavior-event-all-day' => 'b41537c9',
|
'javelin-behavior-event-all-day' => 'b41537c9',
|
||||||
'javelin-behavior-fancy-datepicker' => 'ecf4e799',
|
'javelin-behavior-fancy-datepicker' => 'ecf4e799',
|
||||||
'javelin-behavior-global-drag-and-drop' => '960f6a39',
|
'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-herald-rule-editor' => '7ebaeed3',
|
||||||
'javelin-behavior-high-security-warning' => 'a464fe03',
|
'javelin-behavior-high-security-warning' => 'a464fe03',
|
||||||
'javelin-behavior-history-install' => '7ee2b591',
|
'javelin-behavior-history-install' => '7ee2b591',
|
||||||
|
@ -1526,9 +1526,6 @@ return array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-quicksand',
|
'javelin-quicksand',
|
||||||
),
|
),
|
||||||
'796a8803' => array(
|
|
||||||
'javelin-behavior',
|
|
||||||
),
|
|
||||||
'7a68dda3' => array(
|
'7a68dda3' => array(
|
||||||
'owners-path-editor',
|
'owners-path-editor',
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
|
@ -1768,6 +1765,9 @@ return array(
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
'phabricator-prefab',
|
'phabricator-prefab',
|
||||||
),
|
),
|
||||||
|
'ab1173d1' => array(
|
||||||
|
'javelin-behavior',
|
||||||
|
),
|
||||||
'ab2f381b' => array(
|
'ab2f381b' => array(
|
||||||
'javelin-request',
|
'javelin-request',
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
|
|
|
@ -16,6 +16,8 @@ final class HarbormasterBuildLogRenderController
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$highlight_range = $request->getURILineRange('lines', 1000);
|
||||||
|
|
||||||
$log_size = $this->getTotalByteLength($log);
|
$log_size = $this->getTotalByteLength($log);
|
||||||
|
|
||||||
$head_lines = $request->getInt('head');
|
$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) {
|
if ($tail_lines > 0) {
|
||||||
$views[] = array(
|
$views[] = array(
|
||||||
'offset' => $tail_offset,
|
'offset' => $tail_offset,
|
||||||
|
@ -86,10 +98,11 @@ final class HarbormasterBuildLogRenderController
|
||||||
|
|
||||||
$direction = $read['direction'];
|
$direction = $read['direction'];
|
||||||
if ($direction < 0) {
|
if ($direction < 0) {
|
||||||
|
if ($offset > $read_length) {
|
||||||
$offset -= $read_length;
|
$offset -= $read_length;
|
||||||
if ($offset < 0) {
|
} else {
|
||||||
|
$read_length = $offset;
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
$read_length = $log_size;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,9 +228,11 @@ final class HarbormasterBuildLogRenderController
|
||||||
}
|
}
|
||||||
|
|
||||||
$limit = $view['limit'];
|
$limit = $view['limit'];
|
||||||
|
if ($limit !== null) {
|
||||||
if ($limit < ($view_offset + $view_length)) {
|
if ($limit < ($view_offset + $view_length)) {
|
||||||
$view_length = ($limit - $view_offset);
|
$view_length = ($limit - $view_offset);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$view_offset = $data_offset;
|
$view_offset = $data_offset;
|
||||||
$view_length = $data_length;
|
$view_length = $data_length;
|
||||||
|
@ -226,11 +241,13 @@ final class HarbormasterBuildLogRenderController
|
||||||
}
|
}
|
||||||
|
|
||||||
$limit = $view['limit'];
|
$limit = $view['limit'];
|
||||||
|
if ($limit !== null) {
|
||||||
if ($limit > $view_offset) {
|
if ($limit > $view_offset) {
|
||||||
$view_length -= ($limit - $view_offset);
|
$view_length -= ($limit - $view_offset);
|
||||||
$view_offset = $limit;
|
$view_offset = $limit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$views[$view_key] += array(
|
$views[$view_key] += array(
|
||||||
'viewOffset' => $view_offset,
|
'viewOffset' => $view_offset,
|
||||||
|
@ -325,7 +342,6 @@ final class HarbormasterBuildLogRenderController
|
||||||
}
|
}
|
||||||
|
|
||||||
$uri = $log->getURI();
|
$uri = $log->getURI();
|
||||||
$highlight_range = $request->getURIData('lines');
|
|
||||||
|
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach ($render as $range) {
|
foreach ($render as $range) {
|
||||||
|
@ -339,6 +355,16 @@ final class HarbormasterBuildLogRenderController
|
||||||
$display_line = ($line['line'] + 1);
|
$display_line = ($line['line'] + 1);
|
||||||
$display_text = ($line['data']);
|
$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(
|
$display_line = phutil_tag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
|
@ -347,7 +373,7 @@ final class HarbormasterBuildLogRenderController
|
||||||
$display_line);
|
$display_line);
|
||||||
|
|
||||||
$line_cell = phutil_tag('th', array(), $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(
|
$rows[] = phutil_tag(
|
||||||
'tr',
|
'tr',
|
||||||
|
@ -557,25 +583,43 @@ final class HarbormasterBuildLogRenderController
|
||||||
$vs = $vview['viewOffset'];
|
$vs = $vview['viewOffset'];
|
||||||
$ve = $vs + $vview['viewLength'];
|
$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'];
|
$uss = $uview['sliceOffset'];
|
||||||
$use = $uss + $uview['sliceLength'];
|
$use = $uss + $uview['sliceLength'];
|
||||||
|
|
||||||
$vss = $vview['sliceOffset'];
|
$vss = $vview['sliceOffset'];
|
||||||
$vse = $vss + $vview['sliceLength'];
|
$vse = $vss + $vview['sliceLength'];
|
||||||
|
|
||||||
if ($ue <= $vs) {
|
// Don't merge if one of the slices starts at a line offset
|
||||||
if (($ue + $body_bytes) >= $vs) {
|
// significantly after the other ends.
|
||||||
if (($use + $body_lines) >= $vss) {
|
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(
|
$views[$ukey] = array(
|
||||||
'sliceLength' => ($vse - $uss),
|
'viewOffset' => $offset,
|
||||||
'viewLength' => ($ve - $us),
|
'viewLength' => $length,
|
||||||
|
'sliceOffset' => $slice_offset,
|
||||||
|
'sliceLength' => $slice_length,
|
||||||
) + $views[$ukey];
|
) + $views[$ukey];
|
||||||
|
|
||||||
unset($views[$vkey]);
|
unset($views[$vkey]);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,7 +647,7 @@ final class HarbormasterBuildLogRenderController
|
||||||
'meta' => array(
|
'meta' => array(
|
||||||
'headOffset' => $range['head'],
|
'headOffset' => $range['head'],
|
||||||
'tailOffset' => $range['tail'],
|
'tailOffset' => $range['tail'],
|
||||||
'head' => 4,
|
'head' => 128,
|
||||||
'tail' => 0,
|
'tail' => 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -620,8 +664,8 @@ final class HarbormasterBuildLogRenderController
|
||||||
'meta' => array(
|
'meta' => array(
|
||||||
'headOffset' => $range['head'],
|
'headOffset' => $range['head'],
|
||||||
'tailOffset' => $range['tail'],
|
'tailOffset' => $range['tail'],
|
||||||
'head' => 2,
|
'head' => 128,
|
||||||
'tail' => 2,
|
'tail' => 128,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
$mid_text);
|
$mid_text);
|
||||||
|
@ -640,7 +684,7 @@ final class HarbormasterBuildLogRenderController
|
||||||
'headOffset' => $range['head'],
|
'headOffset' => $range['head'],
|
||||||
'tailOffset' => $range['tail'],
|
'tailOffset' => $range['tail'],
|
||||||
'head' => 0,
|
'head' => 0,
|
||||||
'tail' => 4,
|
'tail' => 128,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
$down_text);
|
$down_text);
|
||||||
|
@ -727,4 +771,98 @@ final class HarbormasterBuildLogRenderController
|
||||||
return phutil_tag('tr', array(), $format_cells);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,6 +212,36 @@ final class HarbormasterBuildLog
|
||||||
return $parts;
|
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) {
|
public function getReadPosition($read_offset) {
|
||||||
$position = array(0, 0);
|
$position = array(0, 0);
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,8 @@ final class HarbormasterBuildLogView extends AphrontView {
|
||||||
'harbormaster-log',
|
'harbormaster-log',
|
||||||
array(
|
array(
|
||||||
'contentNodeID' => $content_id,
|
'contentNodeID' => $content_id,
|
||||||
'renderURI' => $log->getRenderURI($this->getHighlightedLineRange()),
|
'initialURI' => $log->getRenderURI($this->getHighlightedLineRange()),
|
||||||
|
'renderURI' => $log->getRenderURI(null),
|
||||||
));
|
));
|
||||||
|
|
||||||
$box_view->appendChild($content_div);
|
$box_view->appendChild($content_div);
|
||||||
|
|
|
@ -79,7 +79,7 @@ JX.behavior('harbormaster-log', function(config) {
|
||||||
JX.DOM.setContent(contentNode, JX.$H(r.markup));
|
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)
|
new JX.Request(uri, onresponse)
|
||||||
.send();
|
.send();
|
||||||
|
|
Loading…
Reference in a new issue