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

When viewing a live build log, trap users in a small personal hell where nothing but slavish devotion to the log exists

Summary: Depends on D19152. Ref T13088. This adds live log tailing. It is probably not the final version of this feature because it prevents escape once you begin tailing a log.

Test Plan: Used `bin/harbormaster write-log --rate ...` to write a log slowly. Viewed it in the web UI. Clicked "Follow Log". Followed the log until the write finished, a lifetime later.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13088

Differential Revision: https://secure.phabricator.com/D19153
This commit is contained in:
epriestley 2018-02-28 11:36:22 -08:00
parent 21ddfe442e
commit f114b2dd7d
4 changed files with 125 additions and 20 deletions

View file

@ -78,7 +78,7 @@ return array(
'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948',
'rsrc/css/application/flag/flag.css' => 'bba8f811', 'rsrc/css/application/flag/flag.css' => 'bba8f811',
'rsrc/css/application/harbormaster/harbormaster.css' => 'c7e29d9e', 'rsrc/css/application/harbormaster/harbormaster.css' => '5dd4c2de',
'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
'rsrc/css/application/herald/herald.css' => 'cd8d0134', 'rsrc/css/application/herald/herald.css' => 'cd8d0134',
'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/report.css' => '9b9580b7',
@ -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' => 'be6974cc', 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '796a8803',
'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',
@ -579,7 +579,7 @@ return array(
'font-fontawesome' => 'e838e088', 'font-fontawesome' => 'e838e088',
'font-lato' => 'c7ccd872', 'font-lato' => 'c7ccd872',
'global-drag-and-drop-css' => 'b556a948', 'global-drag-and-drop-css' => 'b556a948',
'harbormaster-css' => 'c7e29d9e', 'harbormaster-css' => '5dd4c2de',
'herald-css' => 'cd8d0134', 'herald-css' => 'cd8d0134',
'herald-rule-editor' => 'dca75c0e', 'herald-rule-editor' => 'dca75c0e',
'herald-test-css' => 'a52e323e', 'herald-test-css' => 'a52e323e',
@ -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' => 'be6974cc', 'javelin-behavior-harbormaster-log' => '796a8803',
'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',
@ -1535,6 +1535,9 @@ 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',
@ -1889,9 +1892,6 @@ return array(
'javelin-util', 'javelin-util',
'javelin-request', 'javelin-request',
), ),
'be6974cc' => array(
'javelin-behavior',
),
'bea6e7f4' => array( 'bea6e7f4' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',

View file

@ -22,14 +22,14 @@ final class HarbormasterBuildLogRenderController
if ($head_lines === null) { if ($head_lines === null) {
$head_lines = 8; $head_lines = 8;
} }
$head_lines = min($head_lines, 100); $head_lines = min($head_lines, 1024);
$head_lines = max($head_lines, 0); $head_lines = max($head_lines, 0);
$tail_lines = $request->getInt('tail'); $tail_lines = $request->getInt('tail');
if ($tail_lines === null) { if ($tail_lines === null) {
$tail_lines = 16; $tail_lines = 16;
} }
$tail_lines = min($tail_lines, 100); $tail_lines = min($tail_lines, 1024);
$tail_lines = max($tail_lines, 0); $tail_lines = max($tail_lines, 0);
$head_offset = $request->getInt('headOffset'); $head_offset = $request->getInt('headOffset');
@ -301,7 +301,6 @@ final class HarbormasterBuildLogRenderController
); );
} }
foreach ($views as $view) { foreach ($views as $view) {
if ($spacer) { if ($spacer) {
$spacer['tail'] = $view['viewOffset']; $spacer['tail'] = $view['viewOffset'];
@ -360,6 +359,22 @@ final class HarbormasterBuildLogRenderController
} }
} }
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 = phutil_tag( $table = phutil_tag(
'table', 'table',
array( array(
@ -650,25 +665,66 @@ final class HarbormasterBuildLogRenderController
), ),
$expand_down), $expand_down),
); );
$expand_row = phutil_tag('tr', array(), $expand_cells);
$expand_table = phutil_tag( return $this->renderActionTable($expand_cells);
}
private function renderLiveRow($log_size) {
$icon_down = id(new PHUIIconView())
->setIcon('fa-chevron-down');
$follow = javelin_tag(
'a',
array(
'sigil' => 'harbormaster-log-expand harbormaster-log-live',
'meta' => array(
'headOffset' => $log_size,
'head' => 0,
'tail' => 1024,
'live' => true,
),
),
array(
$icon_down,
' ',
pht('Follow Log'),
' ',
$icon_down,
));
$expand_cells = array(
phutil_tag(
'td',
array(
'class' => 'harbormaster-log-follow',
),
$follow),
);
return $this->renderActionTable($expand_cells);
}
private function renderActionTable(array $action_cells) {
$action_row = phutil_tag('tr', array(), $action_cells);
$action_table = phutil_tag(
'table', 'table',
array( array(
'class' => 'harbormaster-log-expand-table', 'class' => 'harbormaster-log-expand-table',
), ),
$expand_row); $action_row);
$cells = array( $format_cells = array(
phutil_tag('th', array()), phutil_tag('th', array()),
phutil_tag( phutil_tag(
'td', 'td',
array( array(
'class' => 'harbormaster-log-expand-cell', 'class' => 'harbormaster-log-expand-cell',
), ),
$expand_table), $action_table),
); );
return phutil_tag('tr', array(), $cells); return phutil_tag('tr', array(), $format_cells);
} }
} }

