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;