mirror of
https://github.com/ansible/awx.git
synced 2026-01-27 08:31:28 -03:30
JobOutput: extract multiple helper functions
This commit is contained in:
parent
b5708a8cc4
commit
b5bc9bb3f4
@ -29,8 +29,11 @@ import PageControls from './PageControls';
|
|||||||
import HostEventModal from './HostEventModal';
|
import HostEventModal from './HostEventModal';
|
||||||
import JobOutputSearch from './JobOutputSearch';
|
import JobOutputSearch from './JobOutputSearch';
|
||||||
import { HostStatusBar, OutputToolbar } from './shared';
|
import { HostStatusBar, OutputToolbar } from './shared';
|
||||||
import getRowRangePageSize from './shared/jobOutputUtils';
|
|
||||||
import getLineTextHtml from './getLineTextHtml';
|
import getLineTextHtml from './getLineTextHtml';
|
||||||
|
import connectJobSocket, { closeWebSocket } from './connectJobSocket';
|
||||||
|
import getEventRequestParams from './getEventRequestParams';
|
||||||
|
import isHostEvent from './isHostEvent';
|
||||||
|
import { fetchCount, normalizeEvents } from './loadJobEvents';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('job_output', {
|
const QS_CONFIG = getQSConfig('job_output', {
|
||||||
order_by: 'counter',
|
order_by: 'counter',
|
||||||
@ -77,101 +80,11 @@ const OutputFooter = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let ws;
|
|
||||||
function connectJobSocket({ type, id }, onMessage) {
|
|
||||||
ws = new WebSocket(
|
|
||||||
`${window.location.protocol === 'http:' ? 'ws:' : 'wss:'}//${
|
|
||||||
window.location.host
|
|
||||||
}/websocket/`
|
|
||||||
);
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
const xrftoken = `; ${document.cookie}`
|
|
||||||
.split('; csrftoken=')
|
|
||||||
.pop()
|
|
||||||
.split(';')
|
|
||||||
.shift();
|
|
||||||
const eventGroup = `${type}_events`;
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
xrftoken,
|
|
||||||
groups: { jobs: ['summary', 'status_changed'], [eventGroup]: [id] },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (e) => {
|
|
||||||
onMessage(JSON.parse(e.data));
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = (e) => {
|
|
||||||
if (e.code !== 1000) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.debug('Socket closed. Reconnecting...', e);
|
|
||||||
setTimeout(() => {
|
|
||||||
connectJobSocket({ type, id }, onMessage);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (err) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.debug('Socket error: ', err, 'Disconnecting...');
|
|
||||||
ws.close();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function range(low, high) {
|
|
||||||
const numbers = [];
|
|
||||||
for (let n = low; n <= high; n++) {
|
|
||||||
numbers.push(n);
|
|
||||||
}
|
|
||||||
return numbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isHostEvent(jobEvent) {
|
|
||||||
const { event, event_data, host, type } = jobEvent;
|
|
||||||
let isHost;
|
|
||||||
if (typeof host === 'number' || (event_data && event_data.res)) {
|
|
||||||
isHost = true;
|
|
||||||
} else if (
|
|
||||||
type === 'project_update_event' &&
|
|
||||||
event !== 'runner_on_skipped' &&
|
|
||||||
event_data.host
|
|
||||||
) {
|
|
||||||
isHost = true;
|
|
||||||
} else {
|
|
||||||
isHost = false;
|
|
||||||
}
|
|
||||||
return isHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cache = new CellMeasurerCache({
|
const cache = new CellMeasurerCache({
|
||||||
fixedWidth: true,
|
fixedWidth: true,
|
||||||
defaultHeight: 25,
|
defaultHeight: 25,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getEventRequestParams = (job, remoteRowCount, requestRange) => {
|
|
||||||
const [startIndex, stopIndex] = requestRange;
|
|
||||||
if (isJobRunning(job?.status)) {
|
|
||||||
return [
|
|
||||||
{ counter__gte: startIndex, limit: stopIndex - startIndex + 1 },
|
|
||||||
range(startIndex, Math.min(stopIndex, remoteRowCount)),
|
|
||||||
startIndex,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const { page, pageSize, firstIndex } = getRowRangePageSize(
|
|
||||||
startIndex,
|
|
||||||
stopIndex
|
|
||||||
);
|
|
||||||
const loadRange = range(
|
|
||||||
firstIndex,
|
|
||||||
Math.min(firstIndex + pageSize, remoteRowCount)
|
|
||||||
);
|
|
||||||
|
|
||||||
return [{ page, page_size: pageSize }, loadRange, firstIndex];
|
|
||||||
};
|
|
||||||
|
|
||||||
function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const listRef = useRef(null);
|
const listRef = useRef(null);
|
||||||
@ -226,9 +139,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function cleanup() {
|
return function cleanup() {
|
||||||
if (ws) {
|
closeWebSocket();
|
||||||
ws.close();
|
|
||||||
}
|
|
||||||
setIsMonitoringWebsocket(false);
|
setIsMonitoringWebsocket(false);
|
||||||
isMounted.current = false;
|
isMounted.current = false;
|
||||||
};
|
};
|
||||||
@ -312,73 +223,29 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
...parseQueryString(QS_CONFIG, location.search),
|
...parseQueryString(QS_CONFIG, location.search),
|
||||||
});
|
});
|
||||||
|
|
||||||
let countRequest;
|
|
||||||
if (isJobRunning(job?.status)) {
|
|
||||||
// If the job is running, it means we're using limit-offset pagination. Requests
|
|
||||||
// with limit-offset pagination won't return a total event count for performance
|
|
||||||
// reasons. In this situation, we derive the remote row count by using the highest
|
|
||||||
// counter available in the database.
|
|
||||||
countRequest = async () => {
|
|
||||||
const {
|
|
||||||
data: { results: lastEvents = [] },
|
|
||||||
} = await getJobModel(job.type).readEvents(job.id, {
|
|
||||||
order_by: '-counter',
|
|
||||||
limit: 1,
|
|
||||||
});
|
|
||||||
return lastEvents.length >= 1 ? lastEvents[0].counter : 0;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
countRequest = async () => {
|
|
||||||
const {
|
|
||||||
data: { count: eventCount },
|
|
||||||
} = await eventPromise;
|
|
||||||
return eventCount;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [
|
const [
|
||||||
{
|
{
|
||||||
data: { results: fetchedEvents = [] },
|
data: { results: fetchedEvents = [] },
|
||||||
},
|
},
|
||||||
count,
|
count,
|
||||||
] = await Promise.all([eventPromise, countRequest()]);
|
] = await Promise.all([eventPromise, fetchCount(job, eventPromise)]);
|
||||||
|
|
||||||
if (isMounted.current) {
|
if (!isMounted.current) {
|
||||||
let countOffset = 0;
|
return;
|
||||||
if (job?.result_traceback) {
|
|
||||||
const tracebackEvent = {
|
|
||||||
counter: 1,
|
|
||||||
created: null,
|
|
||||||
event: null,
|
|
||||||
type: null,
|
|
||||||
stdout: job?.result_traceback,
|
|
||||||
start_line: 0,
|
|
||||||
};
|
|
||||||
const firstIndex = fetchedEvents.findIndex(
|
|
||||||
(jobEvent) => jobEvent.counter === 1
|
|
||||||
);
|
|
||||||
if (firstIndex && fetchedEvents[firstIndex]?.stdout) {
|
|
||||||
const stdoutLines = fetchedEvents[firstIndex].stdout.split('\r\n');
|
|
||||||
stdoutLines[0] = tracebackEvent.stdout;
|
|
||||||
fetchedEvents[firstIndex].stdout = stdoutLines.join('\r\n');
|
|
||||||
} else {
|
|
||||||
countOffset += 1;
|
|
||||||
fetchedEvents.unshift(tracebackEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newResults = {};
|
|
||||||
let newResultsCssMap = {};
|
|
||||||
fetchedEvents.forEach((jobEvent, index) => {
|
|
||||||
newResults[index] = jobEvent;
|
|
||||||
const { lineCssMap } = getLineTextHtml(jobEvent);
|
|
||||||
newResultsCssMap = { ...newResultsCssMap, ...lineCssMap };
|
|
||||||
});
|
|
||||||
setResults(newResults);
|
|
||||||
setRemoteRowCount(count + countOffset);
|
|
||||||
setCssMap(newResultsCssMap);
|
|
||||||
}
|
}
|
||||||
|
const { events, countOffset } = normalizeEvents(job, fetchedEvents);
|
||||||
|
|
||||||
|
const newResults = {};
|
||||||
|
let newResultsCssMap = {};
|
||||||
|
events.forEach((jobEvent, index) => {
|
||||||
|
newResults[index] = jobEvent;
|
||||||
|
const { lineCssMap } = getLineTextHtml(jobEvent);
|
||||||
|
newResultsCssMap = { ...newResultsCssMap, ...lineCssMap };
|
||||||
|
});
|
||||||
|
setResults(newResults);
|
||||||
|
setRemoteRowCount(count + countOffset);
|
||||||
|
setCssMap(newResultsCssMap);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setContentError(err);
|
setContentError(err);
|
||||||
} finally {
|
} finally {
|
||||||
@ -479,29 +346,31 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
return getJobModel(job.type)
|
return getJobModel(job.type)
|
||||||
.readEvents(job.id, params)
|
.readEvents(job.id, params)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (isMounted.current) {
|
if (!isMounted.current) {
|
||||||
const newResults = {};
|
return;
|
||||||
let newResultsCssMap = {};
|
|
||||||
response.data.results.forEach((jobEvent, index) => {
|
|
||||||
newResults[firstIndex + index] = jobEvent;
|
|
||||||
const { lineCssMap } = getLineTextHtml(jobEvent);
|
|
||||||
newResultsCssMap = { ...newResultsCssMap, ...lineCssMap };
|
|
||||||
});
|
|
||||||
setResults((prevResults) => ({
|
|
||||||
...prevResults,
|
|
||||||
...newResults,
|
|
||||||
}));
|
|
||||||
setCssMap((prevCssMap) => ({
|
|
||||||
...prevCssMap,
|
|
||||||
...newResultsCssMap,
|
|
||||||
}));
|
|
||||||
setCurrentlyLoading((prevCurrentlyLoading) =>
|
|
||||||
prevCurrentlyLoading.filter((n) => !loadRange.includes(n))
|
|
||||||
);
|
|
||||||
loadRange.forEach((n) => {
|
|
||||||
cache.clear(n);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newResults = {};
|
||||||
|
let newResultsCssMap = {};
|
||||||
|
response.data.results.forEach((jobEvent, index) => {
|
||||||
|
newResults[firstIndex + index] = jobEvent;
|
||||||
|
const { lineCssMap } = getLineTextHtml(jobEvent);
|
||||||
|
newResultsCssMap = { ...newResultsCssMap, ...lineCssMap };
|
||||||
|
});
|
||||||
|
setResults((prevResults) => ({
|
||||||
|
...prevResults,
|
||||||
|
...newResults,
|
||||||
|
}));
|
||||||
|
setCssMap((prevCssMap) => ({
|
||||||
|
...prevCssMap,
|
||||||
|
...newResultsCssMap,
|
||||||
|
}));
|
||||||
|
setCurrentlyLoading((prevCurrentlyLoading) =>
|
||||||
|
prevCurrentlyLoading.filter((n) => !loadRange.includes(n))
|
||||||
|
);
|
||||||
|
loadRange.forEach((n) => {
|
||||||
|
cache.clear(n);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
50
awx/ui/src/screens/Job/JobOutput/connectJobSocket.js
Normal file
50
awx/ui/src/screens/Job/JobOutput/connectJobSocket.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
let ws;
|
||||||
|
|
||||||
|
export default function connectJobSocket({ type, id }, onMessage) {
|
||||||
|
ws = new WebSocket(
|
||||||
|
`${window.location.protocol === 'http:' ? 'ws:' : 'wss:'}//${
|
||||||
|
window.location.host
|
||||||
|
}/websocket/`
|
||||||
|
);
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
const xrftoken = `; ${document.cookie}`
|
||||||
|
.split('; csrftoken=')
|
||||||
|
.pop()
|
||||||
|
.split(';')
|
||||||
|
.shift();
|
||||||
|
const eventGroup = `${type}_events`;
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
xrftoken,
|
||||||
|
groups: { jobs: ['summary', 'status_changed'], [eventGroup]: [id] },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
onMessage(JSON.parse(e.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = (e) => {
|
||||||
|
if (e.code !== 1000) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.debug('Socket closed. Reconnecting...', e);
|
||||||
|
setTimeout(() => {
|
||||||
|
connectJobSocket({ type, id }, onMessage);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = (err) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.debug('Socket error: ', err, 'Disconnecting...');
|
||||||
|
ws.close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeWebSocket() {
|
||||||
|
if (ws) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
35
awx/ui/src/screens/Job/JobOutput/getEventRequestParams.js
Normal file
35
awx/ui/src/screens/Job/JobOutput/getEventRequestParams.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { isJobRunning } from 'util/jobs';
|
||||||
|
import getRowRangePageSize from './shared/jobOutputUtils';
|
||||||
|
|
||||||
|
export default function getEventRequestParams(
|
||||||
|
job,
|
||||||
|
remoteRowCount,
|
||||||
|
requestRange
|
||||||
|
) {
|
||||||
|
const [startIndex, stopIndex] = requestRange;
|
||||||
|
if (isJobRunning(job?.status)) {
|
||||||
|
return [
|
||||||
|
{ counter__gte: startIndex, limit: stopIndex - startIndex + 1 },
|
||||||
|
range(startIndex, Math.min(stopIndex, remoteRowCount)),
|
||||||
|
startIndex,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const { page, pageSize, firstIndex } = getRowRangePageSize(
|
||||||
|
startIndex,
|
||||||
|
stopIndex
|
||||||
|
);
|
||||||
|
const loadRange = range(
|
||||||
|
firstIndex,
|
||||||
|
Math.min(firstIndex + pageSize, remoteRowCount)
|
||||||
|
);
|
||||||
|
|
||||||
|
return [{ page, page_size: pageSize }, loadRange, firstIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
function range(low, high) {
|
||||||
|
const numbers = [];
|
||||||
|
for (let n = low; n <= high; n++) {
|
||||||
|
numbers.push(n);
|
||||||
|
}
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
16
awx/ui/src/screens/Job/JobOutput/isHostEvent.js
Normal file
16
awx/ui/src/screens/Job/JobOutput/isHostEvent.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export default function isHostEvent(jobEvent) {
|
||||||
|
const { event, event_data, host, type } = jobEvent;
|
||||||
|
let isHost;
|
||||||
|
if (typeof host === 'number' || (event_data && event_data.res)) {
|
||||||
|
isHost = true;
|
||||||
|
} else if (
|
||||||
|
type === 'project_update_event' &&
|
||||||
|
event !== 'runner_on_skipped' &&
|
||||||
|
event_data.host
|
||||||
|
) {
|
||||||
|
isHost = true;
|
||||||
|
} else {
|
||||||
|
isHost = false;
|
||||||
|
}
|
||||||
|
return isHost;
|
||||||
|
}
|
||||||
46
awx/ui/src/screens/Job/JobOutput/loadJobEvents.js
Normal file
46
awx/ui/src/screens/Job/JobOutput/loadJobEvents.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { getJobModel, isJobRunning } from 'util/jobs';
|
||||||
|
|
||||||
|
export async function fetchCount(job, eventPromise) {
|
||||||
|
if (isJobRunning(job?.status)) {
|
||||||
|
const {
|
||||||
|
data: { results: lastEvents = [] },
|
||||||
|
} = await getJobModel(job.type).readEvents(job.id, {
|
||||||
|
order_by: '-counter',
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
return lastEvents.length >= 1 ? lastEvents[0].counter : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { count: eventCount },
|
||||||
|
} = await eventPromise;
|
||||||
|
return eventCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeEvents(job, events) {
|
||||||
|
let countOffset = 0;
|
||||||
|
if (job?.result_traceback) {
|
||||||
|
const tracebackEvent = {
|
||||||
|
counter: 1,
|
||||||
|
created: null,
|
||||||
|
event: null,
|
||||||
|
type: null,
|
||||||
|
stdout: job?.result_traceback,
|
||||||
|
start_line: 0,
|
||||||
|
};
|
||||||
|
const firstIndex = events.findIndex((jobEvent) => jobEvent.counter === 1);
|
||||||
|
if (firstIndex && events[firstIndex]?.stdout) {
|
||||||
|
const stdoutLines = events[firstIndex].stdout.split('\r\n');
|
||||||
|
stdoutLines[0] = tracebackEvent.stdout;
|
||||||
|
events[firstIndex].stdout = stdoutLines.join('\r\n');
|
||||||
|
} else {
|
||||||
|
countOffset += 1;
|
||||||
|
events.unshift(tracebackEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
countOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user