View file

@ -97,7 +97,6 @@
'Helvetica Neue', Helvetica, Arial, sans-serif; 'Helvetica Neue', Helvetica, Arial, sans-serif;
} }
.harbormaster-log-expand-up { .harbormaster-log-expand-up {
text-align: right; text-align: right;
width: 50%; width: 50%;
@ -107,6 +106,14 @@
margin: 0 0 0px 4px; margin: 0 0 0px 4px;
} }
.harbormaster-log-follow {
text-align: center;
}
.harbormaster-log-follow .phui-icon-view {
margin: 0 4px;
}
.harbormaster-log-expand-mid { .harbormaster-log-expand-mid {
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;

View file

@ -5,6 +5,7 @@
JX.behavior('harbormaster-log', function(config) { JX.behavior('harbormaster-log', function(config) {
var contentNode = JX.$(config.contentNodeID); var contentNode = JX.$(config.contentNodeID);
var following = false;
JX.DOM.listen(contentNode, 'click', 'harbormaster-log-expand', function(e) { JX.DOM.listen(contentNode, 'click', 'harbormaster-log-expand', function(e) {
if (!e.isNormalClick()) { if (!e.isNormalClick()) {
@ -13,23 +14,64 @@ JX.behavior('harbormaster-log', function(config) {
e.kill(); e.kill();
var row = e.getNode('tag:tr'); expand(e.getTarget());
});
function expand(node) {
var row = JX.DOM.findAbove(node, 'tr');
row = JX.DOM.findAbove(row, 'tr'); row = JX.DOM.findAbove(row, 'tr');
var data = e.getNodeData('harbormaster-log-expand'); var data = JX.Stratcom.getData(node);
var uri = new JX.URI(config.renderURI) var uri = new JX.URI(config.renderURI)
.addQueryParams(data); .addQueryParams(data);
if (data.live) {
following = true;
}
var request = new JX.Request(uri, function(r) { var request = new JX.Request(uri, function(r) {
var result = JX.$H(r.markup).getNode(); var result = JX.$H(r.markup).getNode();
var rows = [].slice.apply(result.firstChild.childNodes); var rows = [].slice.apply(result.firstChild.childNodes);
// If we're following the bottom of the log, the result always includes
// the last line from the previous render. Throw it away, then add the
// new data.
if (data.live && row.previousSibling) {
JX.DOM.remove(row.previousSibling);
}
JX.DOM.replace(row, rows); JX.DOM.replace(row, rows);
if (data.live) {
// If this was a live follow, scroll the new data into view. This is
// probably intensely annoying in practice but seems cool for now.
var last_row = rows[rows.length - 1];
var tail_pos = JX.$V(last_row).y + JX.Vector.getDim(last_row).y;
var view_y = JX.Vector.getViewport().y;
JX.DOM.scrollToPosition(null, (tail_pos - view_y) + 32);
setTimeout(follow, 500);
}
}); });
request.send(); request.send();
}); }
function follow() {
if (!following) {
return;
}
var live;
try {
live = JX.DOM.find(contentNode, 'a', 'harbormaster-log-live');
} catch (e) {
return;
}
expand(live);
}
function onresponse(r) { function onresponse(r) {
JX.DOM.alterClass(contentNode, 'harbormaster-log-view-loading', false); JX.DOM.alterClass(contentNode, 'harbormaster-log-view-loading', false);