From 60a19246ae44c2bf038abf9dc97d1fb102b8a6da Mon Sep 17 00:00:00 2001 From: gconsidine Date: Tue, 6 Mar 2018 17:47:22 -0500 Subject: [PATCH] Add promise-based event processesing for consistency --- .../features/output/index.controller.js | 180 +++++++------ awx/ui/client/features/output/page.service.js | 245 ++++++++++-------- .../client/features/output/scroll.service.js | 7 + 3 files changed, 244 insertions(+), 188 deletions(-) diff --git a/awx/ui/client/features/output/index.controller.js b/awx/ui/client/features/output/index.controller.js index 25ebb9b531..e2aeae2d90 100644 --- a/awx/ui/client/features/output/index.controller.js +++ b/awx/ui/client/features/output/index.controller.js @@ -10,6 +10,8 @@ let render; let scroll; let resource; +let chain; + function JobsIndexController ( _resource_, _page_, @@ -56,6 +58,7 @@ function JobsIndexController ( const stream = false; // TODO: Set in route + chain = $q.resolve(); render.requestAnimationFrame(() => init()); } @@ -81,37 +84,39 @@ function init (stream) { } function process (scope, data) { - if (data.event === JOB_START) { - vm.stream.active = true; - scroll.lock(); - } else if (data.event === JOB_END) { - vm.stream.active = false; - } + chain = chain.then(() => { + if (data.event === JOB_START) { + vm.stream.active = true; + scroll.lock(); + } else if (data.event === JOB_END) { + vm.stream.active = false; + } - const pageAdded = page.addToBuffer(data); + const pageAdded = page.addToBuffer(data); - if (pageAdded && !scroll.isLocked()) { - vm.stream.paused = true; - } + if (pageAdded && !scroll.isLocked()) { + vm.stream.paused = true; + } - if (vm.stream.paused && scroll.isLocked()) { - vm.stream.paused = false; - } + if (vm.stream.paused && scroll.isLocked()) { + vm.stream.paused = false; + } - if (vm.stream.rendering || vm.stream.paused) { - return; - } + if (vm.stream.rendering || vm.stream.paused) { + return; + } - const events = page.emptyBuffer(); + const events = page.emptyBuffer(); - return renderStream(events); + return renderStream(events); + }) } function renderStream (events) { vm.stream.rendering = true; return shift() - .then(() => append(events)) + .then(() => append(events, true)) .then(() => { if (scroll.isLocked()) { scroll.setScrollPosition(scroll.getScrollHeight()); @@ -135,6 +140,12 @@ function renderStream (events) { function devClear () { init(true); render.clear(); + + vm.stream = { + active: false, + rendering: false, + paused: false + }; } function next () { @@ -167,23 +178,22 @@ function previous () { }) .then(() => { const currentHeight = scroll.getScrollHeight(); - scroll.setScrollPosition(currentHeight - postPopHeight + initialPosition); }); }); } -function append (events) { +function append (events, stream) { return render.append(events) .then(count => { - page.updateLineCount('current', count); + page.updateLineCount(count, stream); }); } function prepend (events) { return render.prepend(events) .then(count => { - page.updateLineCount('current', count); + page.updateLineCount(count); }); } @@ -192,7 +202,7 @@ function pop () { return $q.resolve(); } - const lines = page.trim('right'); + const lines = page.trim(); return render.pop(lines); } @@ -202,11 +212,71 @@ function shift () { return $q.resolve(); } - const lines = page.trim('left'); + const lines = page.trim(true); return render.shift(lines); } +function scrollHome () { + scroll.pause(); + + return page.first() + .then(events => { + if (!events) { + return; + } + + return render.clear() + .then(() => prepend(events)) + .then(() => { + scroll.resetScrollPosition(); + scroll.resume(); + }); + }); +} + +function scrollEnd () { + if (scroll.isLocked()) { + page.setBookmark(); + scroll.unlock(); + + return; + } else if (!scroll.isLocked() && vm.stream.active) { + page.removeBookmark(); + scroll.lock(); + + return; + } + + scroll.pause(); + + return page.last() + .then(events => { + if (!events) { + return; + } + + return render.clear() + .then(() => append(events)) + .then(() => { + scroll.setScrollPosition(scroll.getScrollHeight()); + scroll.resume(); + }); + }); +} + +function scrollPageUp () { + scroll.pageUp(); +} + +function scrollPageDown () { + scroll.pageDown(); +} + +function scrollIsAtRest (isAtRest) { + vm.scroll.showBackToTop = !isAtRest; +} + function expand () { vm.toggle(parent, true); } @@ -250,66 +320,6 @@ function toggle (uuid, menu) { } } -function scrollHome () { - scroll.pause(); - - return page.first() - .then(events => { - if (!events) { - return; - } - - return render.clear() - .then(() => render.prepend(events)) - .then(() => { - scroll.setScrollPosition(0); - scroll.resume(); - }); - }); -} - -function scrollEnd () { - if (scroll.isLocked()) { - page.bookmark(); - scroll.unlock(); - - return; - } else if (!scroll.isLocked() && vm.stream.active) { - page.bookmark(); - scroll.lock(); - - return; - } - - scroll.pause(); - - return page.last() - .then(events => { - if (!events) { - return; - } - - return render.clear() - .then(() => render.append(events)) - .then(() => { - scroll.setScrollPosition(scroll.getScrollHeight()); - scroll.resume(); - }); - }); -} - -function scrollPageUp () { - scroll.pageUp(); -} - -function scrollPageDown () { - scroll.pageDown(); -} - -function scrollIsAtRest (isAtRest) { - vm.scroll.showBackToTop = !isAtRest; -} - JobsIndexController.$inject = [ 'resource', 'JobPageService', diff --git a/awx/ui/client/features/output/page.service.js b/awx/ui/client/features/output/page.service.js index e91b80191e..a89d56d6b8 100644 --- a/awx/ui/client/features/output/page.service.js +++ b/awx/ui/client/features/output/page.service.js @@ -5,12 +5,21 @@ function JobPageService ($q) { this.page = { limit: this.resource.page.pageLimit, size: this.resource.page.size, - current: 0, - index: -1, - count: 0, - first: 0, - last: 0, - bookmark: { + cache: [], + state: { + count: 0, + current: 0, + first: 0, + last: 0 + } + }; + + this.bookmark = { + pending: false, + set: false, + cache: [], + state: { + count: 0, first: 0, last: 0, current: 0 @@ -25,43 +34,45 @@ function JobPageService ($q) { this.buffer = { count: 0 }; - - this.cache = []; }; - this.add = (page, position, bookmark) => { - page.events = page.events || []; - page.lines = page.lines || 0; + this.addPage = (number, events, push, reference) => { + const page = { number, events, lines: 0 }; + reference = reference || this.getActiveReference(); - if (position === 'first') { - this.cache.unshift(page); - this.page.first = page.number; - this.page.last = this.cache[this.cache.length -1].number; + if (push) { + reference.cache.push(page); + reference.state.last = page.number; + reference.state.first = reference.cache[0].number; } else { - this.cache.push(page); - this.page.last = page.number; - this.page.first = this.cache[0].number; + reference.cache.unshift(page); + reference.state.first = page.number; + reference.state.last = reference.cache[reference.cache.length -1].number; } - if (bookmark) { - this.page.bookmark.current = page.number; - } + reference.state.current = page.number; + reference.state.count++; + }; - this.page.current = page.number; - this.page.count++; + this.addToPageCache = (index, event, reference) => { + reference.cache[index].events.push(event); }; this.addToBuffer = event => { + const reference = this.getReference(); let pageAdded = false; if (this.result.count % this.page.size === 0) { - pageAdded = true; + this.addPage(reference.state.current + 1, [event], true, reference); - this.add({ number: this.page.count + 1, events: [event] }); + if (this.isBookmarkPending()) { + this.setBookmark(); + } this.trimBuffer(); + pageAdded = true; } else { - this.cache[this.cache.length - 1].events.push(event); + this.addToPageCache(reference.cache.length - 1, event, reference); } this.buffer.count++; @@ -71,104 +82,127 @@ function JobPageService ($q) { }; this.trimBuffer = () => { - const diff = this.cache.length - this.page.limit; + const reference = this.getReference(); + const diff = reference.cache.length - this.page.limit; if (diff <= 0) { return; } for (let i = 0; i < diff; i++) { - if (this.cache[i].events) { - this.buffer.count -= this.cache[i].events.length; - this.cache[i].events = []; + if (reference.cache[i].events) { + this.buffer.count -= reference.cache[i].events.length; + reference.cache[i].events.splice(0, reference.cache[i].events.length); } } }; this.emptyBuffer = () => { + const reference = this.getReference(); let data = []; - for (let i = 0; i < this.cache.length; i++) { - const events = this.cache[i].events; + for (let i = 0; i < reference.cache.length; i++) { + const count = reference.cache[i].events.length; - if (events.length > 0) { - this.buffer.count -= events.length; - data = data.concat(this.cache[i].events.splice(0, events.length)); + if (count > 0) { + this.buffer.count -= count; + data = data.concat(reference.cache[i].events.splice(0, count)); } } return data; }; - this.emptyCache = () => { - this.page.first = this.page.current; - this.page.last = this.page.current; - this.cache = []; + this.emptyCache = number => { + const reference = this.getActiveReference(); + + number = number || reference.state.current; + + reference.state.first = number; + reference.state.last = number; + reference.state.current = number; + reference.cache.splice(0, reference.cache.length); }; this.isOverCapacity = () => { - return (this.cache.length - this.page.limit) > 0; + const reference = this.getActiveReference(); + + return (reference.cache.length - this.page.limit) > 0; }; - this.trim = side => { - const count = this.cache.length - this.page.limit; - + this.trim = left => { + let reference = this.getActiveReference(); + let excess = reference.cache.length - this.page.limit; let ejected; - if (side === 'left') { - ejected = this.cache.splice(0, count); - this.page.first = this.cache[0].number; + if (left) { + ejected = reference.cache.splice(0, excess); + reference.state.first = reference.cache[0].number; } else { - ejected = this.cache.splice(-count); - this.page.last = this.cache[this.cache.length - 1].number; + ejected = reference.cache.splice(-excess); + reference.state.last = reference.cache[reference.cache.length - 1].number; } return ejected.reduce((total, page) => total + page.lines, 0); }; - this.getPageNumber = page => { - let index; - - if (page === 'first') { - index = 0; - } - - return this.cache[index].number; + this.isPageBookmarked = number => { + return number >= this.page.bookmark.first && number <= this.page.bookmark.last; }; - this.updateLineCount = (page, lines) => { - let index; + this.updateLineCount = (lines, stream) => { + let reference; - if (page === 'current') { - index = this.cache.findIndex(item => item.number === this.page.current); + if (stream) { + reference = this.getReference(); + } else { + reference = this.getActiveReference(); } - this.cache[index].lines += lines; + const index = reference.cache.findIndex(item => item.number === reference.state.current); + + reference.cache[index].lines += lines; } - this.bookmark = () => { - if (!this.page.bookmark.active) { - this.page.bookmark.first = this.page.first; - this.page.bookmark.last = this.page.last; - this.page.bookmark.current = this.page.current; - this.page.bookmark.active = true; - } else { - this.page.bookmark.active = false; + this.isBookmarkPending = () => { + return this.bookmark.pending; + }; + + this.isBookmarkSet = () => { + return this.bookmark.set; + }; + + this.setBookmark = () => { + if (this.isBookmarkSet()) { + return; } + + if (!this.isBookmarkPending()) { + this.bookmark.pending = true; + + return; + } + + this.bookmark.state.first = this.page.state.first; + this.bookmark.state.last = this.page.state.last; + this.bookmark.state.current = this.page.state.current; + this.bookmark.cache = JSON.parse(JSON.stringify(this.page.cache)); + this.bookmark.set = true; + this.bookmark.pending = false; + }; + + this.removeBookmark = () => { + this.bookmark.set = false; + this.bookmark.pending = false; + this.bookmark.cache.splice(0, this.bookmark.cache.length); + this.bookmark.state.first = 0; + this.bookmark.state.last = 0; + this.bookmark.state.current = 0; }; this.next = () => { - let page; - let bookmark; - - if (this.page.bookmark.active) { - page = this.page.bookmark.current + 1; - bookmark = true; - } else { - page = this.page.last + 1; - } - - const config = this.buildRequestConfig(page); + const reference = this.getActiveReference(); + const config = this.buildRequestConfig(reference.state.last + 1); return this.resource.model.goToPage(config) .then(data => { @@ -176,24 +210,15 @@ function JobPageService ($q) { return $q.resolve(); } - this.add({ number: data.page, events: [], lines: 0 }, 'last', bookmark); + this.addPage(data.page, [], true); return data.results; }); }; this.previous = () => { - let page; - let bookmark; - - if (this.page.bookmark.active) { - page = this.page.bookmark.current - 1; - bookmark = true; - } else { - page = this.page.first - 1; - } - - const config = this.buildRequestConfig(page); + const reference = this.getActiveReference(); + const config = this.buildRequestConfig(reference.state.first - 1); return this.resource.model.goToPage(config) .then(data => { @@ -201,7 +226,7 @@ function JobPageService ($q) { return $q.resolve(); } - this.add({ number: data.page, events: [], lines: 0 }, 'first', bookmark); + this.addPage(data.page, [], false); return data.results; }); @@ -210,15 +235,14 @@ function JobPageService ($q) { this.last = () => { const config = this.buildRequestConfig('last'); - this.emptyCache(); - return this.resource.model.goToPage(config) .then(data => { if (!data || !data.results) { return $q.resolve(); } - this.add({ number: data.page, events: [], lines: 0 }, 'last'); + this.emptyCache(data.page); + this.addPage(data.page, [], true); return data.results; }); @@ -227,23 +251,22 @@ function JobPageService ($q) { this.first = () => { const config = this.buildRequestConfig('first'); - this.emptyCache(); - return this.resource.model.goToPage(config) .then(data => { if (!data || !data.results) { return $q.resolve(); } - this.add({ number: data.page, events: [], lines: 0 }, 'first'); + this.emptyCache(data.page); + this.addPage(data.page, [], false); return data.results; }); }; - this.buildRequestConfig = (page) => { + this.buildRequestConfig = number => { return { - page, + page: number, related: this.resource.related, params: { order_by: 'start_line' @@ -251,8 +274,24 @@ function JobPageService ($q) { }; }; - this.current = () => { - return this.resource.model.get(`related.${this.resource.related}.results`); + this.getActiveReference = () => { + return this.isBookmarkSet() ? this.getReference(true) : this.getReference(); + }; + + this.getReference = (bookmark) => { + if (bookmark) { + return { + bookmark: true, + cache: this.bookmark.cache, + state: this.bookmark.state + }; + } + + return { + bookmark: false, + cache: this.page.cache, + state: this.page.state + }; }; } diff --git a/awx/ui/client/features/output/scroll.service.js b/awx/ui/client/features/output/scroll.service.js index 489871e354..ef80655bf5 100644 --- a/awx/ui/client/features/output/scroll.service.js +++ b/awx/ui/client/features/output/scroll.service.js @@ -130,6 +130,13 @@ function JobScrollService ($q, $timeout) { this.isAtRest(); }; + this.resetScrollPosition = () => { + this.position.previous = 0; + this.position.current = 0; + this.el[0].scrollTop = 0; + this.isAtRest(); + }; + this.isAtRest = () => { if (this.position.current === 0 && !this.state.top) { this.state.top = true;