diff --git a/awx/ui/client/features/output/index.controller.js b/awx/ui/client/features/output/index.controller.js
index 6e11152b76..f314a28afc 100644
--- a/awx/ui/client/features/output/index.controller.js
+++ b/awx/ui/client/features/output/index.controller.js
@@ -31,12 +31,6 @@ function onFrames (events) {
}
const popCount = events.length - render.getCapacity();
- const isAttached = events.length > 0;
-
- if (!isAttached) {
- stopFollowing();
- return $q.resolve();
- }
if (!vm.isFollowing && canStartFollowing()) {
startFollowing();
@@ -58,7 +52,7 @@ function onFrames (events) {
scroll.scrollToBottom();
}
- return render.pushFrames(events);
+ return render.pushFront(events);
})
.then(() => {
if (vm.isFollowing) {
@@ -80,11 +74,18 @@ function firstRange () {
return $q.resolve();
}
+ stopFollowing();
+ lockFollow = true;
+
+ if (slide.isOnFirstPage()) {
+ scroll.resetScrollPosition();
+
+ return $q.resolve();
+ }
+
scroll.pause();
lockFrames = true;
- stopFollowing();
-
return render.clear()
.then(() => slide.getFirst())
.then(results => render.pushFront(results))
@@ -97,7 +98,7 @@ function firstRange () {
})
.finally(() => {
scroll.resume();
- lockFrames = false;
+ lockFollow = false;
});
}
@@ -112,10 +113,6 @@ function nextRange () {
return $q.resolve();
}
- if (slide.getTailCounter() >= slide.getMaxCounter()) {
- return $q.resolve();
- }
-
scroll.pause();
lockFrames = true;
@@ -129,6 +126,8 @@ function nextRange () {
.finally(() => {
scroll.resume();
lockFrames = false;
+
+ return $q.resolve();
});
}
@@ -138,8 +137,8 @@ function previousRange () {
}
scroll.pause();
- lockFrames = true;
stopFollowing();
+ lockFrames = true;
let initialPosition;
let popHeight;
@@ -182,13 +181,15 @@ function lastRange () {
.then(() => slide.getLast())
.then(results => render.pushFront(results))
.then(() => {
+ stream.setMissingCounterThreshold(slide.getTailCounter() + 1);
+
scroll.scrollToBottom();
+ lockFrames = false;
return $q.resolve();
})
.finally(() => {
scroll.resume();
- lockFrames = false;
return $q.resolve();
});
@@ -204,13 +205,12 @@ function menuLastRange () {
lockFollow = false;
- if (slide.isOnLastPage()) {
- scroll.scrollToBottom();
+ return lastRange()
+ .then(() => {
+ startFollowing();
- return $q.resolve();
- }
-
- return last();
+ return $q.resolve();
+ });
}
let followOnce;
diff --git a/awx/ui/client/features/output/render.service.js b/awx/ui/client/features/output/render.service.js
index 68831f2a1e..7d98a1be2c 100644
--- a/awx/ui/client/features/output/render.service.js
+++ b/awx/ui/client/features/output/render.service.js
@@ -58,6 +58,9 @@ function JobRenderService ($q, $sce, $window) {
this.setCollapseAll = value => {
this.state.collapseAll = value;
+ Object.keys(this.records).forEach(key => {
+ this.records[key].isCollapsed = value;
+ });
};
this.sortByCounter = (a, b) => {
@@ -76,7 +79,7 @@ function JobRenderService ($q, $sce, $window) {
// Event Data Transformation / HTML Building
//
- this.transformEventGroup = (events, streaming = false) => {
+ this.appendEventGroup = events => {
let lines = 0;
let html = '';
@@ -84,16 +87,13 @@ function JobRenderService ($q, $sce, $window) {
for (let i = 0; i <= events.length - 1; i++) {
const current = events[i];
+ const tailCounter = this.getTailCounter();
- if (streaming) {
- const tailCounter = this.getTailCounter();
+ if (tailCounter && (current.counter !== tailCounter + 1)) {
+ const missing = this.appendMissingEventGroup(current);
- if (tailCounter && (current.counter !== tailCounter + 1)) {
- const missing = this.transformMissingEventGroup(current);
-
- html += missing.html;
- lines += missing.count;
- }
+ html += missing.html;
+ lines += missing.count;
}
const line = this.transformEvent(current);
@@ -105,21 +105,39 @@ function JobRenderService ($q, $sce, $window) {
return { html, lines };
};
- this.transformMissingEventGroup = event => {
- const tail = this.lookupRecord(this.getTailCounter());
+ this.appendMissingEventGroup = event => {
+ const tailCounter = this.getTailCounter();
+ const tail = this.lookupRecord(tailCounter);
+ const tailMissing = this.isCounterMissing(tailCounter);
- if (!tail || !tail.counter) {
+ if (!tailMissing && (!tail || !tail.counter)) {
return { html: '', count: 0 };
}
- const uuid = getUUID();
+ let uuid;
+
+ if (tailMissing) {
+ uuid = this.missingCounterUUIDs[tailCounter];
+ } else {
+ uuid = getUUID();
+ }
+
const counters = [];
- for (let i = tail.counter + 1; i < event.counter; i++) {
- counters.push(i);
+ for (let i = tailCounter + 1; i < event.counter; i++) {
+ if (tailMissing) {
+ this.missingCounterRecords[uuid].counters.push(i);
+ } else {
+ counters.push(i);
+ }
+
this.missingCounterUUIDs[i] = uuid;
}
+ if (tailMissing) {
+ return { html: '', count: 0 };
+ }
+
const record = {
counters,
uuid,
@@ -127,6 +145,90 @@ function JobRenderService ($q, $sce, $window) {
end: event.start_line,
};
+ if (record.start === record.end) {
+ return { html: '', count: 0 };
+ }
+
+ this.missingCounterRecords[uuid] = record;
+
+ const html = `
`;
+ const count = 1;
+
+ return { html, count };
+ };
+
+ this.prependEventGroup = events => {
+ let lines = 0;
+ let html = '';
+
+ events.sort(this.sortByCounter);
+
+ for (let i = events.length - 1; i >= 0; i--) {
+ const current = events[i];
+ const headCounter = this.getHeadCounter();
+
+ if (headCounter && (current.counter !== headCounter - 1)) {
+ const missing = this.prependMissingEventGroup(current);
+
+ html = missing.html + html;
+ lines += missing.count;
+ }
+
+ const line = this.transformEvent(current);
+
+ html = line.html + html;
+ lines += line.count;
+ }
+
+ return { html, lines };
+ };
+
+ this.prependMissingEventGroup = event => {
+ const headCounter = this.getHeadCounter();
+ const head = this.lookupRecord(headCounter);
+ const headMissing = this.isCounterMissing(headCounter);
+
+ if (!headMissing && (!head || !head.counter)) {
+ return { html: '', count: 0 };
+ }
+
+ let uuid;
+
+ if (headMissing) {
+ uuid = this.missingCounterUUIDs[headCounter];
+ } else {
+ uuid = getUUID();
+ }
+
+ const counters = [];
+
+ for (let i = headCounter - 1; i > event.counter; i--) {
+ if (headMissing) {
+ this.missingCounterRecords[uuid].counters.unshift(i);
+ } else {
+ counters.unshift(i);
+ }
+
+ this.missingCounterUUIDs[i] = uuid;
+ }
+
+ if (headMissing) {
+ return { html: '', count: 0 };
+ }
+
+ const record = {
+ counters,
+ uuid,
+ start: event.end_line,
+ end: head.start,
+ };
+
+ if (record.start === record.end) {
+ return { html: '', count: 0 };
+ }
+
this.missingCounterRecords[uuid] = record;
const html = `
@@ -401,7 +503,7 @@ function JobRenderService ($q, $sce, $window) {
return $q.resolve();
}
- const result = this.transformEventGroup(events);
+ const result = this.prependEventGroup(events);
const html = this.trustHtml(result.html);
const newElements = angular.element(html);
@@ -411,12 +513,12 @@ function JobRenderService ($q, $sce, $window) {
.then(() => result.lines);
};
- this.append = (events, streaming = false) => {
+ this.append = events => {
if (events.length < 1) {
return $q.resolve();
}
- const result = this.transformEventGroup(events, streaming);
+ const result = this.appendEventGroup(events);
const html = this.trustHtml(result.html);
const newElements = angular.element(html);
@@ -498,10 +600,10 @@ function JobRenderService ($q, $sce, $window) {
return $q.resolve();
});
- this.pushFront = (events, streaming = false) => {
+ this.pushFront = events => {
const tail = this.getTailCounter();
- return this.append(events.filter(({ counter }) => counter > tail), streaming);
+ return this.append(events.filter(({ counter }) => counter > tail));
};
this.pushBack = events => {
@@ -511,8 +613,6 @@ function JobRenderService ($q, $sce, $window) {
return this.prepend(events.filter(({ counter }) => counter < head || counter > tail));
};
- this.pushFrames = events => this.pushFront(events, true);
-
this.popMissing = counter => {
const uuid = this.missingCounterUUIDs[counter];
diff --git a/awx/ui/client/features/output/slide.service.js b/awx/ui/client/features/output/slide.service.js
index 63a1753972..fd052cc8fc 100644
--- a/awx/ui/client/features/output/slide.service.js
+++ b/awx/ui/client/features/output/slide.service.js
@@ -1,37 +1,9 @@
/* eslint camelcase: 0 */
import {
- OUTPUT_EVENT_LIMIT,
+ OUTPUT_MAX_BUFFER_LENGTH,
OUTPUT_PAGE_SIZE,
} from './constants';
-function getContinuous (events, reverse = false) {
- const counters = events.map(({ counter }) => counter);
-
- const min = Math.min(...counters);
- const max = Math.max(...counters);
-
- const missing = [];
- for (let i = min; i <= max; i++) {
- if (counters.indexOf(i) < 0) {
- missing.push(i);
- }
- }
-
- if (missing.length === 0) {
- return events;
- }
-
- if (reverse) {
- const threshold = Math.max(...missing);
-
- return events.filter(({ counter }) => counter > threshold);
- }
-
- const threshold = Math.min(...missing);
-
- return events.filter(({ counter }) => counter < threshold);
-}
-
function SlidingWindowService ($q) {
this.init = ({ getRange, getFirst, getLast, getMaxCounter }, storage) => {
const { getHeadCounter, getTailCounter } = storage;
@@ -81,15 +53,13 @@ function SlidingWindowService ($q) {
this.getNext = (displacement = OUTPUT_PAGE_SIZE) => {
const next = this.getNextRange(displacement);
- return this.api.getRange(next)
- .then(results => getContinuous(results));
+ return this.api.getRange(next);
};
this.getPrevious = (displacement = OUTPUT_PAGE_SIZE) => {
const previous = this.getPreviousRange(displacement);
- return this.api.getRange(previous)
- .then(results => getContinuous(results, true));
+ return this.api.getRange(previous);
};
this.getFirst = () => {
@@ -128,7 +98,7 @@ function SlidingWindowService ($q) {
for (let i = frames.length - 1; i >= 0; i--) {
count++;
- if (count > OUTPUT_EVENT_LIMIT) {
+ if (count > OUTPUT_MAX_BUFFER_LENGTH) {
frames.splice(i, 1);
count--;
@@ -153,29 +123,25 @@ function SlidingWindowService ($q) {
return frames;
}
- if (min >= head && min <= tail + 1) {
- return frames.filter(({ counter }) => counter > tail);
- }
-
- return [];
+ return frames.filter(({ counter }) => counter > tail);
};
this.getFrames = () => $q.resolve(this.buffer.events);
this.getMaxCounter = () => {
- if (this.buffer.min && this.buffer.min > 1) {
- return this.buffer.min - 1;
+ if (this.buffer.max && this.buffer.max > 1) {
+ return this.buffer.max;
}
return this.api.getMaxCounter();
};
this.isOnLastPage = () => {
- if (this.getTailCounter() === 0) {
- return true;
+ if (this.buffer.min) {
+ return this.getTailCounter() >= this.buffer.min - 1;
}
- return this.getTailCounter() >= (this.getMaxCounter() - OUTPUT_PAGE_SIZE);
+ return this.getTailCounter() >= this.getMaxCounter() - OUTPUT_PAGE_SIZE;
};
this.isOnFirstPage = () => this.getHeadCounter() === 1;