mirror of
https://github.com/ansible/awx.git
synced 2026-01-20 14:11:24 -03:30
Merge pull request #2880 from jakemcdermott/fix-2828
add event discard with interactive discontinuities for high volume jobs
This commit is contained in:
commit
860fbdad02
@ -104,6 +104,10 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&-line--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-event {
|
||||
.at-mixin-event();
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ export const JOB_STATUS_FINISHED = JOB_STATUS_COMPLETE.concat(JOB_STATUS_INCOMPL
|
||||
export const OUTPUT_ELEMENT_CONTAINER = '.at-Stdout-container';
|
||||
export const OUTPUT_ELEMENT_TBODY = '#atStdoutResultTable';
|
||||
export const OUTPUT_ELEMENT_LAST = '#atStdoutMenuLast';
|
||||
export const OUTPUT_MAX_BUFFER_LENGTH = 1000;
|
||||
export const OUTPUT_MAX_LAG = 120;
|
||||
export const OUTPUT_NO_COUNT_JOB_TYPES = ['ad_hoc_command', 'system_job', 'inventory_update'];
|
||||
export const OUTPUT_ORDER_BY = 'counter';
|
||||
|
||||
@ -17,61 +17,21 @@ let scroll;
|
||||
let status;
|
||||
let slide;
|
||||
let stream;
|
||||
let page;
|
||||
|
||||
let vm;
|
||||
|
||||
const bufferState = [0, 0]; // [length, count]
|
||||
const listeners = [];
|
||||
const rx = [];
|
||||
let lockFrames = false;
|
||||
|
||||
function bufferInit () {
|
||||
rx.length = 0;
|
||||
|
||||
bufferState[0] = 0;
|
||||
bufferState[1] = 0;
|
||||
}
|
||||
|
||||
function bufferAdd (event) {
|
||||
rx.push(event);
|
||||
|
||||
bufferState[0] += 1;
|
||||
bufferState[1] += 1;
|
||||
|
||||
return bufferState[1];
|
||||
}
|
||||
|
||||
function bufferEmpty (min, max) {
|
||||
let count = 0;
|
||||
let removed = [];
|
||||
|
||||
for (let i = bufferState[0] - 1; i >= 0; i--) {
|
||||
if (rx[i].counter <= max) {
|
||||
removed = removed.concat(rx.splice(i, 1));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
bufferState[0] -= count;
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
let lockFrames;
|
||||
function onFrames (events) {
|
||||
if (lockFrames) {
|
||||
events.forEach(bufferAdd);
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
events = slide.pushFrames(events);
|
||||
const popCount = events.length - slide.getCapacity();
|
||||
const isAttached = events.length > 0;
|
||||
|
||||
if (!isAttached) {
|
||||
stopFollowing();
|
||||
if (lockFrames) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const popCount = events.length - render.getCapacity();
|
||||
|
||||
if (!vm.isFollowing && canStartFollowing()) {
|
||||
startFollowing();
|
||||
}
|
||||
@ -86,13 +46,13 @@ function onFrames (events) {
|
||||
scroll.scrollToBottom();
|
||||
}
|
||||
|
||||
return slide.popBack(popCount)
|
||||
return render.popBack(popCount)
|
||||
.then(() => {
|
||||
if (vm.isFollowing) {
|
||||
scroll.scrollToBottom();
|
||||
}
|
||||
|
||||
return slide.pushFront(events);
|
||||
return render.pushFront(events);
|
||||
})
|
||||
.then(() => {
|
||||
if (vm.isFollowing) {
|
||||
@ -105,27 +65,44 @@ function onFrames (events) {
|
||||
});
|
||||
}
|
||||
|
||||
function first () {
|
||||
//
|
||||
// Menu Controls (Running)
|
||||
//
|
||||
|
||||
function firstRange () {
|
||||
if (scroll.isPaused()) {
|
||||
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))
|
||||
.then(() => slide.getNext())
|
||||
.then(results => {
|
||||
const popCount = results.length - render.getCapacity();
|
||||
|
||||
return slide.getFirst()
|
||||
.then(() => {
|
||||
scroll.resetScrollPosition();
|
||||
return render.popBack(popCount)
|
||||
.then(() => render.pushFront(results));
|
||||
})
|
||||
.finally(() => {
|
||||
scroll.resume();
|
||||
lockFrames = false;
|
||||
lockFollow = false;
|
||||
});
|
||||
}
|
||||
|
||||
function next () {
|
||||
function nextRange () {
|
||||
if (vm.isFollowing) {
|
||||
scroll.scrollToBottom();
|
||||
|
||||
@ -136,34 +113,49 @@ function next () {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
if (slide.getTailCounter() >= slide.getMaxCounter()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
scroll.pause();
|
||||
lockFrames = true;
|
||||
|
||||
return slide.getNext()
|
||||
.then(results => {
|
||||
const popCount = results.length - render.getCapacity();
|
||||
|
||||
return render.popBack(popCount)
|
||||
.then(() => render.pushFront(results));
|
||||
})
|
||||
.finally(() => {
|
||||
scroll.resume();
|
||||
lockFrames = false;
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function previous () {
|
||||
function previousRange () {
|
||||
if (scroll.isPaused()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
scroll.pause();
|
||||
stopFollowing();
|
||||
lockFrames = true;
|
||||
|
||||
stopFollowing();
|
||||
|
||||
const initialPosition = scroll.getScrollPosition();
|
||||
let initialPosition;
|
||||
let popHeight;
|
||||
|
||||
return slide.getPrevious()
|
||||
.then(popHeight => {
|
||||
.then(results => {
|
||||
const popCount = results.length - render.getCapacity();
|
||||
initialPosition = scroll.getScrollPosition();
|
||||
|
||||
return render.popFront(popCount)
|
||||
.then(() => {
|
||||
popHeight = scroll.getScrollHeight();
|
||||
|
||||
return render.pushBack(results);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
const currentHeight = scroll.getScrollHeight();
|
||||
scroll.setScrollPosition(currentHeight - popHeight + initialPosition);
|
||||
|
||||
@ -172,10 +164,12 @@ function previous () {
|
||||
.finally(() => {
|
||||
scroll.resume();
|
||||
lockFrames = false;
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function last () {
|
||||
function lastRange () {
|
||||
if (scroll.isPaused()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
@ -183,16 +177,39 @@ function last () {
|
||||
scroll.pause();
|
||||
lockFrames = true;
|
||||
|
||||
return slide.getLast()
|
||||
return render.clear()
|
||||
.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();
|
||||
});
|
||||
}
|
||||
|
||||
function menuLastRange () {
|
||||
if (vm.isFollowing) {
|
||||
lockFollow = true;
|
||||
stopFollowing();
|
||||
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
lockFollow = false;
|
||||
|
||||
return lastRange()
|
||||
.then(() => {
|
||||
startFollowing();
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
@ -211,8 +228,7 @@ function canStartFollowing () {
|
||||
|
||||
if (followOnce && // one-time activation from top of first page
|
||||
scroll.isBeyondUpperThreshold() &&
|
||||
slide.getHeadCounter() === 1 &&
|
||||
slide.getTailCounter() >= OUTPUT_PAGE_SIZE) {
|
||||
slide.getTailCounter() - slide.getHeadCounter() >= OUTPUT_PAGE_SIZE) {
|
||||
followOnce = false;
|
||||
|
||||
return true;
|
||||
@ -242,23 +258,159 @@ function stopFollowing () {
|
||||
vm.followTooltip = vm.strings.get('tooltips.MENU_LAST');
|
||||
}
|
||||
|
||||
//
|
||||
// Menu Controls (Page Mode)
|
||||
//
|
||||
|
||||
function firstPage () {
|
||||
if (scroll.isPaused()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
scroll.pause();
|
||||
|
||||
return render.clear()
|
||||
.then(() => page.getFirst())
|
||||
.then(results => render.pushFront(results))
|
||||
.then(() => page.getNext())
|
||||
.then(results => {
|
||||
const popCount = page.trimHead();
|
||||
|
||||
return render.popBack(popCount)
|
||||
.then(() => render.pushFront(results));
|
||||
})
|
||||
.finally(() => {
|
||||
scroll.resume();
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function lastPage () {
|
||||
if (scroll.isPaused()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
scroll.pause();
|
||||
|
||||
return render.clear()
|
||||
.then(() => page.getLast())
|
||||
.then(results => render.pushBack(results))
|
||||
.then(() => page.getPrevious())
|
||||
.then(results => {
|
||||
const popCount = page.trimTail();
|
||||
|
||||
return render.popFront(popCount)
|
||||
.then(() => render.pushBack(results));
|
||||
})
|
||||
.then(() => {
|
||||
scroll.scrollToBottom();
|
||||
|
||||
return $q.resolve();
|
||||
})
|
||||
.finally(() => {
|
||||
scroll.resume();
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function nextPage () {
|
||||
if (scroll.isPaused()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
scroll.pause();
|
||||
|
||||
return page.getNext()
|
||||
.then(results => {
|
||||
const popCount = page.trimHead();
|
||||
|
||||
return render.popBack(popCount)
|
||||
.then(() => render.pushFront(results));
|
||||
})
|
||||
.finally(() => {
|
||||
scroll.resume();
|
||||
});
|
||||
}
|
||||
|
||||
function previousPage () {
|
||||
if (scroll.isPaused()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
scroll.pause();
|
||||
|
||||
let initialPosition;
|
||||
let popHeight;
|
||||
|
||||
return page.getPrevious()
|
||||
.then(results => {
|
||||
const popCount = page.trimTail();
|
||||
initialPosition = scroll.getScrollPosition();
|
||||
|
||||
return render.popFront(popCount)
|
||||
.then(() => {
|
||||
popHeight = scroll.getScrollHeight();
|
||||
|
||||
return render.pushBack(results);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
const currentHeight = scroll.getScrollHeight();
|
||||
scroll.setScrollPosition(currentHeight - popHeight + initialPosition);
|
||||
|
||||
return $q.resolve();
|
||||
})
|
||||
.finally(() => {
|
||||
scroll.resume();
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Menu Controls
|
||||
//
|
||||
|
||||
function first () {
|
||||
if (vm.isProcessingFinished) {
|
||||
return firstPage();
|
||||
}
|
||||
|
||||
return firstRange();
|
||||
}
|
||||
|
||||
function last () {
|
||||
if (vm.isProcessingFinished) {
|
||||
return lastPage();
|
||||
}
|
||||
|
||||
return lastRange();
|
||||
}
|
||||
|
||||
function next () {
|
||||
if (vm.isProcessingFinished) {
|
||||
return nextPage();
|
||||
}
|
||||
|
||||
return nextRange();
|
||||
}
|
||||
|
||||
function previous () {
|
||||
if (vm.isProcessingFinished) {
|
||||
return previousPage();
|
||||
}
|
||||
|
||||
return previousRange();
|
||||
}
|
||||
|
||||
function menuLast () {
|
||||
if (vm.isFollowing) {
|
||||
lockFollow = true;
|
||||
stopFollowing();
|
||||
|
||||
return $q.resolve();
|
||||
if (vm.isProcessingFinished) {
|
||||
return lastPage();
|
||||
}
|
||||
|
||||
lockFollow = false;
|
||||
|
||||
if (slide.isOnLastPage()) {
|
||||
scroll.scrollToBottom();
|
||||
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
return last();
|
||||
return menuLastRange();
|
||||
}
|
||||
|
||||
function down () {
|
||||
@ -273,6 +425,10 @@ function togglePanelExpand () {
|
||||
vm.isPanelExpanded = !vm.isPanelExpanded;
|
||||
}
|
||||
|
||||
//
|
||||
// Line Interaction
|
||||
//
|
||||
|
||||
const iconCollapsed = 'fa-angle-right';
|
||||
const iconExpanded = 'fa-angle-down';
|
||||
const iconSelector = '.at-Stdout-toggle > i';
|
||||
@ -281,7 +437,7 @@ const lineCollapsed = 'hidden';
|
||||
function toggleCollapseAll () {
|
||||
if (scroll.isPaused()) return;
|
||||
|
||||
const records = Object.keys(render.record).map(key => render.record[key]);
|
||||
const records = Object.keys(render.records).map(key => render.records[key]);
|
||||
const plays = records.filter(({ name }) => name === EVENT_START_PLAY);
|
||||
const tasks = records.filter(({ name }) => name === EVENT_START_TASK);
|
||||
|
||||
@ -321,7 +477,7 @@ function toggleCollapseAll () {
|
||||
function toggleCollapse (uuid) {
|
||||
if (scroll.isPaused()) return;
|
||||
|
||||
const record = render.record[uuid];
|
||||
const record = render.records[uuid];
|
||||
|
||||
if (record.name === EVENT_START_PLAY) {
|
||||
togglePlayCollapse(uuid);
|
||||
@ -333,7 +489,7 @@ function toggleCollapse (uuid) {
|
||||
}
|
||||
|
||||
function togglePlayCollapse (uuid) {
|
||||
const record = render.record[uuid];
|
||||
const record = render.records[uuid];
|
||||
const descendants = record.children || [];
|
||||
|
||||
const icon = $(`#${uuid} ${iconSelector}`);
|
||||
@ -364,11 +520,11 @@ function togglePlayCollapse (uuid) {
|
||||
}
|
||||
|
||||
descendants
|
||||
.map(item => render.record[item])
|
||||
.map(item => render.records[item])
|
||||
.filter(({ name }) => name === EVENT_START_TASK)
|
||||
.forEach(rec => { render.record[rec.uuid].isCollapsed = true; });
|
||||
.forEach(rec => { render.records[rec.uuid].isCollapsed = true; });
|
||||
|
||||
render.record[uuid].isCollapsed = !isCollapsed;
|
||||
render.records[uuid].isCollapsed = !isCollapsed;
|
||||
}
|
||||
|
||||
function toggleTaskCollapse (uuid) {
|
||||
@ -387,7 +543,7 @@ function toggleTaskCollapse (uuid) {
|
||||
lines.addClass(lineCollapsed);
|
||||
}
|
||||
|
||||
render.record[uuid].isCollapsed = !isCollapsed;
|
||||
render.records[uuid].isCollapsed = !isCollapsed;
|
||||
}
|
||||
|
||||
function compile (html) {
|
||||
@ -398,6 +554,60 @@ function showHostDetails (id, uuid) {
|
||||
$state.go('output.host-event.json', { eventId: id, taskUuid: uuid });
|
||||
}
|
||||
|
||||
function showMissingEvents (uuid) {
|
||||
const record = render.records[uuid];
|
||||
|
||||
const min = Math.min(...record.counters);
|
||||
const max = Math.min(Math.max(...record.counters), min + OUTPUT_PAGE_SIZE);
|
||||
|
||||
const selector = `#${uuid}`;
|
||||
const clicked = $(selector);
|
||||
|
||||
return resource.events.getRange([min, max])
|
||||
.then(results => {
|
||||
const counters = results.map(({ counter }) => counter);
|
||||
|
||||
for (let i = min; i <= max; i++) {
|
||||
if (counters.indexOf(i) < 0) {
|
||||
results = results.filter(({ counter }) => counter < i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let lines = 0;
|
||||
let untrusted = '';
|
||||
|
||||
for (let i = 0; i <= results.length - 1; i++) {
|
||||
const { html, count } = render.transformEvent(results[i]);
|
||||
|
||||
lines += count;
|
||||
untrusted += html;
|
||||
|
||||
const shifted = render.records[uuid].counters.shift();
|
||||
delete render.uuids[shifted];
|
||||
}
|
||||
|
||||
const trusted = render.trustHtml(untrusted);
|
||||
const elements = angular.element(trusted);
|
||||
|
||||
return render
|
||||
.requestAnimationFrame(() => {
|
||||
elements.insertBefore(clicked);
|
||||
|
||||
if (render.records[uuid].counters.length === 0) {
|
||||
clicked.remove();
|
||||
delete render.records[uuid];
|
||||
}
|
||||
})
|
||||
.then(() => render.compile(elements))
|
||||
.then(() => lines);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Event Handling
|
||||
//
|
||||
|
||||
let streaming;
|
||||
function stopListening () {
|
||||
streaming = null;
|
||||
@ -420,7 +630,7 @@ function startListening () {
|
||||
|
||||
function handleJobEvent (data) {
|
||||
streaming = streaming || resource.events
|
||||
.getRange([Math.max(0, data.counter - 50), data.counter + 50])
|
||||
.getRange([Math.max(1, data.counter - 50), data.counter + 50])
|
||||
.then(results => {
|
||||
results.push(data);
|
||||
|
||||
@ -440,12 +650,13 @@ function handleJobEvent (data) {
|
||||
results = results.filter(({ counter }) => counter > maxMissing);
|
||||
}
|
||||
|
||||
stream.setMissingCounterThreshold(max);
|
||||
results.forEach(item => {
|
||||
stream.pushJobEvent(item);
|
||||
status.pushJobEvent(item);
|
||||
});
|
||||
|
||||
stream.setMissingCounterThreshold(min);
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
|
||||
@ -467,12 +678,20 @@ function handleSummaryEvent (data) {
|
||||
stream.setFinalCounter(data.final_counter);
|
||||
}
|
||||
|
||||
//
|
||||
// Search
|
||||
//
|
||||
|
||||
function reloadState (params) {
|
||||
params.isPanelExpanded = vm.isPanelExpanded;
|
||||
|
||||
return $state.transitionTo($state.current, params, { inherit: false, location: 'replace' });
|
||||
}
|
||||
|
||||
//
|
||||
// Debug Mode
|
||||
//
|
||||
|
||||
function clear () {
|
||||
stopListening();
|
||||
render.clear();
|
||||
@ -481,9 +700,9 @@ function clear () {
|
||||
lockFollow = false;
|
||||
lockFrames = false;
|
||||
|
||||
bufferInit();
|
||||
stream.bufferInit();
|
||||
status.init(resource);
|
||||
slide.init(render, resource.events, scroll);
|
||||
slide.init(resource.events, render);
|
||||
status.subscribe(data => { vm.status = data.status; });
|
||||
|
||||
startListening();
|
||||
@ -518,7 +737,8 @@ function OutputIndexController (
|
||||
render = _render_;
|
||||
status = _status_;
|
||||
stream = _stream_;
|
||||
slide = isProcessingFinished ? _page_ : _slide_;
|
||||
slide = _slide_;
|
||||
page = _page_;
|
||||
|
||||
vm = this || {};
|
||||
|
||||
@ -529,6 +749,7 @@ function OutputIndexController (
|
||||
vm.resource = resource;
|
||||
vm.reloadState = reloadState;
|
||||
vm.isPanelExpanded = isPanelExpanded;
|
||||
vm.isProcessingFinished = isProcessingFinished;
|
||||
vm.togglePanelExpand = togglePanelExpand;
|
||||
|
||||
// Stdout Navigation
|
||||
@ -538,16 +759,17 @@ function OutputIndexController (
|
||||
vm.toggleCollapseAll = toggleCollapseAll;
|
||||
vm.toggleCollapse = toggleCollapse;
|
||||
vm.showHostDetails = showHostDetails;
|
||||
vm.showMissingEvents = showMissingEvents;
|
||||
vm.toggleLineEnabled = resource.model.get('type') === 'job';
|
||||
vm.followTooltip = vm.strings.get('tooltips.MENU_LAST');
|
||||
vm.debug = _debug;
|
||||
|
||||
render.requestAnimationFrame(() => {
|
||||
bufferInit();
|
||||
render.init({ compile, toggles: vm.toggleLineEnabled });
|
||||
|
||||
status.init(resource);
|
||||
slide.init(render, resource.events, scroll);
|
||||
render.init({ compile, toggles: vm.toggleLineEnabled });
|
||||
page.init(resource.events);
|
||||
slide.init(resource.events, render);
|
||||
|
||||
scroll.init({
|
||||
next,
|
||||
@ -564,8 +786,6 @@ function OutputIndexController (
|
||||
let showFollowTip = true;
|
||||
const rates = [];
|
||||
stream.init({
|
||||
bufferAdd,
|
||||
bufferEmpty,
|
||||
onFrames,
|
||||
onFrameRate (rate) {
|
||||
rates.push(rate);
|
||||
@ -638,4 +858,3 @@ OutputIndexController.$inject = [
|
||||
];
|
||||
|
||||
module.exports = OutputIndexController;
|
||||
|
||||
|
||||
@ -2,244 +2,153 @@
|
||||
import { OUTPUT_PAGE_LIMIT } from './constants';
|
||||
|
||||
function PageService ($q) {
|
||||
this.init = (storage, api, { getScrollHeight }) => {
|
||||
const { prepend, append, shift, pop, deleteRecord } = storage;
|
||||
const { getPage, getFirst, getLast, getLastPageNumber, getMaxCounter } = api;
|
||||
|
||||
this.init = ({ getPage, getFirst, getLast, getLastPageNumber }) => {
|
||||
this.api = {
|
||||
getPage,
|
||||
getFirst,
|
||||
getLast,
|
||||
getLastPageNumber,
|
||||
getMaxCounter,
|
||||
};
|
||||
|
||||
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, key) => {
|
||||
if (!results) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
return this.storage.append(results)
|
||||
.then(() => {
|
||||
const tail = key || ++this.state.tail;
|
||||
|
||||
this.records[tail] = {};
|
||||
results.forEach(({ counter, start_line, end_line, uuid }) => {
|
||||
this.records[tail][counter] = { start_line, end_line };
|
||||
this.uuids[counter] = uuid;
|
||||
});
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
this.pushBack = (results, key) => {
|
||||
if (!results) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
return this.storage.prepend(results)
|
||||
.then(() => {
|
||||
const head = key || --this.state.head;
|
||||
|
||||
this.records[head] = {};
|
||||
results.forEach(({ counter, start_line, end_line, uuid }) => {
|
||||
this.records[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.pages = {};
|
||||
this.state = { head: 0, tail: 0 };
|
||||
};
|
||||
|
||||
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;
|
||||
if (number < 1) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
const pageCount = this.state.head - this.state.tail;
|
||||
|
||||
if (pageCount >= OUTPUT_PAGE_LIMIT) {
|
||||
this.chain = this.chain
|
||||
.then(() => this.popBack())
|
||||
.then(() => {
|
||||
popHeight = this.hooks.getScrollHeight();
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
if (number > lastPageNumber) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
this.chain = this.chain
|
||||
.then(() => this.api.getPage(number))
|
||||
.then(events => this.pushFront(events))
|
||||
.then(() => $q.resolve(popHeight));
|
||||
let promise;
|
||||
|
||||
return this.chain;
|
||||
if (this.pages[number]) {
|
||||
promise = $q.resolve(this.pages[number]);
|
||||
} else {
|
||||
promise = this.api.getPage(number);
|
||||
}
|
||||
|
||||
return promise
|
||||
.then(results => {
|
||||
if (results.length <= 0) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
this.state.tail = number;
|
||||
this.pages[number] = results;
|
||||
|
||||
return $q.resolve(results);
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
if (number < 1) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
const pageCount = this.state.head - this.state.tail;
|
||||
|
||||
if (pageCount >= OUTPUT_PAGE_LIMIT) {
|
||||
this.chain = this.chain
|
||||
.then(() => this.popFront())
|
||||
.then(() => {
|
||||
popHeight = this.hooks.getScrollHeight();
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
if (number > this.api.getLastPageNumber()) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
this.chain = this.chain
|
||||
.then(() => this.api.getPage(number))
|
||||
.then(events => this.pushBack(events))
|
||||
.then(() => $q.resolve(popHeight));
|
||||
let promise;
|
||||
|
||||
return this.chain;
|
||||
if (this.pages[number]) {
|
||||
promise = $q.resolve(this.pages[number]);
|
||||
} else {
|
||||
promise = this.api.getPage(number);
|
||||
}
|
||||
|
||||
return promise
|
||||
.then(results => {
|
||||
if (results.length <= 0) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
this.state.head = number;
|
||||
this.pages[number] = results;
|
||||
|
||||
return $q.resolve(results);
|
||||
});
|
||||
};
|
||||
|
||||
this.clear = () => {
|
||||
const count = this.getRecordCount();
|
||||
this.getLast = () => this.api.getLast()
|
||||
.then(results => {
|
||||
if (results.length <= 0) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
for (let i = 0; i <= count; ++i) {
|
||||
this.chain = this.chain.then(() => this.popBack());
|
||||
}
|
||||
const number = this.api.getLastPageNumber();
|
||||
|
||||
return this.chain;
|
||||
};
|
||||
this.state.head = number;
|
||||
this.state.tail = number;
|
||||
this.pages[number] = results;
|
||||
|
||||
this.getLast = () => this.clear()
|
||||
.then(() => this.api.getLast())
|
||||
.then(events => {
|
||||
const lastPage = this.api.getLastPageNumber();
|
||||
return $q.resolve(results);
|
||||
});
|
||||
|
||||
this.state.head = lastPage;
|
||||
this.state.tail = lastPage;
|
||||
this.getFirst = () => this.api.getFirst()
|
||||
.then(results => {
|
||||
if (results.length <= 0) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
return this.pushBack(events, lastPage);
|
||||
})
|
||||
.then(() => this.getPrevious());
|
||||
|
||||
this.getFirst = () => this.clear()
|
||||
.then(() => this.api.getFirst())
|
||||
.then(events => {
|
||||
this.state.head = 1;
|
||||
this.state.tail = 1;
|
||||
this.pages[1] = results;
|
||||
|
||||
return this.pushBack(events, 1);
|
||||
})
|
||||
.then(() => this.getNext());
|
||||
return $q.resolve(results);
|
||||
});
|
||||
|
||||
this.isOnLastPage = () => this.api.getLastPageNumber() === this.state.tail;
|
||||
this.getRecordCount = () => Object.keys(this.records).length;
|
||||
this.getTailCounter = () => this.state.tail;
|
||||
this.getMaxCounter = () => this.api.getMaxCounter();
|
||||
this.trimTail = () => {
|
||||
const { tail, head } = this.state;
|
||||
let popCount = 0;
|
||||
|
||||
for (let i = tail; i > head; i--) {
|
||||
if (!this.isOverCapacity()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.pages[i]) {
|
||||
popCount += this.pages[i].length;
|
||||
}
|
||||
|
||||
delete this.pages[i];
|
||||
|
||||
this.state.tail--;
|
||||
}
|
||||
|
||||
return popCount;
|
||||
};
|
||||
|
||||
this.trimHead = () => {
|
||||
const { head, tail } = this.state;
|
||||
let popCount = 0;
|
||||
|
||||
for (let i = head; i < tail; i++) {
|
||||
if (!this.isOverCapacity()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.pages[i]) {
|
||||
popCount += this.pages[i].length;
|
||||
}
|
||||
|
||||
delete this.pages[i];
|
||||
|
||||
this.state.head++;
|
||||
}
|
||||
|
||||
return popCount;
|
||||
};
|
||||
|
||||
this.isOverCapacity = () => this.state.tail - this.state.head > OUTPUT_PAGE_LIMIT;
|
||||
}
|
||||
|
||||
PageService.$inject = ['$q'];
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
EVENT_STATS_PLAY,
|
||||
EVENT_START_TASK,
|
||||
OUTPUT_ELEMENT_TBODY,
|
||||
OUTPUT_EVENT_LIMIT,
|
||||
} from './constants';
|
||||
|
||||
const EVENT_GROUPS = [
|
||||
@ -33,105 +34,235 @@ const hasAnsi = input => re.test(input);
|
||||
|
||||
function JobRenderService ($q, $sce, $window) {
|
||||
this.init = ({ compile, toggles }) => {
|
||||
this.parent = null;
|
||||
this.record = {};
|
||||
this.el = $(OUTPUT_ELEMENT_TBODY);
|
||||
this.hooks = { compile };
|
||||
this.el = $(OUTPUT_ELEMENT_TBODY);
|
||||
this.parent = null;
|
||||
|
||||
this.createToggles = toggles;
|
||||
this.state = {
|
||||
collapseAll: false
|
||||
head: 0,
|
||||
tail: 0,
|
||||
collapseAll: false,
|
||||
toggleMode: toggles,
|
||||
};
|
||||
|
||||
this.records = {};
|
||||
this.uuids = {};
|
||||
};
|
||||
|
||||
this.setCollapseAll = value => {
|
||||
this.state.collapseAll = value;
|
||||
Object.keys(this.records).forEach(key => {
|
||||
this.records[key].isCollapsed = value;
|
||||
});
|
||||
};
|
||||
|
||||
this.sortByLineNumber = (a, b) => {
|
||||
if (a.start_line > b.start_line) {
|
||||
this.sortByCounter = (a, b) => {
|
||||
if (a.counter > b.counter) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.start_line < b.start_line) {
|
||||
if (a.counter < b.counter) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
this.transformEventGroup = events => {
|
||||
//
|
||||
// Event Data Transformation / HTML Building
|
||||
//
|
||||
|
||||
this.appendEventGroup = events => {
|
||||
let lines = 0;
|
||||
let html = '';
|
||||
|
||||
events.sort(this.sortByLineNumber);
|
||||
events.sort(this.sortByCounter);
|
||||
|
||||
for (let i = 0; i < events.length; ++i) {
|
||||
const line = this.transformEvent(events[i]);
|
||||
html += line.html;
|
||||
lines += line.count;
|
||||
for (let i = 0; i <= events.length - 1; i++) {
|
||||
const current = events[i];
|
||||
|
||||
if (this.state.tail && current.counter !== this.state.tail + 1) {
|
||||
const missing = this.appendMissingEventGroup(current);
|
||||
|
||||
html += missing.html;
|
||||
lines += missing.count;
|
||||
}
|
||||
|
||||
const eventLines = this.transformEvent(current);
|
||||
|
||||
html += eventLines.html;
|
||||
lines += eventLines.count;
|
||||
}
|
||||
|
||||
return { html, lines };
|
||||
};
|
||||
|
||||
this.transformEvent = event => {
|
||||
if (this.record[event.uuid]) {
|
||||
this.appendMissingEventGroup = event => {
|
||||
const tailUUID = this.uuids[this.state.tail];
|
||||
const tailRecord = this.records[tailUUID];
|
||||
|
||||
if (!tailRecord) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
let uuid;
|
||||
|
||||
if (tailRecord.isMissing) {
|
||||
uuid = tailUUID;
|
||||
} else {
|
||||
uuid = `${event.counter}-${tailUUID}`;
|
||||
this.records[uuid] = { uuid, counters: [], lineCount: 1, isMissing: true };
|
||||
}
|
||||
|
||||
for (let i = this.state.tail + 1; i < event.counter; i++) {
|
||||
this.records[uuid].counters.push(i);
|
||||
this.uuids[i] = uuid;
|
||||
}
|
||||
|
||||
if (tailRecord.isMissing) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
if (tailRecord.end === event.start_line) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
const html = this.buildRowHTML(this.records[uuid]);
|
||||
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];
|
||||
|
||||
if (this.state.head && current.counter !== this.state.head - 1) {
|
||||
const missing = this.prependMissingEventGroup(current);
|
||||
|
||||
html = missing.html + html;
|
||||
lines += missing.count;
|
||||
}
|
||||
|
||||
const eventLines = this.transformEvent(current);
|
||||
|
||||
html = eventLines.html + html;
|
||||
lines += eventLines.count;
|
||||
}
|
||||
|
||||
return { html, lines };
|
||||
};
|
||||
|
||||
this.prependMissingEventGroup = event => {
|
||||
const headUUID = this.uuids[this.state.head];
|
||||
const headRecord = this.records[headUUID];
|
||||
|
||||
if (!headRecord) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
let uuid;
|
||||
|
||||
if (headRecord.isMissing) {
|
||||
uuid = headUUID;
|
||||
} else {
|
||||
uuid = `${headUUID}-${event.counter}`;
|
||||
this.records[uuid] = { uuid, counters: [], lineCount: 1, isMissing: true };
|
||||
}
|
||||
|
||||
for (let i = this.state.head - 1; i > event.counter; i--) {
|
||||
this.records[uuid].counters.unshift(i);
|
||||
this.uuids[i] = uuid;
|
||||
}
|
||||
|
||||
if (headRecord.isMissing) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
if (event.end_line === headRecord.start) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
const html = this.buildRowHTML(this.records[uuid]);
|
||||
const count = 1;
|
||||
|
||||
return { html, count };
|
||||
};
|
||||
|
||||
this.transformEvent = event => {
|
||||
if (!event || !event.stdout) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
if (event.uuid && this.records[event.uuid]) {
|
||||
return { html: '', count: 0 };
|
||||
}
|
||||
|
||||
const stdout = this.sanitize(event.stdout);
|
||||
const lines = stdout.split('\r\n');
|
||||
const record = this.createRecord(event, lines);
|
||||
|
||||
let html = '';
|
||||
let count = lines.length;
|
||||
let ln = event.start_line;
|
||||
|
||||
const current = this.createRecord(ln, lines, event);
|
||||
|
||||
const html = lines.reduce((concat, line, i) => {
|
||||
for (let i = 0; i <= lines.length - 1; i++) {
|
||||
ln++;
|
||||
|
||||
const line = lines[i];
|
||||
const isLastLine = i === lines.length - 1;
|
||||
|
||||
let row = this.createRow(current, ln, line);
|
||||
let row = this.buildRowHTML(record, ln, line);
|
||||
|
||||
if (current && current.isTruncated && isLastLine) {
|
||||
row += this.createRow(current);
|
||||
if (record && record.isTruncated && isLastLine) {
|
||||
row += this.buildRowHTML(record);
|
||||
count++;
|
||||
}
|
||||
|
||||
return `${concat}${row}`;
|
||||
}, '');
|
||||
html += row;
|
||||
}
|
||||
|
||||
return { html, count };
|
||||
};
|
||||
|
||||
this.isHostEvent = (event) => {
|
||||
if (typeof event.host === 'number') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.type === 'project_update_event' &&
|
||||
event.event !== 'runner_on_skipped' &&
|
||||
event.event_data.host) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.createRecord = (ln, lines, event) => {
|
||||
if (!event.uuid) {
|
||||
this.createRecord = (event, lines) => {
|
||||
if (!event.counter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const info = {
|
||||
if (!this.state.head || event.counter < this.state.head) {
|
||||
this.state.head = event.counter;
|
||||
}
|
||||
|
||||
if (!this.state.tail || event.counter > this.state.tail) {
|
||||
this.state.tail = event.counter;
|
||||
}
|
||||
|
||||
if (!event.uuid) {
|
||||
this.uuids[event.counter] = event.counter;
|
||||
this.records[event.counter] = { counters: [event.counter], lineCount: lines.length };
|
||||
|
||||
return this.records[event.counter];
|
||||
}
|
||||
|
||||
let isHost = false;
|
||||
if (typeof event.host === 'number') {
|
||||
isHost = true;
|
||||
} else if (event.type === 'project_update_event' &&
|
||||
event.event !== 'runner_on_skipped' &&
|
||||
event.event_data.host) {
|
||||
isHost = true;
|
||||
}
|
||||
|
||||
const record = {
|
||||
isHost,
|
||||
id: event.id,
|
||||
line: ln + 1,
|
||||
line: event.start_line + 1,
|
||||
name: event.event,
|
||||
uuid: event.uuid,
|
||||
level: event.event_level,
|
||||
@ -139,54 +270,49 @@ function JobRenderService ($q, $sce, $window) {
|
||||
end: event.end_line,
|
||||
isTruncated: (event.end_line - event.start_line) > lines.length,
|
||||
lineCount: lines.length,
|
||||
isHost: this.isHostEvent(event),
|
||||
isCollapsed: this.state.collapseAll,
|
||||
counters: [event.counter],
|
||||
};
|
||||
|
||||
if (event.parent_uuid) {
|
||||
info.parents = this.getParentEvents(event.parent_uuid);
|
||||
if (this.record[event.parent_uuid]) {
|
||||
info.isCollapsed = this.record[event.parent_uuid].isCollapsed;
|
||||
record.parents = this.getParentEvents(event.parent_uuid);
|
||||
if (this.records[event.parent_uuid]) {
|
||||
record.isCollapsed = this.records[event.parent_uuid].isCollapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.isTruncated) {
|
||||
info.truncatedAt = event.start_line + lines.length;
|
||||
if (record.isTruncated) {
|
||||
record.truncatedAt = event.start_line + lines.length;
|
||||
}
|
||||
|
||||
if (EVENT_GROUPS.includes(event.event)) {
|
||||
info.isParent = true;
|
||||
record.isParent = true;
|
||||
|
||||
if (event.event_level === 1) {
|
||||
this.parent = event.uuid;
|
||||
}
|
||||
|
||||
if (event.parent_uuid) {
|
||||
if (this.record[event.parent_uuid]) {
|
||||
if (this.record[event.parent_uuid].children &&
|
||||
!this.record[event.parent_uuid].children.includes(event.uuid)) {
|
||||
this.record[event.parent_uuid].children.push(event.uuid);
|
||||
if (this.records[event.parent_uuid]) {
|
||||
if (this.records[event.parent_uuid].children &&
|
||||
!this.records[event.parent_uuid].children.includes(event.uuid)) {
|
||||
this.records[event.parent_uuid].children.push(event.uuid);
|
||||
} else {
|
||||
this.record[event.parent_uuid].children = [event.uuid];
|
||||
this.records[event.parent_uuid].children = [event.uuid];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TIME_EVENTS.includes(event.event)) {
|
||||
info.time = this.getTimestamp(event.created);
|
||||
info.line++;
|
||||
record.time = this.getTimestamp(event.created);
|
||||
record.line++;
|
||||
}
|
||||
|
||||
this.record[event.uuid] = info;
|
||||
this.records[event.uuid] = record;
|
||||
this.uuids[event.counter] = event.uuid;
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
this.getRecord = uuid => this.record[uuid];
|
||||
|
||||
this.deleteRecord = uuid => {
|
||||
delete this.record[uuid];
|
||||
return record;
|
||||
};
|
||||
|
||||
this.getParentEvents = (uuid, list) => {
|
||||
@ -194,14 +320,14 @@ function JobRenderService ($q, $sce, $window) {
|
||||
// always push its parent if exists
|
||||
list.push(uuid);
|
||||
// if we can get grandparent in current visible lines, we also push it
|
||||
if (this.record[uuid] && this.record[uuid].parents) {
|
||||
list = list.concat(this.record[uuid].parents);
|
||||
if (this.records[uuid] && this.records[uuid].parents) {
|
||||
list = list.concat(this.records[uuid].parents);
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
this.createRow = (current, ln, content) => {
|
||||
this.buildRowHTML = (record, ln, content) => {
|
||||
let id = '';
|
||||
let icon = '';
|
||||
let timestamp = '';
|
||||
@ -209,17 +335,23 @@ function JobRenderService ($q, $sce, $window) {
|
||||
let tdEvent = '';
|
||||
let classList = '';
|
||||
|
||||
if (record.isMissing) {
|
||||
return `<div id="${record.uuid}" class="at-Stdout-row">
|
||||
<div class="at-Stdout-toggle"></div>
|
||||
<div class="at-Stdout-line at-Stdout-line--clickable" ng-click="vm.showMissingEvents('${record.uuid}')">...</div></div>`;
|
||||
}
|
||||
|
||||
content = content || '';
|
||||
|
||||
if (hasAnsi(content)) {
|
||||
content = ansi.toHtml(content);
|
||||
}
|
||||
|
||||
if (current) {
|
||||
if (this.createToggles && current.isParent && current.line === ln) {
|
||||
id = current.uuid;
|
||||
if (record) {
|
||||
if (this.state.toggleMode && record.isParent && record.line === ln) {
|
||||
id = record.uuid;
|
||||
|
||||
if (current.isCollapsed) {
|
||||
if (record.isCollapsed) {
|
||||
icon = 'fa-angle-right';
|
||||
} else {
|
||||
icon = 'fa-angle-down';
|
||||
@ -228,16 +360,16 @@ function JobRenderService ($q, $sce, $window) {
|
||||
tdToggle = `<div class="at-Stdout-toggle" ng-click="vm.toggleCollapse('${id}')"><i class="fa ${icon} can-toggle"></i></div>`;
|
||||
}
|
||||
|
||||
if (current.isHost) {
|
||||
tdEvent = `<div class="at-Stdout-event--host" ng-click="vm.showHostDetails('${current.id}', '${current.uuid}')"><span ng-non-bindable>${content}</span></div>`;
|
||||
if (record.isHost) {
|
||||
tdEvent = `<div class="at-Stdout-event--host" ng-click="vm.showHostDetails('${record.id}', '${record.uuid}')"><span ng-non-bindable>${content}</span></div>`;
|
||||
}
|
||||
|
||||
if (current.time && current.line === ln) {
|
||||
timestamp = `<span>${current.time}</span>`;
|
||||
if (record.time && record.line === ln) {
|
||||
timestamp = `<span>${record.time}</span>`;
|
||||
}
|
||||
|
||||
if (current.parents) {
|
||||
classList = current.parents.reduce((list, uuid) => `${list} child-of-${uuid}`, '');
|
||||
if (record.parents) {
|
||||
classList = record.parents.reduce((list, uuid) => `${list} child-of-${uuid}`, '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,8 +385,8 @@ function JobRenderService ($q, $sce, $window) {
|
||||
ln = '...';
|
||||
}
|
||||
|
||||
if (current && current.isCollapsed) {
|
||||
if (current.level === 3 || current.level === 0) {
|
||||
if (record && record.isCollapsed) {
|
||||
if (record.level === 3 || record.level === 0) {
|
||||
classList += ' hidden';
|
||||
}
|
||||
}
|
||||
@ -277,6 +409,10 @@ function JobRenderService ($q, $sce, $window) {
|
||||
return `${hour}:${minute}:${second}`;
|
||||
};
|
||||
|
||||
//
|
||||
// Element Operations
|
||||
//
|
||||
|
||||
this.remove = elements => this.requestAnimationFrame(() => elements.remove());
|
||||
|
||||
this.requestAnimationFrame = fn => $q(resolve => {
|
||||
@ -295,7 +431,7 @@ function JobRenderService ($q, $sce, $window) {
|
||||
return this.requestAnimationFrame();
|
||||
};
|
||||
|
||||
this.clear = () => {
|
||||
this.removeAll = () => {
|
||||
const elements = this.el.children();
|
||||
return this.remove(elements);
|
||||
};
|
||||
@ -317,7 +453,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);
|
||||
@ -332,7 +468,7 @@ function JobRenderService ($q, $sce, $window) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const result = this.transformEventGroup(events);
|
||||
const result = this.appendEventGroup(events);
|
||||
const html = this.trustHtml(result.html);
|
||||
|
||||
const newElements = angular.element(html);
|
||||
@ -343,8 +479,110 @@ function JobRenderService ($q, $sce, $window) {
|
||||
};
|
||||
|
||||
this.trustHtml = html => $sce.getTrustedHtml($sce.trustAsHtml(html));
|
||||
|
||||
this.sanitize = html => entities.encode(html);
|
||||
|
||||
//
|
||||
// Event Counter Methods - External code should prefer these.
|
||||
//
|
||||
|
||||
this.clear = () => this.removeAll()
|
||||
.then(() => {
|
||||
const head = this.getHeadCounter();
|
||||
const tail = this.getTailCounter();
|
||||
|
||||
for (let i = head; i <= tail; ++i) {
|
||||
const uuid = this.uuids[i];
|
||||
|
||||
if (uuid) {
|
||||
delete this.records[uuid];
|
||||
delete this.uuids[i];
|
||||
}
|
||||
}
|
||||
|
||||
this.state.head = 0;
|
||||
this.state.tail = 0;
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
|
||||
this.pushFront = events => {
|
||||
const tail = this.getTailCounter();
|
||||
|
||||
return this.append(events.filter(({ counter }) => counter > tail));
|
||||
};
|
||||
|
||||
this.pushBack = events => {
|
||||
const head = this.getHeadCounter();
|
||||
const tail = this.getTailCounter();
|
||||
|
||||
return this.prepend(events.filter(({ counter }) => counter < head || counter > tail));
|
||||
};
|
||||
|
||||
this.popFront = count => {
|
||||
if (!count || count <= 0) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const max = this.state.tail;
|
||||
const min = max - count;
|
||||
|
||||
let lines = 0;
|
||||
|
||||
for (let i = max; i >= min; --i) {
|
||||
const uuid = this.uuids[i];
|
||||
|
||||
if (!uuid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.records[uuid].counters.pop();
|
||||
delete this.uuids[i];
|
||||
|
||||
if (this.records[uuid].counters.length === 0) {
|
||||
lines += this.records[uuid].lineCount;
|
||||
|
||||
delete this.records[uuid];
|
||||
this.state.tail--;
|
||||
}
|
||||
}
|
||||
|
||||
return this.pop(lines);
|
||||
};
|
||||
|
||||
this.popBack = count => {
|
||||
if (!count || count <= 0) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const min = this.state.head;
|
||||
const max = min + count;
|
||||
|
||||
let lines = 0;
|
||||
|
||||
for (let i = min; i <= max; ++i) {
|
||||
const uuid = this.uuids[i];
|
||||
|
||||
if (!uuid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.records[uuid].counters.shift();
|
||||
delete this.uuids[i];
|
||||
|
||||
if (this.records[uuid].counters.length === 0) {
|
||||
lines += this.records[uuid].lineCount;
|
||||
|
||||
delete this.records[uuid];
|
||||
this.state.head++;
|
||||
}
|
||||
}
|
||||
|
||||
return this.shift(lines);
|
||||
};
|
||||
|
||||
this.getHeadCounter = () => this.state.head;
|
||||
this.getTailCounter = () => this.state.tail;
|
||||
this.getCapacity = () => OUTPUT_EVENT_LIMIT - (this.getTailCounter() - this.getHeadCounter());
|
||||
}
|
||||
|
||||
JobRenderService.$inject = ['$q', '$sce', '$window'];
|
||||
|
||||
@ -1,42 +1,12 @@
|
||||
/* eslint camelcase: 0 */
|
||||
import {
|
||||
API_MAX_PAGE_SIZE,
|
||||
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 = (storage, api, { getScrollHeight }) => {
|
||||
const { prepend, append, shift, pop, getRecord, deleteRecord, clear } = storage;
|
||||
const { getRange, getFirst, getLast, getMaxCounter } = api;
|
||||
this.init = ({ getRange, getFirst, getLast, getMaxCounter }, storage) => {
|
||||
const { getHeadCounter, getTailCounter } = storage;
|
||||
|
||||
this.api = {
|
||||
getRange,
|
||||
@ -46,32 +16,20 @@ function SlidingWindowService ($q) {
|
||||
};
|
||||
|
||||
this.storage = {
|
||||
clear,
|
||||
prepend,
|
||||
append,
|
||||
shift,
|
||||
pop,
|
||||
getRecord,
|
||||
deleteRecord,
|
||||
getHeadCounter,
|
||||
getTailCounter,
|
||||
};
|
||||
|
||||
this.hooks = {
|
||||
getScrollHeight,
|
||||
};
|
||||
|
||||
this.lines = {};
|
||||
this.uuids = {};
|
||||
this.chain = $q.resolve();
|
||||
|
||||
this.state = { head: null, tail: null };
|
||||
this.cache = { first: null };
|
||||
|
||||
this.buffer = {
|
||||
events: [],
|
||||
min: 0,
|
||||
max: 0,
|
||||
count: 0,
|
||||
};
|
||||
|
||||
this.cache = {
|
||||
first: null
|
||||
};
|
||||
};
|
||||
|
||||
this.getBoundedRange = range => {
|
||||
@ -92,273 +50,46 @@ function SlidingWindowService ($q) {
|
||||
return this.getBoundedRange([head - 1 - displacement, head - 1]);
|
||||
};
|
||||
|
||||
this.createRecord = ({ counter, uuid, start_line, end_line }) => {
|
||||
this.lines[counter] = end_line - start_line;
|
||||
this.uuids[counter] = uuid;
|
||||
|
||||
if (this.state.tail === null) {
|
||||
this.state.tail = counter;
|
||||
}
|
||||
|
||||
if (counter > this.state.tail) {
|
||||
this.state.tail = counter;
|
||||
}
|
||||
|
||||
if (this.state.head === null) {
|
||||
this.state.head = counter;
|
||||
}
|
||||
|
||||
if (counter < this.state.head) {
|
||||
this.state.head = counter;
|
||||
}
|
||||
};
|
||||
|
||||
this.deleteRecord = counter => {
|
||||
this.storage.deleteRecord(this.uuids[counter]);
|
||||
|
||||
delete this.uuids[counter];
|
||||
delete this.lines[counter];
|
||||
};
|
||||
|
||||
this.getLineCount = counter => {
|
||||
const record = this.storage.getRecord(counter);
|
||||
|
||||
if (record && record.lineCount) {
|
||||
return record.lineCount;
|
||||
}
|
||||
|
||||
if (this.lines[counter]) {
|
||||
return this.lines[counter];
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
this.pushFront = events => {
|
||||
const tail = this.getTailCounter();
|
||||
const newEvents = events.filter(({ counter }) => counter > tail);
|
||||
|
||||
return this.storage.append(newEvents)
|
||||
.then(() => {
|
||||
newEvents.forEach(event => this.createRecord(event));
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
this.pushBack = events => {
|
||||
const [head, tail] = this.getRange();
|
||||
const newEvents = events
|
||||
.filter(({ counter }) => counter < head || counter > tail);
|
||||
|
||||
return this.storage.prepend(newEvents)
|
||||
.then(() => {
|
||||
newEvents.forEach(event => this.createRecord(event));
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
this.popFront = count => {
|
||||
if (!count || count <= 0) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const max = this.getTailCounter();
|
||||
const min = max - count;
|
||||
|
||||
let lines = 0;
|
||||
|
||||
for (let i = max; i >= min; --i) {
|
||||
lines += this.getLineCount(i);
|
||||
}
|
||||
|
||||
return this.storage.pop(lines)
|
||||
.then(() => {
|
||||
for (let i = max; i >= min; --i) {
|
||||
this.deleteRecord(i);
|
||||
this.state.tail--;
|
||||
}
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
this.popBack = count => {
|
||||
if (!count || count <= 0) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const min = this.getHeadCounter();
|
||||
const max = min + count;
|
||||
|
||||
let lines = 0;
|
||||
|
||||
for (let i = min; i <= max; ++i) {
|
||||
lines += this.getLineCount(i);
|
||||
}
|
||||
|
||||
return this.storage.shift(lines)
|
||||
.then(() => {
|
||||
for (let i = min; i <= max; ++i) {
|
||||
this.deleteRecord(i);
|
||||
this.state.head++;
|
||||
}
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
this.clear = () => this.storage.clear()
|
||||
.then(() => {
|
||||
const [head, tail] = this.getRange();
|
||||
|
||||
for (let i = head; i <= tail; ++i) {
|
||||
this.deleteRecord(i);
|
||||
}
|
||||
|
||||
this.state.head = null;
|
||||
this.state.tail = null;
|
||||
|
||||
return $q.resolve();
|
||||
});
|
||||
|
||||
this.getNext = (displacement = OUTPUT_PAGE_SIZE) => {
|
||||
const next = this.getNextRange(displacement);
|
||||
const [head, tail] = this.getRange();
|
||||
|
||||
this.chain = this.chain
|
||||
.then(() => this.api.getRange(next))
|
||||
.then(events => {
|
||||
const results = getContinuous(events);
|
||||
const min = Math.min(...results.map(({ counter }) => counter));
|
||||
|
||||
if (min > tail + 1) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
return $q.resolve(results);
|
||||
})
|
||||
.then(results => {
|
||||
const count = (tail - head + results.length);
|
||||
const excess = count - OUTPUT_EVENT_LIMIT;
|
||||
|
||||
return this.popBack(excess)
|
||||
.then(() => {
|
||||
const popHeight = this.hooks.getScrollHeight();
|
||||
|
||||
return this.pushFront(results).then(() => $q.resolve(popHeight));
|
||||
});
|
||||
});
|
||||
|
||||
return this.chain;
|
||||
return this.api.getRange(next);
|
||||
};
|
||||
|
||||
this.getPrevious = (displacement = OUTPUT_PAGE_SIZE) => {
|
||||
const previous = this.getPreviousRange(displacement);
|
||||
const [head, tail] = this.getRange();
|
||||
|
||||
this.chain = this.chain
|
||||
.then(() => this.api.getRange(previous))
|
||||
.then(events => {
|
||||
const results = getContinuous(events, true);
|
||||
const max = Math.max(...results.map(({ counter }) => counter));
|
||||
|
||||
if (head > max + 1) {
|
||||
return $q.resolve([]);
|
||||
}
|
||||
|
||||
return $q.resolve(results);
|
||||
})
|
||||
.then(results => {
|
||||
const count = (tail - head + results.length);
|
||||
const excess = count - OUTPUT_EVENT_LIMIT;
|
||||
|
||||
return this.popFront(excess)
|
||||
.then(() => {
|
||||
const popHeight = this.hooks.getScrollHeight();
|
||||
|
||||
return this.pushBack(results).then(() => $q.resolve(popHeight));
|
||||
});
|
||||
});
|
||||
|
||||
return this.chain;
|
||||
return this.api.getRange(previous);
|
||||
};
|
||||
|
||||
this.getFirst = () => {
|
||||
this.chain = this.chain
|
||||
.then(() => this.clear())
|
||||
.then(() => {
|
||||
if (this.cache.first) {
|
||||
return $q.resolve(this.cache.first);
|
||||
}
|
||||
if (this.cache.first) {
|
||||
return $q.resolve(this.cache.first);
|
||||
}
|
||||
|
||||
return this.api.getFirst();
|
||||
})
|
||||
return this.api.getFirst()
|
||||
.then(events => {
|
||||
if (events.length === OUTPUT_PAGE_SIZE) {
|
||||
this.cache.first = events;
|
||||
}
|
||||
|
||||
return this.pushFront(events);
|
||||
return $q.resolve(events);
|
||||
});
|
||||
|
||||
return this.chain
|
||||
.then(() => this.getNext());
|
||||
};
|
||||
|
||||
this.getLast = () => {
|
||||
this.chain = this.chain
|
||||
.then(() => this.getFrames())
|
||||
.then(frames => {
|
||||
if (frames.length > 0) {
|
||||
return $q.resolve(frames);
|
||||
}
|
||||
this.getLast = () => this.getFrames()
|
||||
.then(frames => {
|
||||
if (frames.length > 0) {
|
||||
return $q.resolve(frames);
|
||||
}
|
||||
|
||||
return this.api.getLast();
|
||||
})
|
||||
.then(events => {
|
||||
const min = Math.min(...events.map(({ counter }) => counter));
|
||||
|
||||
if (min <= this.getTailCounter() + 1) {
|
||||
return this.pushFront(events);
|
||||
}
|
||||
|
||||
return this.clear()
|
||||
.then(() => this.pushBack(events));
|
||||
});
|
||||
|
||||
return this.chain
|
||||
.then(() => this.getPrevious());
|
||||
};
|
||||
|
||||
this.getTailCounter = () => {
|
||||
if (this.state.tail === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.state.tail < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.state.tail;
|
||||
};
|
||||
|
||||
this.getHeadCounter = () => {
|
||||
if (this.state.head === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.state.head < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.state.head;
|
||||
};
|
||||
return this.api.getLast();
|
||||
});
|
||||
|
||||
this.pushFrames = events => {
|
||||
const head = this.getHeadCounter();
|
||||
const tail = this.getTailCounter();
|
||||
const frames = this.buffer.events.concat(events);
|
||||
const [head, tail] = this.getRange();
|
||||
|
||||
let min;
|
||||
let max;
|
||||
@ -367,7 +98,7 @@ function SlidingWindowService ($q) {
|
||||
for (let i = frames.length - 1; i >= 0; i--) {
|
||||
count++;
|
||||
|
||||
if (count > API_MAX_PAGE_SIZE) {
|
||||
if (count > OUTPUT_MAX_BUFFER_LENGTH) {
|
||||
frames.splice(i, 1);
|
||||
|
||||
count--;
|
||||
@ -388,27 +119,34 @@ function SlidingWindowService ($q) {
|
||||
this.buffer.max = max;
|
||||
this.buffer.count = count;
|
||||
|
||||
if (min >= head && min <= tail + 1) {
|
||||
return frames.filter(({ counter }) => counter > tail);
|
||||
if (tail - head === 0) {
|
||||
return frames;
|
||||
}
|
||||
|
||||
return [];
|
||||
return frames.filter(({ counter }) => counter > tail);
|
||||
};
|
||||
|
||||
this.getFrames = () => $q.resolve(this.buffer.events);
|
||||
|
||||
this.getMaxCounter = () => {
|
||||
if (this.buffer.min) {
|
||||
return this.buffer.min;
|
||||
if (this.buffer.max && this.buffer.max > 1) {
|
||||
return this.buffer.max;
|
||||
}
|
||||
|
||||
return this.api.getMaxCounter();
|
||||
};
|
||||
|
||||
this.isOnLastPage = () => this.getTailCounter() >= (this.getMaxCounter() - OUTPUT_PAGE_SIZE);
|
||||
this.getRange = () => [this.getHeadCounter(), this.getTailCounter()];
|
||||
this.getRecordCount = () => Object.keys(this.lines).length;
|
||||
this.getCapacity = () => OUTPUT_EVENT_LIMIT - this.getRecordCount();
|
||||
this.isOnLastPage = () => {
|
||||
if (this.buffer.min) {
|
||||
return this.getTailCounter() >= this.buffer.min - 1;
|
||||
}
|
||||
|
||||
return this.getTailCounter() >= this.getMaxCounter() - OUTPUT_PAGE_SIZE;
|
||||
};
|
||||
|
||||
this.isOnFirstPage = () => this.getHeadCounter() === 1;
|
||||
this.getTailCounter = () => this.storage.getTailCounter();
|
||||
this.getHeadCounter = () => this.storage.getHeadCounter();
|
||||
}
|
||||
|
||||
SlidingWindowService.$inject = ['$q'];
|
||||
|
||||
@ -1,37 +1,49 @@
|
||||
/* eslint camelcase: 0 */
|
||||
import {
|
||||
EVENT_STATS_PLAY,
|
||||
OUTPUT_MAX_BUFFER_LENGTH,
|
||||
OUTPUT_MAX_LAG,
|
||||
OUTPUT_PAGE_SIZE,
|
||||
OUTPUT_EVENT_LIMIT,
|
||||
} from './constants';
|
||||
|
||||
const rx = [];
|
||||
|
||||
function OutputStream ($q) {
|
||||
this.init = ({ bufferAdd, bufferEmpty, onFrames, onFrameRate, onStop }) => {
|
||||
this.init = ({ onFrames, onFrameRate, onStop }) => {
|
||||
this.hooks = {
|
||||
bufferAdd,
|
||||
bufferEmpty,
|
||||
onFrames,
|
||||
onFrameRate,
|
||||
onStop,
|
||||
};
|
||||
|
||||
this.bufferInit();
|
||||
};
|
||||
|
||||
this.bufferInit = () => {
|
||||
rx.length = 0;
|
||||
|
||||
this.counters = {
|
||||
used: [],
|
||||
ready: [],
|
||||
min: 1,
|
||||
max: 0,
|
||||
max: -1,
|
||||
ready: -1,
|
||||
final: null,
|
||||
used: [],
|
||||
missing: [],
|
||||
total: 0,
|
||||
length: 0,
|
||||
};
|
||||
|
||||
this.state = {
|
||||
ending: false,
|
||||
ended: false,
|
||||
overflow: false,
|
||||
};
|
||||
|
||||
this.lag = 0;
|
||||
this.chain = $q.resolve();
|
||||
|
||||
this.factors = this.calcFactors(OUTPUT_PAGE_SIZE);
|
||||
this.factors = this.calcFactors(OUTPUT_EVENT_LIMIT);
|
||||
this.setFramesPerRender();
|
||||
};
|
||||
|
||||
@ -63,36 +75,87 @@ function OutputStream ($q) {
|
||||
}
|
||||
};
|
||||
|
||||
this.updateCounterState = ({ counter }) => {
|
||||
this.counters.used.push(counter);
|
||||
this.bufferAdd = event => {
|
||||
const { counter } = event;
|
||||
|
||||
if (counter > this.counters.max) {
|
||||
this.counters.max = counter;
|
||||
}
|
||||
|
||||
let ready;
|
||||
const used = [];
|
||||
const missing = [];
|
||||
let minReady;
|
||||
let maxReady;
|
||||
|
||||
for (let i = this.counters.min; i <= this.counters.max; i++) {
|
||||
if (this.counters.used.indexOf(i) === -1) {
|
||||
missing.push(i);
|
||||
} else if (missing.length === 0) {
|
||||
maxReady = i;
|
||||
if (i === counter) {
|
||||
rx.push(event);
|
||||
used.push(i);
|
||||
this.counters.length += 1;
|
||||
} else {
|
||||
missing.push(i);
|
||||
}
|
||||
} else {
|
||||
used.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (maxReady) {
|
||||
minReady = this.counters.min;
|
||||
const excess = this.counters.length - OUTPUT_MAX_BUFFER_LENGTH;
|
||||
this.state.overflow = (excess > 0);
|
||||
|
||||
this.counters.min = maxReady + 1;
|
||||
this.counters.used = this.counters.used.filter(c => c > maxReady);
|
||||
if (missing.length === 0) {
|
||||
ready = this.counters.max;
|
||||
} else if (this.state.overflow) {
|
||||
ready = this.counters.min + this.framesPerRender;
|
||||
} else {
|
||||
ready = missing[0] - 1;
|
||||
}
|
||||
|
||||
this.counters.total += 1;
|
||||
this.counters.ready = ready;
|
||||
this.counters.used = used;
|
||||
this.counters.missing = missing;
|
||||
this.counters.ready = [minReady, maxReady];
|
||||
};
|
||||
|
||||
return this.counters.ready;
|
||||
this.bufferEmpty = threshold => {
|
||||
let removed = [];
|
||||
|
||||
for (let i = rx.length - 1; i >= 0; i--) {
|
||||
if (rx[i].counter <= threshold) {
|
||||
removed = removed.concat(rx.splice(i, 1));
|
||||
}
|
||||
}
|
||||
|
||||
this.counters.min = threshold + 1;
|
||||
this.counters.used = this.counters.used.filter(c => c > threshold);
|
||||
this.counters.length = rx.length;
|
||||
|
||||
return removed;
|
||||
};
|
||||
|
||||
this.isReadyToRender = () => {
|
||||
const { total } = this.counters;
|
||||
const readyCount = this.counters.ready - this.counters.min;
|
||||
|
||||
if (readyCount <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.state.ending) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (total % this.framesPerRender === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (total < OUTPUT_PAGE_SIZE) {
|
||||
if (readyCount % this.framesPerRender === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.pushJobEvent = data => {
|
||||
@ -105,25 +168,24 @@ function OutputStream ($q) {
|
||||
this.counters.final = data.counter;
|
||||
}
|
||||
|
||||
const [minReady, maxReady] = this.updateCounterState(data);
|
||||
const count = this.hooks.bufferAdd(data);
|
||||
this.bufferAdd(data);
|
||||
|
||||
if (count % OUTPUT_PAGE_SIZE === 0) {
|
||||
if (this.counters.total % OUTPUT_PAGE_SIZE === 0) {
|
||||
this.setFramesPerRender();
|
||||
}
|
||||
|
||||
const isReady = maxReady && (this.state.ending ||
|
||||
count % this.framesPerRender === 0 ||
|
||||
count < OUTPUT_PAGE_SIZE && (maxReady - minReady) % this.framesPerRender === 0);
|
||||
|
||||
if (!isReady) {
|
||||
if (!this.isReadyToRender()) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const isLastFrame = this.state.ending && (maxReady >= this.counters.final);
|
||||
const events = this.hooks.bufferEmpty(minReady, maxReady);
|
||||
const isLast = this.state.ending && (this.counters.ready >= this.counters.final);
|
||||
const events = this.bufferEmpty(this.counters.ready);
|
||||
|
||||
return this.emitFrames(events, isLastFrame);
|
||||
if (events.length > 0) {
|
||||
return this.emitFrames(events, isLast);
|
||||
}
|
||||
|
||||
return $q.resolve();
|
||||
})
|
||||
.then(() => --this.lag);
|
||||
|
||||
@ -136,16 +198,20 @@ function OutputStream ($q) {
|
||||
this.state.ending = true;
|
||||
this.counters.final = counter;
|
||||
|
||||
if (counter >= this.counters.min) {
|
||||
if (counter > this.counters.ready) {
|
||||
return $q.resolve();
|
||||
}
|
||||
|
||||
const readyCount = this.counters.ready - this.counters.min;
|
||||
|
||||
let events = [];
|
||||
if (this.counters.ready.length > 0) {
|
||||
events = this.hooks.bufferEmpty(...this.counters.ready);
|
||||
if (readyCount > 0) {
|
||||
events = this.bufferEmpty(this.counters.ready);
|
||||
|
||||
return this.emitFrames(events, true);
|
||||
}
|
||||
|
||||
return this.emitFrames(events, true);
|
||||
return $q.resolve();
|
||||
});
|
||||
|
||||
return this.chain;
|
||||
@ -160,7 +226,6 @@ function OutputStream ($q) {
|
||||
this.hooks.onStop();
|
||||
}
|
||||
|
||||
this.counters.ready.length = 0;
|
||||
return $q.resolve();
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user