diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutput.js b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
index 5d4e06807c..21510db48c 100644
--- a/awx/ui/src/screens/Job/JobOutput/JobOutput.js
+++ b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
@@ -2,19 +2,12 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { t } from '@lingui/macro';
import styled from 'styled-components';
-import {
- AutoSizer,
- CellMeasurer,
- CellMeasurerCache,
- InfiniteLoader,
- List,
-} from 'react-virtualized';
+import { CellMeasurerCache } from 'react-virtualized';
import { Button } from '@patternfly/react-core';
import AlertModal from 'components/AlertModal';
import { CardBody as _CardBody } from 'components/Card';
import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
import ErrorDetail from 'components/ErrorDetail';
import StatusIcon from 'components/StatusIcon';
@@ -23,11 +16,7 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
import useInterval from 'hooks/useInterval';
import { parseQueryString, getQSConfig } from 'util/qs';
import useIsMounted from 'hooks/useIsMounted';
-import JobEvent from './JobEvent';
-import JobEventSkeleton from './JobEventSkeleton';
-import PageControls from './PageControls';
-import HostEventModal from './HostEventModal';
-import JobOutputSearch from './JobOutputSearch';
+import JobOutputPane from './JobOutputPane';
import { HostStatusBar, OutputToolbar } from './shared';
import getRowRangePageSize from './shared/jobOutputUtils';
import getLineTextHtml from './getLineTextHtml';
@@ -56,27 +45,6 @@ const OutputHeader = styled.div`
justify-content: space-between;
`;
-const OutputWrapper = styled.div`
- background-color: #ffffff;
- display: flex;
- flex-direction: column;
- flex: 1 1 auto;
- font-family: monospace;
- font-size: 15px;
- outline: 1px solid #d7d7d7;
- ${({ cssMap }) =>
- Object.keys(cssMap).map(
- (className) => `.${className}{${cssMap[className]}}`
- )}
-`;
-
-const OutputFooter = styled.div`
- background-color: #ebebeb;
- border-right: 1px solid #d7d7d7;
- width: 75px;
- flex: 1;
-`;
-
let ws;
function connectJobSocket({ type, id }, onMessage) {
ws = new WebSocket(
@@ -129,23 +97,6 @@ function range(low, high) {
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({
fixedWidth: true,
defaultHeight: 25,
@@ -175,18 +126,13 @@ const getEventRequestParams = (job, remoteRowCount, requestRange) => {
function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
const location = useLocation();
const listRef = useRef(null);
- const previousWidth = useRef(0);
const jobSocketCounter = useRef(0);
const isMounted = useIsMounted();
- const scrollTop = useRef(0);
- const scrollHeight = useRef(0);
const history = useHistory();
const [contentError, setContentError] = useState(null);
const [cssMap, setCssMap] = useState({});
const [currentlyLoading, setCurrentlyLoading] = useState([]);
const [hasContentLoading, setHasContentLoading] = useState(true);
- const [hostEvent, setHostEvent] = useState({});
- const [isHostModalOpen, setIsHostModalOpen] = useState(false);
const [jobStatus, setJobStatus] = useState(job.status ?? 'waiting');
const [showCancelModal, setShowCancelModal] = useState(false);
const [remoteRowCount, setRemoteRowCount] = useState(0);
@@ -203,6 +149,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
isMonitoringWebsocket ? 5000 : null
);
+ // A
useEffect(() => {
loadJobEvents();
@@ -234,12 +181,14 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
};
}, [location.search]); // eslint-disable-line react-hooks/exhaustive-deps
+ // B
useEffect(() => {
if (listRef.current?.recomputeRowHeights) {
listRef.current.recomputeRowHeights();
}
}, [currentlyLoading, cssMap, remoteRowCount]);
+ // C
useEffect(() => {
if (jobStatus && !isJobRunning(jobStatus)) {
if (jobSocketCounter.current > remoteRowCount && isMounted.current) {
@@ -394,165 +343,6 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
}
};
- const isRowLoaded = ({ index }) => {
- if (results[index]) {
- return true;
- }
- return currentlyLoading.includes(index);
- };
-
- const handleHostEventClick = (hostEventToOpen) => {
- setHostEvent(hostEventToOpen);
- setIsHostModalOpen(true);
- };
-
- const handleHostModalClose = () => {
- setIsHostModalOpen(false);
- };
-
- const rowRenderer = ({ index, parent, key, style }) => {
- if (listRef.current && isFollowModeEnabled) {
- setTimeout(() => scrollToRow(remoteRowCount - 1), 0);
- }
- let actualLineTextHtml = [];
- if (results[index]) {
- const { lineTextHtml } = getLineTextHtml(results[index]);
- actualLineTextHtml = lineTextHtml;
- }
-
- return (
-
- {results[index] ? (
- handleHostEventClick(results[index])}
- className="row"
- style={style}
- lineTextHtml={actualLineTextHtml}
- index={index}
- {...results[index]}
- />
- ) : (
-
- )}
-
- );
- };
-
- const loadMoreRows = ({ startIndex, stopIndex }) => {
- if (startIndex === 0 && stopIndex === 0) {
- return Promise.resolve(null);
- }
-
- if (stopIndex > startIndex + 50) {
- stopIndex = startIndex + 50;
- }
-
- const [requestParams, loadRange, firstIndex] = getEventRequestParams(
- job,
- remoteRowCount,
- [startIndex, stopIndex]
- );
-
- if (isMounted.current) {
- setCurrentlyLoading((prevCurrentlyLoading) =>
- prevCurrentlyLoading.concat(loadRange)
- );
- }
-
- const params = {
- ...requestParams,
- ...parseQueryString(QS_CONFIG, location.search),
- };
-
- return getJobModel(job.type)
- .readEvents(job.id, params)
- .then((response) => {
- if (isMounted.current) {
- 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);
- });
- }
- });
- };
-
- const scrollToRow = (rowIndex) => {
- if (listRef.current) {
- listRef.current.scrollToRow(rowIndex);
- }
- };
-
- const handleScrollPrevious = () => {
- const startIndex = listRef.current.Grid._renderedRowStartIndex;
- const stopIndex = listRef.current.Grid._renderedRowStopIndex;
- const scrollRange = stopIndex - startIndex + 1;
- scrollToRow(Math.max(0, startIndex - scrollRange));
- };
-
- const handleScrollNext = () => {
- const stopIndex = listRef.current.Grid._renderedRowStopIndex;
- scrollToRow(stopIndex - 1);
- };
-
- const handleScrollFirst = () => {
- scrollToRow(0);
- };
-
- const handleScrollLast = () => {
- scrollToRow(remoteRowCount - 1);
- };
-
- const handleResize = ({ width }) => {
- if (width !== previousWidth) {
- cache.clearAll();
- if (listRef.current?.recomputeRowHeights) {
- listRef.current.recomputeRowHeights();
- }
- }
- previousWidth.current = width;
- };
-
- const handleScroll = (e) => {
- if (
- isFollowModeEnabled &&
- scrollTop.current > e.scrollTop &&
- scrollHeight.current === e.scrollHeight
- ) {
- setIsFollowModeEnabled(false);
- }
- scrollTop.current = e.scrollTop;
- scrollHeight.current = e.scrollHeight;
- };
-
if (contentError) {
return ;
}
@@ -560,13 +350,6 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
return (
<>
- {isHostModalOpen && (
-
- )}
@@ -581,61 +364,25 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
/>
-
-
-
-
- {({ onRowsRendered, registerChild }) => (
-
- {({ width, height }) => (
- <>
- {hasContentLoading ? (
-
-
-
- ) : (
- {
- registerChild(ref);
- listRef.current = ref;
- }}
- deferredMeasurementCache={cache}
- height={height || 1}
- onRowsRendered={onRowsRendered}
- rowCount={remoteRowCount}
- rowHeight={cache.rowHeight}
- rowRenderer={rowRenderer}
- scrollToAlignment="start"
- width={width || 1}
- overscanRowCount={20}
- onScroll={handleScroll}
- />
- )}
- >
- )}
-
- )}
-
-
-
{showCancelModal && isJobRunning(job.status) && (
+ Object.keys(cssMap).map(
+ (className) => `.${className}{${cssMap[className]}}`
+ )}
+`;
+
+const OutputFooter = styled.div`
+ background-color: #ebebeb;
+ border-right: 1px solid #d7d7d7;
+ width: 75px;
+ flex: 1;
+`;
+
+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;
+}
+
+export default function JobOutputPane({
+ qsConfig,
+ job,
+ eventRelatedSearchableKeys,
+ eventSearchableKeys,
+ results,
+ setResults,
+ currentlyLoading,
+ setCurrentlyLoading,
+ hasContentLoading,
+ listRef,
+ remoteRowCount,
+ isFollowModeEnabled,
+ setIsFollowModeEnabled,
+ cache,
+ cssMap,
+ setCssMap,
+ getEventRequestParams,
+}) {
+ const previousWidth = useRef(0);
+ const scrollTop = useRef(0);
+ const scrollHeight = useRef(0);
+ const isMounted = useIsMounted();
+ const location = useLocation();
+
+ const [hostEvent, setHostEvent] = useState({});
+ const [isHostModalOpen, setIsHostModalOpen] = useState(false);
+
+ const isRowLoaded = ({ index }) => {
+ if (results[index]) {
+ return true;
+ }
+ return currentlyLoading.includes(index);
+ };
+
+ const handleHostEventClick = (hostEventToOpen) => {
+ setHostEvent(hostEventToOpen);
+ setIsHostModalOpen(true);
+ };
+
+ const scrollToRow = (rowIndex) => {
+ if (listRef.current) {
+ listRef.current.scrollToRow(rowIndex);
+ }
+ };
+
+ const rowRenderer = ({ index, parent, key, style }) => {
+ if (listRef.current && isFollowModeEnabled) {
+ setTimeout(() => scrollToRow(remoteRowCount - 1), 0);
+ }
+ let actualLineTextHtml = [];
+ if (results[index]) {
+ const { lineTextHtml } = getLineTextHtml(results[index]);
+ actualLineTextHtml = lineTextHtml;
+ }
+
+ return (
+
+ {results[index] ? (
+ handleHostEventClick(results[index])}
+ className="row"
+ style={style}
+ lineTextHtml={actualLineTextHtml}
+ index={index}
+ {...results[index]}
+ />
+ ) : (
+
+ )}
+
+ );
+ };
+
+ const loadMoreRows = ({ startIndex, stopIndex }) => {
+ if (startIndex === 0 && stopIndex === 0) {
+ return Promise.resolve(null);
+ }
+
+ if (stopIndex > startIndex + 50) {
+ stopIndex = startIndex + 50;
+ }
+
+ const [requestParams, loadRange, firstIndex] = getEventRequestParams(
+ job,
+ remoteRowCount,
+ [startIndex, stopIndex]
+ );
+
+ if (isMounted.current) {
+ setCurrentlyLoading((prevCurrentlyLoading) =>
+ prevCurrentlyLoading.concat(loadRange)
+ );
+ }
+
+ const params = {
+ ...requestParams,
+ ...parseQueryString(qsConfig, location.search),
+ };
+
+ return getJobModel(job.type)
+ .readEvents(job.id, params)
+ .then((response) => {
+ if (isMounted.current) {
+ 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);
+ });
+ }
+ });
+ };
+
+ const handleResize = ({ width }) => {
+ if (width !== previousWidth) {
+ cache.clearAll();
+ if (listRef.current?.recomputeRowHeights) {
+ listRef.current.recomputeRowHeights();
+ }
+ }
+ previousWidth.current = width;
+ };
+
+ const handleScroll = (e) => {
+ if (
+ isFollowModeEnabled &&
+ scrollTop.current > e.scrollTop &&
+ scrollHeight.current === e.scrollHeight
+ ) {
+ setIsFollowModeEnabled(false);
+ }
+ scrollTop.current = e.scrollTop;
+ scrollHeight.current = e.scrollHeight;
+ };
+
+ const handleScrollPrevious = () => {
+ const startIndex = listRef.current.Grid._renderedRowStartIndex;
+ const stopIndex = listRef.current.Grid._renderedRowStopIndex;
+ const scrollRange = stopIndex - startIndex + 1;
+ scrollToRow(Math.max(0, startIndex - scrollRange));
+ };
+
+ const handleScrollNext = () => {
+ const stopIndex = listRef.current.Grid._renderedRowStopIndex;
+ scrollToRow(stopIndex - 1);
+ };
+
+ const handleScrollFirst = () => {
+ scrollToRow(0);
+ };
+
+ const handleScrollLast = () => {
+ scrollToRow(remoteRowCount - 1);
+ };
+
+ return (
+ <>
+
+
+
+ {isHostModalOpen && (
+ setIsHostModalOpen(false)}
+ isOpen={isHostModalOpen}
+ hostEvent={hostEvent}
+ />
+ )}
+
+ {({ onRowsRendered, registerChild }) => (
+
+ {({ width, height }) => (
+ <>
+ {hasContentLoading ? (
+
+
+
+ ) : (
+ {
+ registerChild(ref);
+ listRef.current = ref;
+ }}
+ deferredMeasurementCache={cache}
+ height={height || 1}
+ onRowsRendered={onRowsRendered}
+ rowCount={remoteRowCount}
+ rowHeight={cache.rowHeight}
+ rowRenderer={rowRenderer}
+ scrollToAlignment="start"
+ width={width || 1}
+ overscanRowCount={20}
+ onScroll={handleScroll}
+ />
+ )}
+ >
+ )}
+
+ )}
+
+
+
+ >
+ );
+}