mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
Use new children-summary endpoint data to traverse job event tree (#11944)
* use new children-summary endpoint data to traverse job event tree * update job output tests for new children summary data * force flat mode if event child summary fails to load * update childrenSummary data for endpoint changes * don't add jobs to job tree until children summary loaded * force job output into flat mode if job processing not complete
This commit is contained in:
parent
bea924ddc6
commit
7cbb783b2c
@ -19,6 +19,10 @@ class Jobs extends RunnableMixin(Base) {
|
||||
readDetail(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/`);
|
||||
}
|
||||
|
||||
readChildrenSummary(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/job_events/children_summary/`);
|
||||
}
|
||||
}
|
||||
|
||||
export default Jobs;
|
||||
|
||||
@ -18,7 +18,7 @@ import ContentError from 'components/ContentError';
|
||||
import ContentLoading from 'components/ContentLoading';
|
||||
import ErrorDetail from 'components/ErrorDetail';
|
||||
import StatusLabel from 'components/StatusLabel';
|
||||
import { JobEventsAPI } from 'api';
|
||||
import { JobsAPI } from 'api';
|
||||
|
||||
import { getJobModel, isJobRunning } from 'util/jobs';
|
||||
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
||||
@ -99,8 +99,6 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||
const scrollHeight = useRef(0);
|
||||
const history = useHistory();
|
||||
const eventByUuidRequests = useRef([]);
|
||||
const siblingRequests = useRef([]);
|
||||
const numEventsRequests = useRef([]);
|
||||
|
||||
const fetchEventByUuid = async (uuid) => {
|
||||
let promise = eventByUuidRequests.current[uuid];
|
||||
@ -113,60 +111,15 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||
return data.results[0] || null;
|
||||
};
|
||||
|
||||
const fetchNextSibling = async (parentEventId, counter) => {
|
||||
const key = `${parentEventId}-${counter}`;
|
||||
let promise = siblingRequests.current[key];
|
||||
if (!promise) {
|
||||
promise = JobEventsAPI.readChildren(parentEventId, {
|
||||
page_size: 1,
|
||||
order_by: 'counter',
|
||||
counter__gt: counter,
|
||||
});
|
||||
siblingRequests.current[key] = promise;
|
||||
}
|
||||
|
||||
const { data } = await promise;
|
||||
siblingRequests.current[key] = null;
|
||||
return data.results[0] || null;
|
||||
};
|
||||
|
||||
const fetchNextRootNode = async (counter) => {
|
||||
const { data } = await getJobModel(job.type).readEvents(job.id, {
|
||||
page_size: 1,
|
||||
order_by: 'counter',
|
||||
counter__gt: counter,
|
||||
parent_uuid: '',
|
||||
});
|
||||
return data.results[0] || null;
|
||||
};
|
||||
|
||||
const fetchNumEvents = async (startCounter, endCounter) => {
|
||||
if (endCounter <= startCounter + 1) {
|
||||
return 0;
|
||||
}
|
||||
const key = `${startCounter}-${endCounter}`;
|
||||
let promise = numEventsRequests.current[key];
|
||||
if (!promise) {
|
||||
const params = {
|
||||
page_size: 1,
|
||||
order_by: 'counter',
|
||||
counter__gt: startCounter,
|
||||
};
|
||||
if (endCounter) {
|
||||
params.counter__lt = endCounter;
|
||||
}
|
||||
promise = getJobModel(job.type).readEvents(job.id, params);
|
||||
numEventsRequests.current[key] = promise;
|
||||
}
|
||||
|
||||
const { data } = await promise;
|
||||
numEventsRequests.current[key] = null;
|
||||
return data.count || 0;
|
||||
};
|
||||
const fetchChildrenSummary = () => JobsAPI.readChildrenSummary(job.id);
|
||||
|
||||
const [jobStatus, setJobStatus] = useState(job.status ?? 'waiting');
|
||||
const [forceFlatMode, setForceFlatMode] = useState(false);
|
||||
const isFlatMode = isJobRunning(jobStatus) || location.search.length > 1;
|
||||
|
||||
const [isTreeReady, setIsTreeReady] = useState(false);
|
||||
const [onReadyEvents, setOnReadyEvents] = useState([]);
|
||||
|
||||
const {
|
||||
addEvents,
|
||||
toggleNodeIsCollapsed,
|
||||
@ -181,11 +134,12 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||
} = useJobEvents(
|
||||
{
|
||||
fetchEventByUuid,
|
||||
fetchNextSibling,
|
||||
fetchNextRootNode,
|
||||
fetchNumEvents,
|
||||
fetchChildrenSummary,
|
||||
setForceFlatMode,
|
||||
setJobTreeReady: () => setIsTreeReady(true),
|
||||
},
|
||||
isFlatMode
|
||||
job.id,
|
||||
isFlatMode || forceFlatMode
|
||||
);
|
||||
const [wsEvents, setWsEvents] = useState([]);
|
||||
const [cssMap, setCssMap] = useState({});
|
||||
@ -203,6 +157,14 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||
const [isMonitoringWebsocket, setIsMonitoringWebsocket] = useState(false);
|
||||
const [lastScrollPosition, setLastScrollPosition] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTreeReady || !onReadyEvents.length) {
|
||||
return;
|
||||
}
|
||||
addEvents(onReadyEvents);
|
||||
setOnReadyEvents([]);
|
||||
}, [isTreeReady, onReadyEvents]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const totalNonCollapsedRows = Math.max(
|
||||
remoteRowCount - getNumCollapsedEvents(),
|
||||
0
|
||||
@ -216,13 +178,9 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const pendingRequests = [
|
||||
...Object.values(eventByUuidRequests.current || {}),
|
||||
...Object.values(siblingRequests.current || {}),
|
||||
...Object.values(numEventsRequests.current || {}),
|
||||
];
|
||||
const pendingRequests = Object.values(eventByUuidRequests.current || {});
|
||||
setHasContentLoading(true); // prevents "no content found" screen from flashing
|
||||
Promise.all(pendingRequests).then(() => {
|
||||
Promise.allSettled(pendingRequests).then(() => {
|
||||
setRemoteRowCount(0);
|
||||
clearLoadedEvents();
|
||||
loadJobEvents();
|
||||
@ -412,7 +370,11 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||
...newCssMap,
|
||||
}));
|
||||
const lastCounter = events[events.length - 1]?.counter || 50;
|
||||
addEvents(events);
|
||||
if (isTreeReady) {
|
||||
addEvents(events);
|
||||
} else {
|
||||
setOnReadyEvents((prev) => prev.concat(events));
|
||||
}
|
||||
setHighestLoadedCounter(lastCounter);
|
||||
setRemoteRowCount(count + countOffset);
|
||||
} catch (err) {
|
||||
@ -707,7 +669,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||
onScrollNext={handleScrollNext}
|
||||
onScrollPrevious={handleScrollPrevious}
|
||||
toggleExpandCollapseAll={handleExpandCollapseAll}
|
||||
isFlatMode={isFlatMode}
|
||||
isFlatMode={isFlatMode || forceFlatMode}
|
||||
isTemplateJob={job.type === 'job'}
|
||||
isAllCollapsed={isAllCollapsed}
|
||||
/>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable max-len */
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { JobsAPI, JobEventsAPI } from 'api';
|
||||
@ -26,14 +25,9 @@ const applyJobEventMock = (mockJobEvents) => {
|
||||
};
|
||||
};
|
||||
JobsAPI.readEvents = jest.fn().mockImplementation(mockReadEvents);
|
||||
JobEventsAPI.readChildren = jest.fn().mockResolvedValue({
|
||||
JobsAPI.readChildrenSummary = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
counter: 20,
|
||||
uuid: 'abc-020',
|
||||
},
|
||||
],
|
||||
1: [0, 100],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -4,7 +4,6 @@ export default styled.div`
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
background-color: white;
|
||||
cursor: ${(props) => (props.isClickable ? 'pointer' : 'default')};
|
||||
}
|
||||
|
||||
|
||||
@ -11,16 +11,21 @@ const initialState = {
|
||||
// events with parent events that aren't yet loaded.
|
||||
// arrays indexed by parent uuid
|
||||
eventsWithoutParents: {},
|
||||
// object in the form { counter: {rowNumber: n, numChildren: m}} for parent nodes
|
||||
childrenSummary: {},
|
||||
// parent_uuid's for "meta" events that need to be injected into the tree to
|
||||
// maintain tree integrity
|
||||
metaEventParentUuid: {},
|
||||
isAllCollapsed: false,
|
||||
};
|
||||
export const ADD_EVENTS = 'ADD_EVENTS';
|
||||
export const TOGGLE_NODE_COLLAPSED = 'TOGGLE_NODE_COLLAPSED';
|
||||
export const SET_EVENT_NUM_CHILDREN = 'SET_EVENT_NUM_CHILDREN';
|
||||
export const CLEAR_EVENTS = 'CLEAR_EVENTS';
|
||||
export const REBUILD_TREE = 'REBUILD_TREE';
|
||||
export const TOGGLE_COLLAPSE_ALL = 'TOGGLE_COLLAPSE_ALL';
|
||||
export const SET_CHILDREN_SUMMARY = 'SET_CHILDREN_SUMMARY';
|
||||
|
||||
export default function useJobEvents(callbacks, isFlatMode) {
|
||||
export default function useJobEvents(callbacks, jobId, isFlatMode) {
|
||||
const [actionQueue, setActionQueue] = useState([]);
|
||||
const enqueueAction = (action) => {
|
||||
setActionQueue((queue) => queue.concat(action));
|
||||
@ -42,6 +47,31 @@ export default function useJobEvents(callbacks, isFlatMode) {
|
||||
});
|
||||
}, [actionQueue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFlatMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
callbacks
|
||||
.fetchChildrenSummary()
|
||||
.then((result) => {
|
||||
if (result.data.event_processing_finished === false) {
|
||||
callbacks.setForceFlatMode(true);
|
||||
callbacks.setJobTreeReady();
|
||||
return;
|
||||
}
|
||||
enqueueAction({
|
||||
type: SET_CHILDREN_SUMMARY,
|
||||
childrenSummary: result.data.children_summary,
|
||||
metaEventParentUuid: result.data.meta_event_nested_uuid,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
callbacks.setForceFlatMode(true);
|
||||
callbacks.setJobTreeReady();
|
||||
});
|
||||
}, [jobId, isFlatMode]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return {
|
||||
addEvents: (events) => dispatch({ type: ADD_EVENTS, events }),
|
||||
getNodeByUuid: (uuid) => getNodeByUuid(state, uuid),
|
||||
@ -53,10 +83,14 @@ export default function useJobEvents(callbacks, isFlatMode) {
|
||||
getNodeForRow: (rowIndex) => getNodeForRow(state, rowIndex),
|
||||
getTotalNumChildren: (uuid) => {
|
||||
const node = getNodeByUuid(state, uuid);
|
||||
return getTotalNumChildren(node);
|
||||
return getTotalNumChildren(node, state.childrenSummary);
|
||||
},
|
||||
getNumCollapsedEvents: () =>
|
||||
state.tree.reduce((sum, node) => sum + getNumCollapsedChildren(node), 0),
|
||||
state.tree.reduce(
|
||||
(sum, node) =>
|
||||
sum + getNumCollapsedChildren(node, state.childrenSummary),
|
||||
0
|
||||
),
|
||||
getCounterForRow: (rowIndex) => getCounterForRow(state, rowIndex),
|
||||
getEvent: (eventIndex) => getEvent(state, eventIndex),
|
||||
clearLoadedEvents: () => dispatch({ type: CLEAR_EVENTS }),
|
||||
@ -74,12 +108,17 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
|
||||
return toggleCollapseAll(state, action.isCollapsed);
|
||||
case TOGGLE_NODE_COLLAPSED:
|
||||
return toggleNodeIsCollapsed(state, action.uuid);
|
||||
case SET_EVENT_NUM_CHILDREN:
|
||||
return setEventNumChildren(state, action.uuid, action.numChildren);
|
||||
case CLEAR_EVENTS:
|
||||
return initialState;
|
||||
case REBUILD_TREE:
|
||||
return rebuildTree(state);
|
||||
case SET_CHILDREN_SUMMARY:
|
||||
callbacks.setJobTreeReady();
|
||||
return {
|
||||
...state,
|
||||
childrenSummary: action.childrenSummary || {},
|
||||
metaEventParentUuid: action.metaEventParentUuid || {},
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unrecognized action: ${action.type}`);
|
||||
}
|
||||
@ -100,6 +139,9 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
|
||||
throw new Error('Cannot add event; missing rowNumber');
|
||||
}
|
||||
const eventIndex = event.counter;
|
||||
if (!event.parent_uuid && state.metaEventParentUuid[eventIndex]) {
|
||||
event.parent_uuid = state.metaEventParentUuid[eventIndex];
|
||||
}
|
||||
if (state.events[eventIndex]) {
|
||||
state.events[eventIndex] = event;
|
||||
state = _gatherEventsForNewParent(state, event.uuid);
|
||||
@ -113,22 +155,21 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
|
||||
let isParentFound;
|
||||
[state, isParentFound] = _addNestedLevelEvent(state, event);
|
||||
if (!isParentFound) {
|
||||
parentsToFetch[event.parent_uuid] = {
|
||||
childCounter: event.counter,
|
||||
childRowNumber: event.rowNumber,
|
||||
};
|
||||
parentsToFetch[event.parent_uuid] = true;
|
||||
state = _addEventWithoutParent(state, event);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(parentsToFetch).forEach(async (uuid) => {
|
||||
const { childCounter, childRowNumber } = parentsToFetch[uuid];
|
||||
const parent = await callbacks.fetchEventByUuid(uuid);
|
||||
const numPrevSiblings = await callbacks.fetchNumEvents(
|
||||
parent.counter,
|
||||
childCounter
|
||||
);
|
||||
parent.rowNumber = childRowNumber - numPrevSiblings - 1;
|
||||
|
||||
if (!state.childrenSummary || !state.childrenSummary[parent.counter]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('No row number found for ', parent.counter);
|
||||
return;
|
||||
}
|
||||
parent.rowNumber = state.childrenSummary[parent.counter].rowNumber;
|
||||
|
||||
enqueueAction({
|
||||
type: ADD_EVENTS,
|
||||
events: [parent],
|
||||
@ -180,7 +221,6 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
|
||||
const index = parent.children.findIndex(
|
||||
(node) => node.eventIndex >= eventIndex
|
||||
);
|
||||
const length = parent.children.length + 1;
|
||||
if (index === -1) {
|
||||
state = updateNodeByUuid(state, event.parent_uuid, (node) => {
|
||||
node.children.push(newNode);
|
||||
@ -206,9 +246,6 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
|
||||
},
|
||||
event.uuid
|
||||
);
|
||||
if (length === 1) {
|
||||
_fetchNumChildren(state, parent);
|
||||
}
|
||||
|
||||
return [state, true];
|
||||
}
|
||||
@ -231,45 +268,6 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
|
||||
};
|
||||
}
|
||||
|
||||
async function _fetchNumChildren(state, node) {
|
||||
const event = state.events[node.eventIndex];
|
||||
if (!event) {
|
||||
throw new Error(
|
||||
`Cannot fetch numChildren; event ${node.eventIndex} not found`
|
||||
);
|
||||
}
|
||||
const sibling = await _getNextSibling(state, event);
|
||||
const numChildren = await callbacks.fetchNumEvents(
|
||||
event.counter,
|
||||
sibling?.counter
|
||||
);
|
||||
enqueueAction({
|
||||
type: SET_EVENT_NUM_CHILDREN,
|
||||
uuid: event.uuid,
|
||||
numChildren,
|
||||
});
|
||||
if (sibling) {
|
||||
sibling.rowNumber = event.rowNumber + numChildren + 1;
|
||||
enqueueAction({
|
||||
type: ADD_EVENTS,
|
||||
events: [sibling],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function _getNextSibling(state, event) {
|
||||
if (!event.parent_uuid) {
|
||||
return callbacks.fetchNextRootNode(event.counter);
|
||||
}
|
||||
const parentNode = getNodeByUuid(state, event.parent_uuid);
|
||||
const parent = state.events[parentNode.eventIndex];
|
||||
const sibling = await callbacks.fetchNextSibling(parent.id, event.counter);
|
||||
if (!sibling) {
|
||||
return _getNextSibling(state, parent);
|
||||
}
|
||||
return sibling;
|
||||
}
|
||||
|
||||
function _gatherEventsForNewParent(state, parentUuid) {
|
||||
if (!state.eventsWithoutParents[parentUuid]) {
|
||||
return state;
|
||||
@ -303,8 +301,13 @@ function getEventForRow(state, rowIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getNodeForRow(state, rowToFind) {
|
||||
const { node } = _getNodeForRow(state, rowToFind, state.tree);
|
||||
function getNodeForRow(state, rowToFind, childrenSummary) {
|
||||
const { node } = _getNodeForRow(
|
||||
state,
|
||||
rowToFind,
|
||||
state.tree,
|
||||
childrenSummary
|
||||
);
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -329,8 +332,14 @@ function _getNodeForRow(state, rowToFind, nodes) {
|
||||
if (event.rowNumber === rowToFind) {
|
||||
return { node };
|
||||
}
|
||||
const totalNodeDescendants = getTotalNumChildren(node);
|
||||
const numCollapsedChildren = getNumCollapsedChildren(node);
|
||||
const totalNodeDescendants = getTotalNumChildren(
|
||||
node,
|
||||
state.childrenSummary
|
||||
);
|
||||
const numCollapsedChildren = getNumCollapsedChildren(
|
||||
node,
|
||||
state.childrenSummary
|
||||
);
|
||||
const nodeChildren = totalNodeDescendants - numCollapsedChildren;
|
||||
if (event.rowNumber + nodeChildren >= rowToFind) {
|
||||
// requested row is in children/descendants
|
||||
@ -370,8 +379,8 @@ function _getNodeForRow(state, rowToFind, nodes) {
|
||||
|
||||
function _getNodeInChildren(state, node, rowToFind) {
|
||||
const event = state.events[node.eventIndex];
|
||||
const firstChild = state.events[node.children[0].eventIndex];
|
||||
if (rowToFind < firstChild.rowNumber) {
|
||||
const firstChild = state.events[node.children[0]?.eventIndex];
|
||||
if (!firstChild || rowToFind < firstChild.rowNumber) {
|
||||
const rowDiff = rowToFind - event.rowNumber;
|
||||
return {
|
||||
node: null,
|
||||
@ -391,25 +400,25 @@ function _getLastDescendantNode(nodes) {
|
||||
return lastDescendant;
|
||||
}
|
||||
|
||||
function getTotalNumChildren(node) {
|
||||
if (typeof node.numChildren !== 'undefined') {
|
||||
return node.numChildren;
|
||||
function getTotalNumChildren(node, childrenSummary) {
|
||||
if (childrenSummary[node.eventIndex]) {
|
||||
return childrenSummary[node.eventIndex].numChildren;
|
||||
}
|
||||
|
||||
let estimatedNumChildren = node.children.length;
|
||||
node.children.forEach((child) => {
|
||||
estimatedNumChildren += getTotalNumChildren(child);
|
||||
estimatedNumChildren += getTotalNumChildren(child, childrenSummary);
|
||||
});
|
||||
return estimatedNumChildren;
|
||||
}
|
||||
|
||||
function getNumCollapsedChildren(node) {
|
||||
function getNumCollapsedChildren(node, childrenSummary) {
|
||||
if (node.isCollapsed) {
|
||||
return getTotalNumChildren(node);
|
||||
return getTotalNumChildren(node, childrenSummary);
|
||||
}
|
||||
let sum = 0;
|
||||
node.children.forEach((child) => {
|
||||
sum += getNumCollapsedChildren(child);
|
||||
sum += getNumCollapsedChildren(child, childrenSummary);
|
||||
});
|
||||
return sum;
|
||||
}
|
||||
@ -514,16 +523,6 @@ function _getNodeByIndex(arr, index) {
|
||||
return _getNodeByIndex(arr[i - 1].children, index);
|
||||
}
|
||||
|
||||
function setEventNumChildren(state, uuid, numChildren) {
|
||||
if (!state.uuidMap[uuid]) {
|
||||
return state;
|
||||
}
|
||||
return updateNodeByUuid(state, uuid, (node) => ({
|
||||
...node,
|
||||
numChildren,
|
||||
}));
|
||||
}
|
||||
|
||||
function getEvent(state, eventIndex) {
|
||||
const event = state.events[eventIndex];
|
||||
if (event) {
|
||||
|
||||
@ -8,24 +8,22 @@ import useJobEvents, {
|
||||
SET_EVENT_NUM_CHILDREN,
|
||||
} from './useJobEvents';
|
||||
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
function Child() {
|
||||
return <div />;
|
||||
}
|
||||
function HookTest({
|
||||
fetchEventByUuid = () => {},
|
||||
fetchNextSibling = () => {},
|
||||
fetchNextRootNode = () => {},
|
||||
fetchNumEvents = () => {},
|
||||
fetchChildrenSummary = () => {},
|
||||
setForceFlatMode = () => {},
|
||||
setJobTreeReady = () => {},
|
||||
isFlatMode = false,
|
||||
}) {
|
||||
const hookFuncs = useJobEvents(
|
||||
{
|
||||
fetchEventByUuid,
|
||||
fetchNextSibling,
|
||||
fetchNextRootNode,
|
||||
fetchNumEvents,
|
||||
fetchChildrenSummary,
|
||||
setForceFlatMode,
|
||||
setJobTreeReady,
|
||||
},
|
||||
isFlatMode
|
||||
);
|
||||
@ -153,19 +151,19 @@ describe('useJobEvents', () => {
|
||||
beforeEach(() => {
|
||||
callbacks = {
|
||||
fetchEventByUuid: jest.fn(),
|
||||
fetchNextSibling: jest.fn(),
|
||||
fetchNextRootNode: jest.fn(),
|
||||
fetchNumEvents: jest.fn(),
|
||||
fetchChildrenSummary: jest.fn(),
|
||||
setForceFlatMode: jest.fn(),
|
||||
setJobTreeReady: jest.fn(),
|
||||
};
|
||||
enqueueAction = jest.fn();
|
||||
callbacks.fetchNextSibling.mockResolvedValue(eventsList[9]);
|
||||
callbacks.fetchNextRootNode.mockResolvedValue(eventsList[9]);
|
||||
reducer = jobEventsReducer(callbacks, false, enqueueAction);
|
||||
emptyState = {
|
||||
tree: [],
|
||||
events: {},
|
||||
uuidMap: {},
|
||||
eventsWithoutParents: {},
|
||||
childrenSummary: {},
|
||||
metaEventParentUuid: {},
|
||||
eventGaps: [],
|
||||
isAllCollapsed: false,
|
||||
};
|
||||
@ -380,10 +378,18 @@ describe('useJobEvents', () => {
|
||||
callbacks.fetchEventByUuid.mockResolvedValue({
|
||||
counter: 10,
|
||||
});
|
||||
const state = reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
});
|
||||
const state = reducer(
|
||||
{
|
||||
...emptyState,
|
||||
childrenSummary: {
|
||||
10: [9, 2],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
}
|
||||
);
|
||||
|
||||
const newEvents = [
|
||||
{
|
||||
@ -404,10 +410,18 @@ describe('useJobEvents', () => {
|
||||
callbacks.fetchEventByUuid.mockResolvedValue({
|
||||
counter: 10,
|
||||
});
|
||||
const state = reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
});
|
||||
const state = reducer(
|
||||
{
|
||||
...emptyState,
|
||||
childrenSummary: {
|
||||
10: [9, 2],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
}
|
||||
);
|
||||
|
||||
const newEvents = [
|
||||
{
|
||||
@ -437,10 +451,18 @@ describe('useJobEvents', () => {
|
||||
callbacks.fetchEventByUuid.mockResolvedValue({
|
||||
counter: 10,
|
||||
});
|
||||
const state = reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
});
|
||||
const state = reducer(
|
||||
{
|
||||
...emptyState,
|
||||
childrenSummary: {
|
||||
10: [9, 1],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
}
|
||||
);
|
||||
|
||||
const newEvents = [
|
||||
{
|
||||
@ -471,10 +493,18 @@ describe('useJobEvents', () => {
|
||||
callbacks.fetchEventByUuid.mockResolvedValue({
|
||||
counter: 10,
|
||||
});
|
||||
const state = reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
});
|
||||
const state = reducer(
|
||||
{
|
||||
...emptyState,
|
||||
childrenSummary: {
|
||||
10: [9, 2],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
}
|
||||
);
|
||||
|
||||
const newEvents = [
|
||||
{
|
||||
@ -561,10 +591,19 @@ describe('useJobEvents', () => {
|
||||
event_level: 2,
|
||||
parent_uuid: 'abc-002',
|
||||
};
|
||||
const state = reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: [event3],
|
||||
});
|
||||
const state = reducer(
|
||||
{
|
||||
...emptyState,
|
||||
childrenSummary: {
|
||||
1: [0, 3],
|
||||
2: [1, 2],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ADD_EVENTS,
|
||||
events: [event3],
|
||||
}
|
||||
);
|
||||
expect(callbacks.fetchEventByUuid).toHaveBeenCalledWith('abc-002');
|
||||
|
||||
const event2 = {
|
||||
@ -741,152 +780,49 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchNumChildren', () => {
|
||||
test('should find child count for root node', async () => {
|
||||
callbacks.fetchNextRootNode.mockResolvedValue({
|
||||
id: 121,
|
||||
counter: 21,
|
||||
rowNumber: 20,
|
||||
uuid: 'abc-021',
|
||||
event_level: 0,
|
||||
parent_uuid: '',
|
||||
});
|
||||
callbacks.fetchNumEvents.mockResolvedValue(19);
|
||||
reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: [eventsList[0], eventsList[1]],
|
||||
});
|
||||
|
||||
expect(callbacks.fetchNextSibling).toHaveBeenCalledTimes(0);
|
||||
expect(callbacks.fetchNextRootNode).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNextRootNode).toHaveBeenCalledWith(1);
|
||||
await sleep(0);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledWith(1, 21);
|
||||
expect(enqueueAction).toHaveBeenCalledWith({
|
||||
type: SET_EVENT_NUM_CHILDREN,
|
||||
uuid: 'abc-001',
|
||||
numChildren: 19,
|
||||
});
|
||||
});
|
||||
|
||||
test('should find child count for last root node', async () => {
|
||||
callbacks.fetchNextRootNode.mockResolvedValue(null);
|
||||
callbacks.fetchNumEvents.mockResolvedValue(19);
|
||||
reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: [eventsList[0], eventsList[1]],
|
||||
});
|
||||
|
||||
expect(callbacks.fetchNextSibling).toHaveBeenCalledTimes(0);
|
||||
expect(callbacks.fetchNextRootNode).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNextRootNode).toHaveBeenCalledWith(1);
|
||||
await sleep(0);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledWith(1, undefined);
|
||||
expect(enqueueAction).toHaveBeenCalledWith({
|
||||
type: SET_EVENT_NUM_CHILDREN,
|
||||
uuid: 'abc-001',
|
||||
numChildren: 19,
|
||||
});
|
||||
});
|
||||
|
||||
test('should find child count for nested node', async () => {
|
||||
const state = {
|
||||
events: {
|
||||
1: eventsList[0],
|
||||
2: eventsList[1],
|
||||
test('should nest "meta" event based on given parent uuid', () => {
|
||||
const state = reducer(
|
||||
{
|
||||
...emptyState,
|
||||
childrenSummary: {
|
||||
2: { rowNumber: 1, numChildren: 3 },
|
||||
},
|
||||
tree: [
|
||||
metaEventParentUuid: {
|
||||
4: 'abc-002',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ADD_EVENTS,
|
||||
events: [...eventsList.slice(0, 3)],
|
||||
}
|
||||
);
|
||||
const state2 = reducer(state, {
|
||||
type: ADD_EVENTS,
|
||||
events: [
|
||||
{
|
||||
counter: 4,
|
||||
rowNumber: 3,
|
||||
parent_uuid: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(state2.tree).toEqual([
|
||||
{
|
||||
eventIndex: 1,
|
||||
isCollapsed: false,
|
||||
children: [
|
||||
{
|
||||
children: [{ children: [], eventIndex: 2, isCollapsed: false }],
|
||||
eventIndex: 1,
|
||||
eventIndex: 2,
|
||||
isCollapsed: false,
|
||||
children: [
|
||||
{ eventIndex: 3, isCollapsed: false, children: [] },
|
||||
{ eventIndex: 4, isCollapsed: false, children: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
uuidMap: {
|
||||
'abc-001': 1,
|
||||
'abc-002': 2,
|
||||
},
|
||||
eventsWithoutParents: {},
|
||||
};
|
||||
|
||||
callbacks.fetchNextSibling.mockResolvedValue({
|
||||
id: 20,
|
||||
counter: 20,
|
||||
rowNumber: 19,
|
||||
uuid: 'abc-020',
|
||||
event_level: 1,
|
||||
parent_uuid: 'abc-001',
|
||||
});
|
||||
callbacks.fetchNumEvents.mockResolvedValue(18);
|
||||
reducer(state, {
|
||||
type: ADD_EVENTS,
|
||||
events: [eventsList[2]],
|
||||
});
|
||||
|
||||
expect(callbacks.fetchNextSibling).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNextSibling).toHaveBeenCalledWith(101, 2);
|
||||
await sleep(0);
|
||||
expect(callbacks.fetchNextRootNode).toHaveBeenCalledTimes(0);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledWith(2, 20);
|
||||
expect(enqueueAction).toHaveBeenCalledWith({
|
||||
type: SET_EVENT_NUM_CHILDREN,
|
||||
uuid: 'abc-002',
|
||||
numChildren: 18,
|
||||
});
|
||||
});
|
||||
|
||||
test('should find child count for nested node, last sibling', async () => {
|
||||
const state = {
|
||||
events: {
|
||||
1: eventsList[0],
|
||||
2: eventsList[1],
|
||||
},
|
||||
tree: [
|
||||
{
|
||||
children: [{ children: [], eventIndex: 2, isCollapsed: false }],
|
||||
eventIndex: 1,
|
||||
isCollapsed: false,
|
||||
},
|
||||
],
|
||||
uuidMap: {
|
||||
'abc-001': 1,
|
||||
'abc-002': 2,
|
||||
},
|
||||
eventsWithoutParents: {},
|
||||
};
|
||||
|
||||
callbacks.fetchNextSibling.mockResolvedValue(null);
|
||||
callbacks.fetchNextRootNode.mockResolvedValue({
|
||||
id: 121,
|
||||
counter: 21,
|
||||
rowNumber: 20,
|
||||
uuid: 'abc-021',
|
||||
event_level: 0,
|
||||
parent_uuid: '',
|
||||
});
|
||||
callbacks.fetchNumEvents.mockResolvedValue(19);
|
||||
reducer(state, {
|
||||
type: ADD_EVENTS,
|
||||
events: [eventsList[2]],
|
||||
});
|
||||
|
||||
expect(callbacks.fetchNextSibling).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNextSibling).toHaveBeenCalledWith(101, 2);
|
||||
await sleep(0);
|
||||
expect(callbacks.fetchNextRootNode).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNextRootNode).toHaveBeenCalledWith(1);
|
||||
await sleep(0);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.fetchNumEvents).toHaveBeenCalledWith(2, 21);
|
||||
expect(enqueueAction).toHaveBeenCalledWith({
|
||||
type: SET_EVENT_NUM_CHILDREN,
|
||||
uuid: 'abc-002',
|
||||
numChildren: 19,
|
||||
});
|
||||
});
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -968,40 +904,6 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEventNumChildren', () => {
|
||||
test('should set number of children on root node', () => {
|
||||
const state = reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
});
|
||||
expect(state.tree[0].numChildren).toEqual(undefined);
|
||||
|
||||
const { tree } = reducer(state, {
|
||||
type: SET_EVENT_NUM_CHILDREN,
|
||||
uuid: 'abc-001',
|
||||
numChildren: 8,
|
||||
});
|
||||
|
||||
expect(tree[0].numChildren).toEqual(8);
|
||||
});
|
||||
|
||||
test('should set number of children on nested node', () => {
|
||||
const state = reducer(emptyState, {
|
||||
type: ADD_EVENTS,
|
||||
events: eventsList,
|
||||
});
|
||||
expect(state.tree[0].numChildren).toEqual(undefined);
|
||||
|
||||
const { tree } = reducer(state, {
|
||||
type: SET_EVENT_NUM_CHILDREN,
|
||||
uuid: 'abc-006',
|
||||
numChildren: 3,
|
||||
});
|
||||
|
||||
expect(tree[0].children[1].numChildren).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNodeForRow', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
@ -1266,16 +1168,19 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
|
||||
test('should get node after gap in loaded children', async () => {
|
||||
const fetchNumEvents = jest.fn();
|
||||
fetchNumEvents.mockImplementation((index) => {
|
||||
const counts = {
|
||||
1: 52,
|
||||
2: 3,
|
||||
6: 47,
|
||||
};
|
||||
return Promise.resolve(counts[index]);
|
||||
const fetchChildrenSummary = jest.fn();
|
||||
fetchChildrenSummary.mockResolvedValue({
|
||||
data: {
|
||||
children_summary: {
|
||||
1: { rowNumber: 0, numChildren: 52 },
|
||||
2: { rowNumber: 1, numChildren: 3 },
|
||||
6: { rowNumber: 5, numChildren: 47 },
|
||||
},
|
||||
meta_event_nested_uuid: {},
|
||||
},
|
||||
});
|
||||
wrapper = mount(<HookTest fetchNumEvents={fetchNumEvents} />);
|
||||
|
||||
wrapper = mount(<HookTest fetchChildrenSummary={fetchChildrenSummary} />);
|
||||
const laterEvents = [
|
||||
{
|
||||
id: 151,
|
||||
@ -1424,13 +1329,12 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
|
||||
test('should return estimated counter when node is non-loaded child', async () => {
|
||||
callbacks.fetchNumEvents.mockImplementation((counter) => {
|
||||
const children = {
|
||||
1: 28,
|
||||
2: 3,
|
||||
6: 23,
|
||||
};
|
||||
return children[counter];
|
||||
callbacks.fetchChildrenSummary.mockResolvedValue({
|
||||
data: {
|
||||
1: { rowNumber: 0, numChildren: 28 },
|
||||
2: { rowNumber: 1, numChildren: 3 },
|
||||
6: { rowNumber: 5, numChidren: 23 },
|
||||
},
|
||||
});
|
||||
const wrapper = mount(<HookTest {...callbacks} />);
|
||||
wrapper.update();
|
||||
@ -1463,13 +1367,15 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
|
||||
test('should estimate counter after skipping collapsed subtree', async () => {
|
||||
callbacks.fetchNumEvents.mockImplementation((counter) => {
|
||||
const children = {
|
||||
1: 85,
|
||||
2: 66,
|
||||
69: 17,
|
||||
};
|
||||
return children[counter];
|
||||
callbacks.fetchChildrenSummary.mockResolvedValue({
|
||||
data: {
|
||||
children_summary: {
|
||||
1: { rowNumber: 0, numChildren: 85 },
|
||||
2: { rowNumber: 1, numChildren: 66 },
|
||||
69: { rowNumber: 68, numChildren: 17 },
|
||||
},
|
||||
meta_event_nested_uuid: {},
|
||||
},
|
||||
});
|
||||
const wrapper = mount(<HookTest {...callbacks} />);
|
||||
await act(async () => {
|
||||
@ -1497,12 +1403,14 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
|
||||
test('should estimate counter in gap between loaded events', async () => {
|
||||
callbacks.fetchNumEvents.mockImplementation(
|
||||
(counter) =>
|
||||
({
|
||||
1: 30,
|
||||
}[counter])
|
||||
);
|
||||
callbacks.fetchChildrenSummary.mockResolvedValue({
|
||||
data: {
|
||||
children_summary: {
|
||||
1: { rowNumber: 0, numChildren: 30 },
|
||||
},
|
||||
meta_event_nested_uuid: {},
|
||||
},
|
||||
});
|
||||
const wrapper = mount(<HookTest {...callbacks} />);
|
||||
await act(async () => {
|
||||
wrapper.find('#test').prop('addEvents')([
|
||||
@ -1556,12 +1464,14 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
|
||||
test('should estimate counter in gap before loaded sibling events', async () => {
|
||||
callbacks.fetchNumEvents.mockImplementation(
|
||||
(counter) =>
|
||||
({
|
||||
1: 30,
|
||||
}[counter])
|
||||
);
|
||||
callbacks.fetchChildrenSummary.mockResolvedValue({
|
||||
data: {
|
||||
children_summary: {
|
||||
1: { rowNumber: 0, numChildren: 30 },
|
||||
},
|
||||
meta_event_nested_uuid: {},
|
||||
},
|
||||
});
|
||||
const wrapper = mount(<HookTest {...callbacks} />);
|
||||
await act(async () => {
|
||||
wrapper.find('#test').prop('addEvents')([
|
||||
@ -1599,12 +1509,14 @@ describe('useJobEvents', () => {
|
||||
});
|
||||
|
||||
test('should get counter for node between unloaded siblings', async () => {
|
||||
callbacks.fetchNumEvents.mockImplementation(
|
||||
(counter) =>
|
||||
({
|
||||
1: 30,
|
||||
}[counter])
|
||||
);
|
||||
callbacks.fetchChildrenSummary.mockResolvedValue({
|
||||
data: {
|
||||
children_summary: {
|
||||
1: { rowNumber: 0, numChildren: 30 },
|
||||
},
|
||||
meta_event_nested_uuid: {},
|
||||
},
|
||||
});
|
||||
const wrapper = mount(<HookTest {...callbacks} />);
|
||||
await act(async () => {
|
||||
wrapper.find('#test').prop('addEvents')([
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user