add pager for static and filtered job results

This commit is contained in:
Jake McDermott 2018-06-26 11:15:51 -04:00
parent ccd8a48617
commit e6668a43cd
No known key found for this signature in database
GPG Key ID: 3B02CAD476EECB35
6 changed files with 354 additions and 67 deletions

View File

@ -14,7 +14,7 @@ function JobEventsApiService ($http, $q) {
this.endpoint = endpoint;
this.params = merge(BASE_PARAMS, params);
this.state = { current: 0, count: 0 };
this.state = { count: 0, maxCounter: 0 };
};
this.fetch = () => this.getLast().then(() => this);
@ -26,14 +26,68 @@ function JobEventsApiService ($http, $q) {
return $http.get(this.endpoint, { params })
.then(({ data }) => {
const { results, count } = data;
const maxCounter = Math.max(...results.map(({ counter }) => counter));
this.state.count = count;
this.state.current = page;
if (maxCounter > this.state.maxCounter) {
this.state.maxCounter = maxCounter;
}
return results;
});
};
this.getPage = number => {
if (number < 1 || number > this.getLastPageNumber()) {
return $q.resolve([]);
}
const params = merge(this.params, { page: number });
return $http.get(this.endpoint, { params })
.then(({ data }) => {
const { results, count } = data;
const maxCounter = Math.max(...results.map(({ counter }) => counter));
this.state.count = count;
if (maxCounter > this.state.maxCounter) {
this.state.maxCounter = maxCounter;
}
return results;
});
};
this.getLast = () => {
const params = merge(this.params, { page: 1, order_by: `-${ORDER_BY}` });
return $http.get(this.endpoint, { params })
.then(({ data }) => {
const { results, count } = data;
const maxCounter = Math.max(...results.map(({ counter }) => counter));
let rotated = results;
if (count > PAGE_SIZE) {
rotated = results.splice(count % PAGE_SIZE);
if (results.length > 0) {
rotated = results;
}
}
this.state.count = count;
if (maxCounter > this.state.maxCounter) {
this.state.maxCounter = maxCounter;
}
return rotated;
});
};
this.getRange = range => {
if (!range) {
return $q.resolve([]);
@ -47,46 +101,18 @@ function JobEventsApiService ($http, $q) {
return $http.get(this.endpoint, { params })
.then(({ data }) => {
const { results } = data;
const maxCounter = Math.max(results.map(({ counter }) => counter));
const maxCounter = Math.max(...results.map(({ counter }) => counter));
this.state.current = Math.ceil(maxCounter / PAGE_SIZE);
if (maxCounter > this.state.maxCounter) {
this.state.maxCounter = maxCounter;
}
return results;
});
};
this.getLast = () => {
const params = merge(this.params, { page: 1, order_by: `-${ORDER_BY}` });
return $http.get(this.endpoint, { params })
.then(({ data }) => {
const { results } = data;
const count = Math.max(...results.map(({ counter }) => counter));
let rotated = results;
if (count > PAGE_SIZE) {
rotated = results.splice(count % PAGE_SIZE);
if (results.length > 0) {
rotated = results;
}
}
this.state.count = count;
this.state.current = Math.ceil(count / PAGE_SIZE);
return rotated;
});
};
this.getCurrentPageNumber = () => this.state.current;
this.getLastPageNumber = () => Math.ceil(this.state.count / PAGE_SIZE);
this.getPreviousPageNumber = () => Math.max(1, this.state.current - 1);
this.getNextPageNumber = () => Math.min(this.state.current + 1, this.getLastPageNumber());
this.getMaxCounter = () => this.state.count;
this.getNext = () => this.getPage(this.getNextPageNumber());
this.getPrevious = () => this.getPage(this.getPreviousPageNumber());
this.getMaxCounter = () => this.state.maxCounter;
}
JobEventsApiService.$inject = ['$http', '$q'];

View File

@ -89,7 +89,7 @@ function first () {
}
function next () {
return slide.slideDown();
return slide.getNext();
}
function previous () {
@ -97,7 +97,7 @@ function previous () {
const initialPosition = scroll.getScrollPosition();
return slide.slideUp()
return slide.getPrevious()
.then(popHeight => {
const currentHeight = scroll.getScrollHeight();
scroll.setScrollPosition(currentHeight - popHeight + initialPosition);
@ -245,6 +245,7 @@ function OutputIndexController (
_$state_,
_resource_,
_scroll_,
_page_,
_render_,
_status_,
_slide_,
@ -253,6 +254,8 @@ function OutputIndexController (
strings,
$stateParams,
) {
const { isPanelExpanded } = $stateParams;
$compile = _$compile_;
$q = _$q_;
$scope = _$scope_;
@ -261,9 +264,9 @@ function OutputIndexController (
resource = _resource_;
scroll = _scroll_;
render = _render_;
slide = _slide_;
status = _status_;
stream = _stream_;
slide = resource.model.get('event_processing_finished') ? _page_ : _slide_;
vm = this || {};
@ -271,8 +274,6 @@ function OutputIndexController (
vm.title = $filter('sanitize')(resource.model.get('name'));
vm.strings = strings;
vm.resource = resource;
const { isPanelExpanded } = $stateParams;
vm.reloadState = reloadState;
vm.isPanelExpanded = isPanelExpanded;
vm.togglePanelExpand = togglePanelExpand;
@ -324,6 +325,7 @@ OutputIndexController.$inject = [
'$state',
'resource',
'OutputScrollService',
'OutputPageService',
'OutputRenderService',
'OutputStatusService',
'OutputSlideService',

View File

@ -9,6 +9,7 @@ import StreamService from '~features/output/stream.service';
import StatusService from '~features/output/status.service';
import MessageService from '~features/output/message.service';
import EventsApiService from '~features/output/api.events.service';
import PageService from '~features/output/page.service';
import SlideService from '~features/output/slide.service';
import LegacyRedirect from '~features/output/legacy.route';
@ -108,11 +109,6 @@ function resolveResource (
status: `${WS_PREFIX}-${name}`,
summary: `${WS_PREFIX}-${name}-summary`,
},
page: {
cache: PAGE_CACHE,
size: PAGE_SIZE,
pageLimit: PAGE_LIMIT
}
}));
if (!handleErrors) {
@ -250,6 +246,7 @@ angular
.service('OutputStatusService', StatusService)
.service('OutputMessageService', MessageService)
.service('JobEventsApiService', EventsApiService)
.service('OutputPageService', PageService)
.service('OutputSlideService', SlideService)
.component('atJobSearch', SearchComponent)
.component('atJobStats', StatsComponent)

View File

@ -0,0 +1,239 @@
/* eslint camelcase: 0 */
const PAGE_LIMIT = 5;
function PageService ($q) {
this.init = (storage, api, { getScrollHeight }) => {
const { prepend, append, shift, pop, deleteRecord } = storage;
const { getPage, getFirst, getLast, getLastPageNumber } = api;
this.api = {
getPage,
getFirst,
getLast,
getLastPageNumber,
};
this.storage = {
prepend,
append,
shift,
pop,
deleteRecord,
};
this.hooks = {
getScrollHeight,
};
this.records = {};
this.uuids = {};
this.state = {
head: 0,
tail: 0,
};
this.chain = $q.resolve();
};
this.pushFront = results => {
if (!results) {
return $q.resolve();
}
return this.storage.append(results)
.then(() => {
this.records[++this.state.tail] = {};
results.forEach(({ counter, start_line, end_line, uuid }) => {
this.records[this.state.tail][counter] = { start_line, end_line };
this.uuids[counter] = uuid;
});
return $q.resolve();
});
};
this.pushBack = results => {
if (!results) {
return $q.resolve();
}
return this.storage.prepend(results)
.then(() => {
this.records[--this.state.head] = {};
results.forEach(({ counter, start_line, end_line, uuid }) => {
this.records[this.state.head][counter] = { start_line, end_line };
this.uuids[counter] = uuid;
});
return $q.resolve();
});
};
this.popBack = () => {
if (this.getRecordCount() === 0) {
return $q.resolve();
}
const pageRecord = this.records[this.state.head] || {};
let lines = 0;
const counters = [];
Object.keys(pageRecord)
.forEach(counter => {
lines += pageRecord[counter].end_line - pageRecord[counter].start_line;
counters.push(counter);
});
return this.storage.shift(lines)
.then(() => {
counters.forEach(counter => {
this.storage.deleteRecord(this.uuids[counter]);
delete this.uuids[counter];
});
delete this.records[this.state.head++];
return $q.resolve();
});
};
this.popFront = () => {
if (this.getRecordCount() === 0) {
return $q.resolve();
}
const pageRecord = this.records[this.state.tail] || {};
let lines = 0;
const counters = [];
Object.keys(pageRecord)
.forEach(counter => {
lines += pageRecord[counter].end_line - pageRecord[counter].start_line;
counters.push(counter);
});
return this.storage.pop(lines)
.then(() => {
counters.forEach(counter => {
this.storage.deleteRecord(this.uuids[counter]);
delete this.uuids[counter];
});
delete this.records[this.state.tail--];
return $q.resolve();
});
};
this.getNext = () => {
const lastPageNumber = this.api.getLastPageNumber();
const number = Math.min(this.state.tail + 1, lastPageNumber);
const isLoaded = (number >= this.state.head && number <= this.state.tail);
const isValid = (number >= 1 && number <= lastPageNumber);
let popHeight = this.hooks.getScrollHeight();
if (!isValid || isLoaded) {
this.chain = this.chain
.then(() => $q.resolve(popHeight));
return this.chain;
}
const pageCount = this.state.head - this.state.tail;
if (pageCount >= PAGE_LIMIT) {
this.chain = this.chain
.then(() => this.popBack())
.then(() => {
popHeight = this.hooks.getScrollHeight();
return $q.resolve();
});
}
this.chain = this.chain
.then(() => this.api.getPage(number))
.then(events => this.pushFront(events))
.then(() => $q.resolve(popHeight));
return this.chain;
};
this.getPrevious = () => {
const number = Math.max(this.state.head - 1, 1);
const isLoaded = (number >= this.state.head && number <= this.state.tail);
const isValid = (number >= 1 && number <= this.api.getLastPageNumber());
let popHeight = this.hooks.getScrollHeight();
if (!isValid || isLoaded) {
this.chain = this.chain
.then(() => $q.resolve(popHeight));
return this.chain;
}
const pageCount = this.state.head - this.state.tail;
if (pageCount >= PAGE_LIMIT) {
this.chain = this.chain
.then(() => this.popFront())
.then(() => {
popHeight = this.hooks.getScrollHeight();
return $q.resolve();
});
}
this.chain = this.chain
.then(() => this.api.getPage(number))
.then(events => this.pushBack(events))
.then(() => $q.resolve(popHeight));
return this.chain;
};
this.clear = () => {
const count = this.getRecordCount();
for (let i = 0; i <= count; ++i) {
this.chain = this.chain.then(() => this.popBack());
}
return this.chain;
};
this.getLast = () => this.clear()
.then(() => this.api.getLast())
.then(events => {
const lastPage = this.api.getLastPageNumber();
this.state.head = lastPage;
this.state.tail = lastPage;
return this.pushBack(events);
})
.then(() => this.getPrevious());
this.getFirst = () => this.clear()
.then(() => this.api.getFirst())
.then(events => {
this.state.head = 1;
this.state.tail = 1;
return this.pushBack(events);
})
.then(() => this.getNext());
this.getRecordCount = () => Object.keys(this.records).length;
this.getTailCounter = () => this.state.tail;
}
PageService.$inject = ['$q'];
export default PageService;

View File

@ -77,7 +77,6 @@ function submitSearch () {
}
const searchInputQueryset = qs.getSearchInputQueryset(vm.value, isFilterable);
const modifiedQueryset = qs.mergeQueryset(currentQueryset, searchInputQueryset);
reloadQueryset(modifiedQueryset, strings.get('search.REJECT_INVALID'));

View File

@ -58,6 +58,23 @@ function getOverlapArray (range, other) {
return [range[0] - other[0], other[1] - range[1]];
}
/**
* Apply a minimum and maximum boundary to a range.
*
* @arg {Array} range - A [low, high] range array.
* @arg {Array} other - A [low, high] range array to be applied as a boundary.
*
* @returns {(Array)} - Returns a new range array by applying the second range
* as a boundary to the first.
*
* getBoundedRange([2, 6], [2, 8]) = [2, 6]
* getBoundedRange([1, 9], [2, 8]) = [2, 8]
* getBoundedRange([4, 9], [2, 8]) = [4, 8]
*/
function getBoundedRange (range, other) {
return [Math.max(range[0], other[0]), Math.min(range[1], other[1])];
}
function SlidingWindowService ($q) {
this.init = (storage, api, { getScrollHeight }) => {
const { prepend, append, shift, pop, deleteRecord } = storage;
@ -67,7 +84,7 @@ function SlidingWindowService ($q) {
getMaxCounter,
getRange,
getFirst,
getLast
getLast,
};
this.storage = {
@ -176,47 +193,54 @@ function SlidingWindowService ($q) {
});
};
this.getBoundedRange = ([low, high]) => {
const bounds = [1, this.getMaxCounter()];
return [Math.max(low, bounds[0]), Math.min(high, bounds[1])];
};
this.move = ([low, high]) => {
const [head, tail] = this.getRange();
const [newHead, newTail] = this.getBoundedRange([low, high]);
const bounds = [1, this.getMaxCounter()];
const [newHead, newTail] = getBoundedRange([low, high], bounds);
let popHeight = this.hooks.getScrollHeight();
if (newHead > newTail) {
return $q.resolve([0, 0]);
this.chain = this.chain
.then(() => $q.resolve(popHeight));
return this.chain;
}
if (!Number.isFinite(newHead) || !Number.isFinite(newTail)) {
return $q.resolve([0, 0]);
this.chain = this.chain
.then(() => $q.resolve(popHeight));
return this.chain;
}
const [head, tail] = this.getRange();
const overlap = getOverlapArray([head, tail], [newHead, newTail]);
if (!overlap) {
this.chain = this.chain
.then(() => this.popBack(this.getRecordCount()))
.then(() => this.clear())
.then(() => this.api.getRange([newHead, newTail]))
.then(events => this.pushFront(events));
}
if (overlap && overlap[0] < 0) {
this.chain = this.chain.then(() => this.popBack(Math.abs(overlap[0])));
const popBackCount = Math.abs(overlap[0]);
this.chain = this.chain.then(() => this.popBack(popBackCount));
}
if (overlap && overlap[1] < 0) {
this.chain = this.chain.then(() => this.popFront(Math.abs(overlap[1])));
const popFrontCount = Math.abs(overlap[1]);
this.chain = this.chain.then(() => this.popFront(popFrontCount));
}
let popHeight;
this.chain = this.chain.then(() => {
popHeight = this.hooks.getScrollHeight();
this.chain = this.chain
.then(() => {
popHeight = this.hooks.getScrollHeight();
return $q.resolve();
});
return $q.resolve();
});
if (overlap && overlap[0] > 0) {
const pushBackRange = [head - overlap[0], head];
@ -240,7 +264,7 @@ function SlidingWindowService ($q) {
return this.chain;
};
this.slideDown = (displacement = PAGE_SIZE) => {
this.getNext = (displacement = PAGE_SIZE) => {
const [head, tail] = this.getRange();
const tailRoom = this.getMaxCounter() - tail;
@ -257,7 +281,7 @@ function SlidingWindowService ($q) {
return this.move([head + headDisplacement, tail + tailDisplacement]);
};
this.slideUp = (displacement = PAGE_SIZE) => {
this.getPrevious = (displacement = PAGE_SIZE) => {
const [head, tail] = this.getRange();
const headRoom = head - 1;