diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutput.js b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
index 1a26cd9409..c6f68cf739 100644
--- a/awx/ui/src/screens/Job/JobOutput/JobOutput.js
+++ b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
@@ -168,12 +168,14 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
const {
addEvents,
toggleNodeIsCollapsed,
+ toggleCollapseAll,
getEventForRow,
getNumCollapsedEvents,
getCounterForRow,
getEvent,
clearLoadedEvents,
rebuildEventsTree,
+ isAllCollapsed,
} = useJobEvents(
{
fetchEventByUuid,
@@ -504,7 +506,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
isCollapsed={node.isCollapsed}
hasChildren={node.children.length}
onToggleCollapsed={() => {
- toggleNodeIsCollapsed(event.uuid);
+ toggleNodeIsCollapsed(event.uuid, !node.isCollapsed);
}}
/>
) : (
@@ -653,6 +655,10 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
scrollHeight.current = e.scrollHeight;
};
+ const handleExpandCollapseAll = () => {
+ toggleCollapseAll(!isAllCollapsed);
+ };
+
if (contentError) {
return ;
}
@@ -696,6 +702,10 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
onScrollLast={handleScrollLast}
onScrollNext={handleScrollNext}
onScrollPrevious={handleScrollPrevious}
+ toggleExpandCollapseAll={handleExpandCollapseAll}
+ isFlatMode={isFlatMode}
+ isTemplateJob={job.type === 'job'}
+ isAllCollapsed={isAllCollapsed}
/>
Button {
+ padding-left: 8px;
+ }
+`;
const PageControls = ({
onScrollFirst,
onScrollLast,
onScrollNext,
onScrollPrevious,
+ toggleExpandCollapseAll,
+ isAllCollapsed,
+ isFlatMode,
+ isTemplateJob,
}) => (
-
-
-
-
-
-
+ <>
+
+
+ {!isFlatMode && isTemplateJob && (
+
+ )}
+
+
+
+
+
+
+
+
+ >
);
export default PageControls;
diff --git a/awx/ui/src/screens/Job/JobOutput/PageControls.test.js b/awx/ui/src/screens/Job/JobOutput/PageControls.test.js
index 94fa3e74ac..65c5a8b684 100644
--- a/awx/ui/src/screens/Job/JobOutput/PageControls.test.js
+++ b/awx/ui/src/screens/Job/JobOutput/PageControls.test.js
@@ -22,11 +22,42 @@ describe('PageControls', () => {
});
test('should render menu control icons', () => {
- wrapper = mountWithContexts();
+ wrapper = mountWithContexts();
findChildren();
expect(AngleDoubleUpIcon.length).toBe(1);
expect(AngleDoubleDownIcon.length).toBe(1);
expect(AngleUpIcon.length).toBe(1);
expect(AngleDownIcon.length).toBe(1);
});
+
+ test('should render expand/collapse all', () => {
+ wrapper = mountWithContexts(
+
+ );
+ const expandCollapse = wrapper.find('PageControls__ExpandCollapseWrapper');
+ expect(expandCollapse).toHaveLength(1);
+ expect(expandCollapse.find('AngleDownIcon')).toHaveLength(1);
+ expect(expandCollapse.find('AngleRightIcon')).toHaveLength(0);
+ });
+
+ test('should render correct expand/collapse angle icon', () => {
+ wrapper = mountWithContexts(
+
+ );
+
+ const expandCollapse = wrapper.find('PageControls__ExpandCollapseWrapper');
+ expect(expandCollapse).toHaveLength(1);
+ expect(expandCollapse.find('AngleDownIcon')).toHaveLength(0);
+ expect(expandCollapse.find('AngleRightIcon')).toHaveLength(1);
+ });
+
+ test('Should not render expand/collapse all', () => {
+ wrapper = mountWithContexts(
+
+ );
+
+ const expandCollapse = wrapper.find('PageControls__ExpandCollapseWrapper');
+ expect(expandCollapse.find('AngleDownIcon')).toHaveLength(0);
+ expect(expandCollapse.find('AngleRightIcon')).toHaveLength(0);
+ });
});
diff --git a/awx/ui/src/screens/Job/JobOutput/useJobEvents.js b/awx/ui/src/screens/Job/JobOutput/useJobEvents.js
index 02cf0fa0bd..d2c848b7e3 100644
--- a/awx/ui/src/screens/Job/JobOutput/useJobEvents.js
+++ b/awx/ui/src/screens/Job/JobOutput/useJobEvents.js
@@ -10,12 +10,14 @@ const initialState = {
// events with parent events that aren't yet loaded.
// arrays indexed by parent uuid
eventsWithoutParents: {},
+ 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 default function useJobEvents(callbacks, isFlatMode) {
const [actionQueue, setActionQueue] = useState([]);
@@ -24,7 +26,6 @@ export default function useJobEvents(callbacks, isFlatMode) {
};
const reducer = jobEventsReducer(callbacks, isFlatMode, enqueueAction);
const [state, dispatch] = useReducer(reducer, initialState);
-
useEffect(() => {
setActionQueue((queue) => {
const action = queue[0];
@@ -43,8 +44,10 @@ export default function useJobEvents(callbacks, isFlatMode) {
return {
addEvents: (events) => dispatch({ type: ADD_EVENTS, events }),
getNodeByUuid: (uuid) => getNodeByUuid(state, uuid),
- toggleNodeIsCollapsed: (uuid) =>
- dispatch({ type: TOGGLE_NODE_COLLAPSED, uuid }),
+ toggleNodeIsCollapsed: (uuid, isCollapsed) =>
+ dispatch({ type: TOGGLE_NODE_COLLAPSED, uuid, isCollapsed }),
+ toggleCollapseAll: (isCollapsed) =>
+ dispatch({ type: TOGGLE_COLLAPSE_ALL, isCollapsed }),
getEventForRow: (rowIndex) => getEventForRow(state, rowIndex),
getNodeForRow: (rowIndex) => getNodeForRow(state, rowIndex),
getTotalNumChildren: (uuid) => {
@@ -57,6 +60,7 @@ export default function useJobEvents(callbacks, isFlatMode) {
getEvent: (eventIndex) => getEvent(state, eventIndex),
clearLoadedEvents: () => dispatch({ type: CLEAR_EVENTS }),
rebuildEventsTree: () => dispatch({ type: REBUILD_TREE }),
+ isAllCollapsed: state.isAllCollapsed,
};
}
@@ -65,6 +69,8 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
switch (action.type) {
case ADD_EVENTS:
return addEvents(state, action.events);
+ case TOGGLE_COLLAPSE_ALL:
+ return toggleCollapseAll(state, action.isCollapsed);
case TOGGLE_NODE_COLLAPSED:
return toggleNodeIsCollapsed(state, action.uuid);
case SET_EVENT_NUM_CHILDREN:
@@ -135,7 +141,7 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
const eventIndex = event.counter;
const newNode = {
eventIndex,
- isCollapsed: false,
+ isCollapsed: state.isAllCollapsed,
children: [],
};
const index = state.tree.findIndex((node) => node.eventIndex > eventIndex);
@@ -167,7 +173,7 @@ export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
}
const newNode = {
eventIndex,
- isCollapsed: false,
+ isCollapsed: state.isAllCollapsed,
children: [],
};
const index = parent.children.findIndex(
@@ -400,7 +406,6 @@ function getNumCollapsedChildren(node) {
if (node.isCollapsed) {
return getTotalNumChildren(node);
}
-
let sum = 0;
node.children.forEach((child) => {
sum += getNumCollapsedChildren(child);
@@ -409,10 +414,40 @@ function getNumCollapsedChildren(node) {
}
function toggleNodeIsCollapsed(state, eventUuid) {
- return updateNodeByUuid(state, eventUuid, (node) => ({
+ return {
+ ...updateNodeByUuid(state, eventUuid, (node) => ({
+ ...node,
+ isCollapsed: !node.isCollapsed,
+ })),
+ isAllCollapsed: false,
+ };
+}
+
+function toggleCollapseAll(state, isAllCollapsed) {
+ const newTree = state.tree.map((node) =>
+ _toggleNestedNodes(state.events, node, isAllCollapsed)
+ );
+ return { ...state, tree: newTree, isAllCollapsed };
+}
+
+function _toggleNestedNodes(events, node, isCollapsed) {
+ const {
+ parent_uuid,
+ event_data: { playbook_uuid },
+ uuid,
+ } = events[node.eventIndex];
+
+ const eventShouldNotCollapse = uuid === playbook_uuid || !parent_uuid?.length;
+
+ const children = node.children?.map((nestedNode) =>
+ _toggleNestedNodes(events, nestedNode, isCollapsed)
+ );
+
+ return {
...node,
- isCollapsed: !node.isCollapsed,
- }));
+ isCollapsed: eventShouldNotCollapse ? false : isCollapsed,
+ children,
+ };
}
function updateNodeByUuid(state, uuid, update) {
diff --git a/awx/ui/src/screens/Job/JobOutput/useJobEvents.test.js b/awx/ui/src/screens/Job/JobOutput/useJobEvents.test.js
index 1f19db3750..e0283e5acc 100644
--- a/awx/ui/src/screens/Job/JobOutput/useJobEvents.test.js
+++ b/awx/ui/src/screens/Job/JobOutput/useJobEvents.test.js
@@ -167,6 +167,7 @@ describe('useJobEvents', () => {
uuidMap: {},
eventsWithoutParents: {},
eventGaps: [],
+ isAllCollapsed: false,
};
});