Use the stream decode util and never opt to use the plain query param

This commit is contained in:
Michael Lange 2019-06-26 14:52:41 -07:00
parent 862d6f6da6
commit c3b33b8420
5 changed files with 37 additions and 37 deletions

View File

@ -8,6 +8,7 @@ import queryString from 'query-string';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import StreamLogger from 'nomad-ui/utils/classes/stream-logger'; import StreamLogger from 'nomad-ui/utils/classes/stream-logger';
import PollLogger from 'nomad-ui/utils/classes/poll-logger'; import PollLogger from 'nomad-ui/utils/classes/poll-logger';
import { decode } from 'nomad-ui/utils/stream-frames';
import Anser from 'anser'; import Anser from 'anser';
const MAX_OUTPUT_LENGTH = 50000; const MAX_OUTPUT_LENGTH = 50000;
@ -73,7 +74,6 @@ const Log = EmberObject.extend(Evented, {
const logFetch = this.logFetch; const logFetch = this.logFetch;
const queryParams = queryString.stringify( const queryParams = queryString.stringify(
assign(this.params, { assign(this.params, {
plain: true,
origin: 'start', origin: 'start',
offset: 0, offset: 0,
}) })
@ -81,7 +81,8 @@ const Log = EmberObject.extend(Evented, {
const url = `${this.url}?${queryParams}`; const url = `${this.url}?${queryParams}`;
this.stop(); this.stop();
let text = yield logFetch(url).then(res => res.text(), fetchFailure(url)); const response = yield logFetch(url).then(res => res.text(), fetchFailure(url));
let text = decode(response).message;
if (text && text.length > MAX_OUTPUT_LENGTH) { if (text && text.length > MAX_OUTPUT_LENGTH) {
text = text.substr(0, MAX_OUTPUT_LENGTH); text = text.substr(0, MAX_OUTPUT_LENGTH);
@ -95,7 +96,6 @@ const Log = EmberObject.extend(Evented, {
const logFetch = this.logFetch; const logFetch = this.logFetch;
const queryParams = queryString.stringify( const queryParams = queryString.stringify(
assign(this.params, { assign(this.params, {
plain: true,
origin: 'end', origin: 'end',
offset: MAX_OUTPUT_LENGTH, offset: MAX_OUTPUT_LENGTH,
}) })
@ -103,7 +103,8 @@ const Log = EmberObject.extend(Evented, {
const url = `${this.url}?${queryParams}`; const url = `${this.url}?${queryParams}`;
this.stop(); this.stop();
let text = yield logFetch(url).then(res => res.text(), fetchFailure(url)); const response = yield logFetch(url).then(res => res.text(), fetchFailure(url));
let text = decode(response).message;
this.set('tail', text); this.set('tail', text);
this.set('logPointer', 'tail'); this.set('logPointer', 'tail');

View File

@ -1,5 +1,6 @@
import EmberObject from '@ember/object'; import EmberObject from '@ember/object';
import { task, timeout } from 'ember-concurrency'; import { task, timeout } from 'ember-concurrency';
import { decode } from 'nomad-ui/utils/stream-frames';
import AbstractLogger from './abstract-logger'; import AbstractLogger from './abstract-logger';
import { fetchFailure } from './log'; import { fetchFailure } from './log';
@ -7,9 +8,7 @@ export default EmberObject.extend(AbstractLogger, {
interval: 1000, interval: 1000,
start() { start() {
return this.poll return this.poll.linked().perform();
.linked()
.perform();
}, },
stop() { stop() {
@ -29,15 +28,10 @@ export default EmberObject.extend(AbstractLogger, {
let text = yield response.text(); let text = yield response.text();
if (text) { if (text) {
const lines = text.replace(/\}\{/g, '}\n{').split('\n'); const { offset, message } = decode(text);
const frames = lines if (message) {
.map(line => JSON.parse(line)) this.set('endOffset', offset);
.filter(frame => frame.Data != null && frame.Offset != null); this.write(message);
if (frames.length) {
frames.forEach(frame => (frame.Data = window.atob(frame.Data)));
this.set('endOffset', frames[frames.length - 1].Offset);
this.write(frames.mapBy('Data').join(''));
} }
} }

View File

@ -1,6 +1,7 @@
import EmberObject, { computed } from '@ember/object'; import EmberObject, { computed } from '@ember/object';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import TextDecoder from 'nomad-ui/utils/classes/text-decoder'; import TextDecoder from 'nomad-ui/utils/classes/text-decoder';
import { decode } from 'nomad-ui/utils/stream-frames';
import AbstractLogger from './abstract-logger'; import AbstractLogger from './abstract-logger';
import { fetchFailure } from './log'; import { fetchFailure } from './log';
@ -60,13 +61,10 @@ export default EmberObject.extend(AbstractLogger, {
// Assuming the logs endpoint never returns nested JSON (it shouldn't), at this // Assuming the logs endpoint never returns nested JSON (it shouldn't), at this
// point chunk is a series of valid JSON objects with no delimiter. // point chunk is a series of valid JSON objects with no delimiter.
const lines = chunk.replace(/\}\{/g, '}\n{').split('\n'); const { offset, message } = decode(chunk);
const frames = lines.map(line => JSON.parse(line)).filter(frame => frame.Data); if (message) {
this.set('endOffset', offset);
if (frames.length) { this.write(message);
frames.forEach(frame => (frame.Data = window.atob(frame.Data)));
this.set('endOffset', frames[frames.length - 1].Offset);
this.write(frames.mapBy('Data').join(''));
} }
} }
}); });

View File

@ -21,24 +21,23 @@ const commonProps = {
serverTimeout: allowedConnectionTime, serverTimeout: allowedConnectionTime,
}; };
const logHead = ['HEAD']; const logHead = [logEncode(['HEAD'], 0)];
const logTail = ['TAIL']; const logTail = [logEncode(['TAIL'], 0)];
const streamFrames = ['one\n', 'two\n', 'three\n', 'four\n', 'five\n']; const streamFrames = ['one\n', 'two\n', 'three\n', 'four\n', 'five\n'];
let streamPointer = 0; let streamPointer = 0;
let logMode = null;
module('Integration | Component | task log', function(hooks) { module('Integration | Component | task log', function(hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
hooks.beforeEach(function() { hooks.beforeEach(function() {
const handler = ({ queryParams }) => { const handler = ({ queryParams }) => {
const { origin, offset, plain, follow } = queryParams;
let frames; let frames;
let data; let data;
if (origin === 'start' && offset === '0' && plain && !follow) { if (logMode === 'head') {
frames = logHead; frames = logHead;
} else if (origin === 'end' && plain && !follow) { } else if (logMode === 'tail') {
frames = logTail; frames = logTail;
} else { } else {
frames = streamFrames; frames = streamFrames;
@ -64,6 +63,7 @@ module('Integration | Component | task log', function(hooks) {
hooks.afterEach(function() { hooks.afterEach(function() {
this.server.shutdown(); this.server.shutdown();
streamPointer = 0; streamPointer = 0;
logMode = null;
}); });
test('Basic appearance', async function(assert) { test('Basic appearance', async function(assert) {
@ -107,6 +107,7 @@ module('Integration | Component | task log', function(hooks) {
}); });
test('Clicking Head loads the log head', async function(assert) { test('Clicking Head loads the log head', async function(assert) {
logMode = 'head';
run.later(run, run.cancelTimers, commonProps.interval); run.later(run, run.cancelTimers, commonProps.interval);
this.setProperties(commonProps); this.setProperties(commonProps);
@ -117,7 +118,7 @@ module('Integration | Component | task log', function(hooks) {
await settled(); await settled();
assert.ok( assert.ok(
this.server.handledRequests.find( this.server.handledRequests.find(
({ queryParams: qp }) => qp.origin === 'start' && qp.plain === 'true' && qp.offset === '0' ({ queryParams: qp }) => qp.origin === 'start' && qp.offset === '0'
), ),
'Log head request was made' 'Log head request was made'
); );
@ -125,6 +126,7 @@ module('Integration | Component | task log', function(hooks) {
}); });
test('Clicking Tail loads the log tail', async function(assert) { test('Clicking Tail loads the log tail', async function(assert) {
logMode = 'tail';
run.later(run, run.cancelTimers, commonProps.interval); run.later(run, run.cancelTimers, commonProps.interval);
this.setProperties(commonProps); this.setProperties(commonProps);
@ -134,9 +136,7 @@ module('Integration | Component | task log', function(hooks) {
await settled(); await settled();
assert.ok( assert.ok(
this.server.handledRequests.find( this.server.handledRequests.find(({ queryParams: qp }) => qp.origin === 'end'),
({ queryParams: qp }) => qp.origin === 'end' && qp.plain === 'true'
),
'Log tail request was made' 'Log tail request was made'
); );
assert.equal(find('[data-test-log-cli]').textContent, logTail[0], 'Tail of the log is shown'); assert.equal(find('[data-test-log-cli]').textContent, logTail[0], 'Tail of the log is shown');

View File

@ -76,7 +76,7 @@ module('Unit | Util | Log', function(hooks) {
test('gotoHead builds the correct URL', async function(assert) { test('gotoHead builds the correct URL', async function(assert) {
const mocks = makeMocks(''); const mocks = makeMocks('');
const expectedUrl = `${mocks.url}?a=param&another=one&offset=0&origin=start&plain=true`; const expectedUrl = `${mocks.url}?a=param&another=one&offset=0&origin=start`;
const log = Log.create(mocks); const log = Log.create(mocks);
run(() => { run(() => {
@ -89,10 +89,11 @@ module('Unit | Util | Log', function(hooks) {
const longLog = Array(50001) const longLog = Array(50001)
.fill('a') .fill('a')
.join(''); .join('');
const encodedLongLog = `{"Offset":0,"Data":"${window.btoa(longLog)}"}`;
const truncationMessage = const truncationMessage =
'\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------'; '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------';
const mocks = makeMocks(longLog); const mocks = makeMocks(encodedLongLog);
const log = Log.create(mocks); const log = Log.create(mocks);
run(() => { run(() => {
@ -100,7 +101,13 @@ module('Unit | Util | Log', function(hooks) {
}); });
await settled(); await settled();
assert.ok(log.get('output').toString().endsWith(truncationMessage), 'Truncation message is shown'); assert.ok(
log
.get('output')
.toString()
.endsWith(truncationMessage),
'Truncation message is shown'
);
assert.equal( assert.equal(
log.get('output').toString().length, log.get('output').toString().length,
50000 + truncationMessage.length, 50000 + truncationMessage.length,
@ -110,7 +117,7 @@ module('Unit | Util | Log', function(hooks) {
test('gotoTail builds the correct URL', async function(assert) { test('gotoTail builds the correct URL', async function(assert) {
const mocks = makeMocks(''); const mocks = makeMocks('');
const expectedUrl = `${mocks.url}?a=param&another=one&offset=50000&origin=end&plain=true`; const expectedUrl = `${mocks.url}?a=param&another=one&offset=50000&origin=end`;
const log = Log.create(mocks); const log = Log.create(mocks);
run(() => { run(() => {