mirror of
https://github.com/ansible/awx.git
synced 2026-02-25 15:06:02 -03:30
JobOutput: extract JobOutputPane
This commit is contained in:
@@ -2,19 +2,12 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import { CellMeasurerCache } from 'react-virtualized';
|
||||||
AutoSizer,
|
|
||||||
CellMeasurer,
|
|
||||||
CellMeasurerCache,
|
|
||||||
InfiniteLoader,
|
|
||||||
List,
|
|
||||||
} from 'react-virtualized';
|
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
|
|
||||||
import AlertModal from 'components/AlertModal';
|
import AlertModal from 'components/AlertModal';
|
||||||
import { CardBody as _CardBody } from 'components/Card';
|
import { CardBody as _CardBody } from 'components/Card';
|
||||||
import ContentError from 'components/ContentError';
|
import ContentError from 'components/ContentError';
|
||||||
import ContentLoading from 'components/ContentLoading';
|
|
||||||
import ErrorDetail from 'components/ErrorDetail';
|
import ErrorDetail from 'components/ErrorDetail';
|
||||||
import StatusIcon from 'components/StatusIcon';
|
import StatusIcon from 'components/StatusIcon';
|
||||||
|
|
||||||
@@ -23,11 +16,7 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
|
|||||||
import useInterval from 'hooks/useInterval';
|
import useInterval from 'hooks/useInterval';
|
||||||
import { parseQueryString, getQSConfig } from 'util/qs';
|
import { parseQueryString, getQSConfig } from 'util/qs';
|
||||||
import useIsMounted from 'hooks/useIsMounted';
|
import useIsMounted from 'hooks/useIsMounted';
|
||||||
import JobEvent from './JobEvent';
|
import JobOutputPane from './JobOutputPane';
|
||||||
import JobEventSkeleton from './JobEventSkeleton';
|
|
||||||
import PageControls from './PageControls';
|
|
||||||
import HostEventModal from './HostEventModal';
|
|
||||||
import JobOutputSearch from './JobOutputSearch';
|
|
||||||
import { HostStatusBar, OutputToolbar } from './shared';
|
import { HostStatusBar, OutputToolbar } from './shared';
|
||||||
import getRowRangePageSize from './shared/jobOutputUtils';
|
import getRowRangePageSize from './shared/jobOutputUtils';
|
||||||
import getLineTextHtml from './getLineTextHtml';
|
import getLineTextHtml from './getLineTextHtml';
|
||||||
@@ -56,27 +45,6 @@ const OutputHeader = styled.div`
|
|||||||
justify-content: space-between;
|
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;
|
let ws;
|
||||||
function connectJobSocket({ type, id }, onMessage) {
|
function connectJobSocket({ type, id }, onMessage) {
|
||||||
ws = new WebSocket(
|
ws = new WebSocket(
|
||||||
@@ -129,23 +97,6 @@ function range(low, high) {
|
|||||||
return numbers;
|
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,
|
||||||
@@ -175,18 +126,13 @@ const getEventRequestParams = (job, remoteRowCount, requestRange) => {
|
|||||||
function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const listRef = useRef(null);
|
const listRef = useRef(null);
|
||||||
const previousWidth = useRef(0);
|
|
||||||
const jobSocketCounter = useRef(0);
|
const jobSocketCounter = useRef(0);
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
const scrollTop = useRef(0);
|
|
||||||
const scrollHeight = useRef(0);
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [contentError, setContentError] = useState(null);
|
const [contentError, setContentError] = useState(null);
|
||||||
const [cssMap, setCssMap] = useState({});
|
const [cssMap, setCssMap] = useState({});
|
||||||
const [currentlyLoading, setCurrentlyLoading] = useState([]);
|
const [currentlyLoading, setCurrentlyLoading] = useState([]);
|
||||||
const [hasContentLoading, setHasContentLoading] = useState(true);
|
const [hasContentLoading, setHasContentLoading] = useState(true);
|
||||||
const [hostEvent, setHostEvent] = useState({});
|
|
||||||
const [isHostModalOpen, setIsHostModalOpen] = useState(false);
|
|
||||||
const [jobStatus, setJobStatus] = useState(job.status ?? 'waiting');
|
const [jobStatus, setJobStatus] = useState(job.status ?? 'waiting');
|
||||||
const [showCancelModal, setShowCancelModal] = useState(false);
|
const [showCancelModal, setShowCancelModal] = useState(false);
|
||||||
const [remoteRowCount, setRemoteRowCount] = useState(0);
|
const [remoteRowCount, setRemoteRowCount] = useState(0);
|
||||||
@@ -203,6 +149,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
isMonitoringWebsocket ? 5000 : null
|
isMonitoringWebsocket ? 5000 : null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadJobEvents();
|
loadJobEvents();
|
||||||
|
|
||||||
@@ -234,12 +181,14 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
};
|
};
|
||||||
}, [location.search]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [location.search]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
// B
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (listRef.current?.recomputeRowHeights) {
|
if (listRef.current?.recomputeRowHeights) {
|
||||||
listRef.current.recomputeRowHeights();
|
listRef.current.recomputeRowHeights();
|
||||||
}
|
}
|
||||||
}, [currentlyLoading, cssMap, remoteRowCount]);
|
}, [currentlyLoading, cssMap, remoteRowCount]);
|
||||||
|
|
||||||
|
// C
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (jobStatus && !isJobRunning(jobStatus)) {
|
if (jobStatus && !isJobRunning(jobStatus)) {
|
||||||
if (jobSocketCounter.current > remoteRowCount && isMounted.current) {
|
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 (
|
|
||||||
<CellMeasurer
|
|
||||||
key={key}
|
|
||||||
cache={cache}
|
|
||||||
parent={parent}
|
|
||||||
rowIndex={index}
|
|
||||||
columnIndex={0}
|
|
||||||
>
|
|
||||||
{results[index] ? (
|
|
||||||
<JobEvent
|
|
||||||
isClickable={isHostEvent(results[index])}
|
|
||||||
onJobEventClick={() => handleHostEventClick(results[index])}
|
|
||||||
className="row"
|
|
||||||
style={style}
|
|
||||||
lineTextHtml={actualLineTextHtml}
|
|
||||||
index={index}
|
|
||||||
{...results[index]}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<JobEventSkeleton
|
|
||||||
className="row"
|
|
||||||
style={style}
|
|
||||||
counter={index}
|
|
||||||
contentLength={80}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CellMeasurer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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) {
|
if (contentError) {
|
||||||
return <ContentError error={contentError} />;
|
return <ContentError error={contentError} />;
|
||||||
}
|
}
|
||||||
@@ -560,13 +350,6 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
{isHostModalOpen && (
|
|
||||||
<HostEventModal
|
|
||||||
onClose={handleHostModalClose}
|
|
||||||
isOpen={isHostModalOpen}
|
|
||||||
hostEvent={hostEvent}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<OutputHeader>
|
<OutputHeader>
|
||||||
<HeaderTitle>
|
<HeaderTitle>
|
||||||
<StatusIcon status={job.status} />
|
<StatusIcon status={job.status} />
|
||||||
@@ -581,61 +364,25 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
/>
|
/>
|
||||||
</OutputHeader>
|
</OutputHeader>
|
||||||
<HostStatusBar counts={job.host_status_counts} />
|
<HostStatusBar counts={job.host_status_counts} />
|
||||||
<JobOutputSearch
|
<JobOutputPane
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
job={job}
|
job={job}
|
||||||
eventRelatedSearchableKeys={eventRelatedSearchableKeys}
|
eventRelatedSearchableKeys={eventRelatedSearchableKeys}
|
||||||
eventSearchableKeys={eventSearchableKeys}
|
eventSearchableKeys={eventSearchableKeys}
|
||||||
|
results={results}
|
||||||
|
setResults={setResults}
|
||||||
|
currentlyLoading={currentlyLoading}
|
||||||
|
setCurrentlyLoading={setCurrentlyLoading}
|
||||||
|
hasContentLoading={hasContentLoading}
|
||||||
|
listRef={listRef}
|
||||||
remoteRowCount={remoteRowCount}
|
remoteRowCount={remoteRowCount}
|
||||||
scrollToRow={scrollToRow}
|
|
||||||
isFollowModeEnabled={isFollowModeEnabled}
|
isFollowModeEnabled={isFollowModeEnabled}
|
||||||
setIsFollowModeEnabled={setIsFollowModeEnabled}
|
setIsFollowModeEnabled={setIsFollowModeEnabled}
|
||||||
|
cache={cache}
|
||||||
|
cssMap={cssMap}
|
||||||
|
setCssMap={setCssMap}
|
||||||
|
getEventRequestParams={getEventRequestParams}
|
||||||
/>
|
/>
|
||||||
<PageControls
|
|
||||||
onScrollFirst={handleScrollFirst}
|
|
||||||
onScrollLast={handleScrollLast}
|
|
||||||
onScrollNext={handleScrollNext}
|
|
||||||
onScrollPrevious={handleScrollPrevious}
|
|
||||||
/>
|
|
||||||
<OutputWrapper cssMap={cssMap}>
|
|
||||||
<InfiniteLoader
|
|
||||||
isRowLoaded={isRowLoaded}
|
|
||||||
loadMoreRows={loadMoreRows}
|
|
||||||
rowCount={remoteRowCount}
|
|
||||||
>
|
|
||||||
{({ onRowsRendered, registerChild }) => (
|
|
||||||
<AutoSizer nonce={window.NONCE_ID} onResize={handleResize}>
|
|
||||||
{({ width, height }) => (
|
|
||||||
<>
|
|
||||||
{hasContentLoading ? (
|
|
||||||
<div style={{ width }}>
|
|
||||||
<ContentLoading />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<List
|
|
||||||
ref={(ref) => {
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
)}
|
|
||||||
</InfiniteLoader>
|
|
||||||
<OutputFooter />
|
|
||||||
</OutputWrapper>
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{showCancelModal && isJobRunning(job.status) && (
|
{showCancelModal && isJobRunning(job.status) && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
|
|||||||
308
awx/ui/src/screens/Job/JobOutput/JobOutputPane.js
Normal file
308
awx/ui/src/screens/Job/JobOutput/JobOutputPane.js
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import React, { useState, useRef } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
AutoSizer,
|
||||||
|
CellMeasurer,
|
||||||
|
InfiniteLoader,
|
||||||
|
List,
|
||||||
|
} from 'react-virtualized';
|
||||||
|
import ContentLoading from 'components/ContentLoading';
|
||||||
|
import { parseQueryString } from 'util/qs';
|
||||||
|
import { getJobModel } from 'util/jobs';
|
||||||
|
import useIsMounted from 'hooks/useIsMounted';
|
||||||
|
import PageControls from './PageControls';
|
||||||
|
import JobOutputSearch from './JobOutputSearch';
|
||||||
|
import JobEvent from './JobEvent';
|
||||||
|
import JobEventSkeleton from './JobEventSkeleton';
|
||||||
|
import HostEventModal from './HostEventModal';
|
||||||
|
import getLineTextHtml from './getLineTextHtml';
|
||||||
|
|
||||||
|
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;
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<CellMeasurer
|
||||||
|
key={key}
|
||||||
|
cache={cache}
|
||||||
|
parent={parent}
|
||||||
|
rowIndex={index}
|
||||||
|
columnIndex={0}
|
||||||
|
>
|
||||||
|
{results[index] ? (
|
||||||
|
<JobEvent
|
||||||
|
isClickable={isHostEvent(results[index])}
|
||||||
|
onJobEventClick={() => handleHostEventClick(results[index])}
|
||||||
|
className="row"
|
||||||
|
style={style}
|
||||||
|
lineTextHtml={actualLineTextHtml}
|
||||||
|
index={index}
|
||||||
|
{...results[index]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<JobEventSkeleton
|
||||||
|
className="row"
|
||||||
|
style={style}
|
||||||
|
counter={index}
|
||||||
|
contentLength={80}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CellMeasurer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<JobOutputSearch
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
job={job}
|
||||||
|
eventRelatedSearchableKeys={eventRelatedSearchableKeys}
|
||||||
|
eventSearchableKeys={eventSearchableKeys}
|
||||||
|
remoteRowCount={remoteRowCount}
|
||||||
|
scrollToRow={scrollToRow}
|
||||||
|
isFollowModeEnabled={isFollowModeEnabled}
|
||||||
|
setIsFollowModeEnabled={setIsFollowModeEnabled}
|
||||||
|
/>
|
||||||
|
<PageControls
|
||||||
|
onScrollFirst={handleScrollFirst}
|
||||||
|
onScrollLast={handleScrollLast}
|
||||||
|
onScrollNext={handleScrollNext}
|
||||||
|
onScrollPrevious={handleScrollPrevious}
|
||||||
|
/>
|
||||||
|
<OutputWrapper cssMap={cssMap}>
|
||||||
|
{isHostModalOpen && (
|
||||||
|
<HostEventModal
|
||||||
|
onClose={() => setIsHostModalOpen(false)}
|
||||||
|
isOpen={isHostModalOpen}
|
||||||
|
hostEvent={hostEvent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<InfiniteLoader
|
||||||
|
isRowLoaded={isRowLoaded}
|
||||||
|
loadMoreRows={loadMoreRows}
|
||||||
|
rowCount={remoteRowCount}
|
||||||
|
>
|
||||||
|
{({ onRowsRendered, registerChild }) => (
|
||||||
|
<AutoSizer nonce={window.NONCE_ID} onResize={handleResize}>
|
||||||
|
{({ width, height }) => (
|
||||||
|
<>
|
||||||
|
{hasContentLoading ? (
|
||||||
|
<div style={{ width }}>
|
||||||
|
<ContentLoading />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<List
|
||||||
|
ref={(ref) => {
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
)}
|
||||||
|
</InfiniteLoader>
|
||||||
|
<OutputFooter />
|
||||||
|
</OutputWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user