From 11e7dcd0dc1145b3e5dedbe50185d739dcc8cee0 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Tue, 15 May 2018 23:37:32 -0400 Subject: [PATCH] maintain correct page counts on running jobs --- .../features/output/api.events.service.js | 132 ++++++++++++++++++ .../features/output/index.controller.js | 49 ++----- awx/ui/client/features/output/index.js | 24 +++- awx/ui/client/features/output/page.service.js | 63 ++++----- 4 files changed, 187 insertions(+), 81 deletions(-) create mode 100644 awx/ui/client/features/output/api.events.service.js diff --git a/awx/ui/client/features/output/api.events.service.js b/awx/ui/client/features/output/api.events.service.js new file mode 100644 index 0000000000..39499eff46 --- /dev/null +++ b/awx/ui/client/features/output/api.events.service.js @@ -0,0 +1,132 @@ +const PAGE_LIMIT = 5; +const PAGE_SIZE = 50; + +const BASE_PARAMS = { + order_by: 'start_line', + page_size: PAGE_SIZE, +}; + +const merge = (...objs) => _.merge({}, ...objs); + +const getInitialState = params => ({ + results: [], + count: 0, + previous: 1, + page: 1, + next: 1, + last: 1, + params: merge(BASE_PARAMS, params), +}); + +function JobEventsApiService ($http, $q) { + this.init = (endpoint, params) => { + this.keys = []; + this.cache = {}; + this.pageSizes = {}; + this.endpoint = endpoint; + this.state = getInitialState(params); + }; + + this.getLastPage = count => Math.ceil(count / this.state.params.page_size); + + this.fetch = () => { + delete this.cache; + delete this.keys; + delete this.pageSizes; + + this.cache = {}; + this.keys = []; + this.pageSizes = {}; + + return this.getPage(1).then(() => this); + }; + + this.getPage = number => { + if (number < 1 || number > this.state.last) { + return $q.resolve(); + } + + if (this.cache[number]) { + if (this.pageSizes[number] === PAGE_SIZE) { + return this.cache[number]; + } + + delete this.pageSizes[number]; + delete this.cache[number]; + + this.keys.splice(this.keys.indexOf(number)); + } + + const { params } = this.state; + + delete params.page; + + params.page = number; + + const promise = $http.get(this.endpoint, { params }) + .then(({ data }) => { + const { results, count } = data; + + this.state.results = results; + this.state.count = count; + this.state.page = number; + this.state.last = this.getLastPage(count); + this.state.previous = Math.max(1, number - 1); + this.state.next = Math.min(this.state.last, number + 1); + + this.pageSizes[number] = results.length; + + return { results, page: number }; + }); + + this.cache[number] = promise; + this.keys.push(number); + + if (this.keys.length > PAGE_LIMIT) { + delete this.cache[this.keys.shift()]; + } + + return promise; + }; + + this.first = () => this.getPage(1); + this.next = () => this.getPage(this.state.next); + this.previous = () => this.getPage(this.state.previous); + + this.last = () => { + const params = merge({}, this.state.params); + + delete params.page; + delete params.order_by; + + params.page = 1; + params.order_by = '-start_line'; + + const promise = $http.get(this.endpoint, { params }) + .then(({ data }) => { + const { results, count } = data; + const lastPage = this.getLastPage(count); + + results.reverse(); + const shifted = results.splice(count % PAGE_SIZE); + + this.state.results = shifted; + this.state.count = count; + this.state.page = lastPage; + this.state.next = lastPage; + this.state.last = lastPage; + this.state.previous = Math.max(1, this.state.page - 1); + + return { results: shifted, page: lastPage }; + }); + + return promise; + }; +} + +JobEventsApiService.$inject = [ + '$http', + '$q' +]; + +export default JobEventsApiService; diff --git a/awx/ui/client/features/output/index.controller.js b/awx/ui/client/features/output/index.controller.js index 5f5ba6b6eb..a6761238c4 100644 --- a/awx/ui/client/features/output/index.controller.js +++ b/awx/ui/client/features/output/index.controller.js @@ -7,7 +7,6 @@ let resource; let scroll; let engine; let status; -let $http; let vm; let streaming; @@ -23,7 +22,6 @@ function JobsIndexController ( _$compile_, _$q_, _status_, - _$http_, ) { vm = this || {}; @@ -37,7 +35,6 @@ function JobsIndexController ( render = _render_; engine = _engine_; status = _status_; - $http = _$http_; // Development helper(s) vm.clear = devClear; @@ -128,37 +125,20 @@ function handleJobEvent (data) { } function attachToRunningJob () { - const target = `${resource.model.get('url')}${resource.related}/`; - const params = { order_by: '-created', page_size: resource.page.size }; + if (!status.state.running) { + return $q.resolve(); + } - scroll.pause(); - - return render.clear() - .then(() => $http.get(target, { params })) - .then(res => { - const { results } = res.data; - - const minLine = 1 + Math.max(...results.map(event => event.end_line)); - const maxCount = Math.max(...results.map(event => event.counter)); - - const lastPage = resource.model.updateCount(maxCount); - - page.emptyCache(lastPage); - page.addPage(lastPage, [], true); - - engine.setMinLine(minLine); - - if (resource.model.page.current === lastPage) { + return page.last() + .then(events => { + if (!events) { return $q.resolve(); } - return append(results); - }) - .then(() => { - scroll.setScrollPosition(scroll.getScrollHeight()); - scroll.resume(); + const minLine = 1 + Math.max(...events.map(event => event.end_line)); - return $q.resolve(); + return render.clear() + .then(() => engine.setMinLine(minLine)); }); } @@ -293,11 +273,11 @@ function scrollEnd () { } return render.clear() - .then(() => append(events)) - .then(() => { - scroll.setScrollPosition(scroll.getScrollHeight()); - scroll.resume(); - }); + .then(() => append(events)); + }) + .then(() => { + scroll.setScrollPosition(scroll.getScrollHeight()); + scroll.resume(); }); } @@ -379,7 +359,6 @@ JobsIndexController.$inject = [ '$compile', '$q', 'JobStatusService', - '$http', ]; module.exports = JobsIndexController; diff --git a/awx/ui/client/features/output/index.js b/awx/ui/client/features/output/index.js index a8d635ed3a..be7bf49b33 100644 --- a/awx/ui/client/features/output/index.js +++ b/awx/ui/client/features/output/index.js @@ -9,6 +9,7 @@ import ScrollService from '~features/output/scroll.service'; import EngineService from '~features/output/engine.service'; import StatusService from '~features/output/status.service'; import MessageService from '~features/output/message.service'; +import EventsApiService from '~features/output/api.events.service'; import LegacyRedirect from '~features/output/legacy.route'; import DetailsComponent from '~features/output/details.component'; @@ -35,7 +36,8 @@ function resolveResource ( InventoryUpdate, $stateParams, qs, - Wait + Wait, + eventsApi, ) { const { id, type, handleErrors } = $stateParams; const { job_event_search } = $stateParams; // eslint-disable-line camelcase @@ -86,23 +88,29 @@ function resolveResource ( Object.assign(config.params, query); } + let model; + Wait('start'); const resourcePromise = new Resource(['get', 'options'], [id, id]) - .then(model => { - const promises = [model.getStats()]; + .then(job => { + const endpoint = `${job.get('url')}${related}/`; + eventsApi.init(endpoint, config.params); - if (model.has('related.labels')) { - promises.push(model.extend('get', 'labels')); + const promises = [job.getStats(), eventsApi.fetch()]; + + if (job.has('related.labels')) { + promises.push(job.extend('get', 'labels')); } - promises.push(model.extend('get', related, config)); + model = job; return Promise.all(promises); }) - .then(([stats, model]) => ({ + .then(([stats, events]) => ({ id, type, stats, model, + events, related, ws: { events: `${WS_PREFIX}-${key}-${id}`, @@ -217,6 +225,7 @@ function JobsRun ($stateRegistry, strings) { '$stateParams', 'QuerySet', 'Wait', + 'JobEventsApiService', resolveResource ], breadcrumbLabel: [ @@ -246,6 +255,7 @@ angular .service('JobEventEngine', EngineService) .service('JobStatusService', StatusService) .service('JobMessageService', MessageService) + .service('JobEventsApiService', EventsApiService) .component('atJobSearch', SearchComponent) .component('atJobStats', StatsComponent) .component('atJobDetails', DetailsComponent) diff --git a/awx/ui/client/features/output/page.service.js b/awx/ui/client/features/output/page.service.js index 5f19fe921a..854bda23b0 100644 --- a/awx/ui/client/features/output/page.service.js +++ b/awx/ui/client/features/output/page.service.js @@ -1,6 +1,7 @@ function JobPageService ($q) { this.init = ({ resource }) => { this.resource = resource; + this.api = this.resource.events; this.page = { limit: this.resource.page.pageLimit, @@ -125,8 +126,9 @@ function JobPageService ($q) { number = number || reference.state.current; reference.state.first = number; - reference.state.last = number; reference.state.current = number; + reference.state.last = number; + reference.cache.splice(0, reference.cache.length); }; @@ -203,9 +205,9 @@ function JobPageService ($q) { this.next = () => { const reference = this.getActiveReference(); - const config = this.buildRequestConfig(reference.state.last + 1); + const number = reference.state.last + 1; - return this.resource.model.goToPage(config) + return this.api.getPage(number) .then(data => { if (!data || !data.results) { return $q.resolve(); @@ -219,9 +221,8 @@ function JobPageService ($q) { this.previous = () => { const reference = this.getActiveReference(); - const config = this.buildRequestConfig(reference.state.first - 1); - return this.resource.model.goToPage(config) + return this.api.getPage(reference.state.first - 1) .then(data => { if (!data || !data.results) { return $q.resolve(); @@ -233,45 +234,29 @@ function JobPageService ($q) { }); }; - this.last = () => { - const config = this.buildRequestConfig('last'); + this.last = () => this.api.last() + .then(data => { + if (!data || !data.results || !data.results.length > 0) { + return $q.resolve(); + } - return this.resource.model.goToPage(config) - .then(data => { - if (!data || !data.results) { - return $q.resolve(); - } + this.emptyCache(data.page); + this.addPage(data.page, [], true); - this.emptyCache(data.page); - this.addPage(data.page, [], true); + return data.results; + }); - return data.results; - }); - }; + this.first = () => this.api.first() + .then(data => { + if (!data || !data.results) { + return $q.resolve(); + } - this.first = () => { - const config = this.buildRequestConfig('first'); + this.emptyCache(data.page); + this.addPage(data.page, [], false); - return this.resource.model.goToPage(config) - .then(data => { - if (!data || !data.results) { - return $q.resolve(); - } - - this.emptyCache(data.page); - this.addPage(data.page, [], false); - - return data.results; - }); - }; - - this.buildRequestConfig = number => ({ - page: number, - related: this.resource.related, - params: { - order_by: 'start_line' - } - }); + return data.results; + }); this.getActiveReference = () => (this.isBookmarkSet() ? this.getReference(true) : this.getReference());