[Terminal Output] fixed an issue with the positioning of the playhead and markers. as w… (#141550)

* fixed an issue with the positioning of the playhead and markers. as well as fixed refetch logic for output events

* fix for skip to end, and also rendering race condition

* smoothed out the slide animation when showing tty player

* fix to search highlight regression

* null check fix

* jest test fix

Co-authored-by: Karl Godard <karlgodard@elastic.co>
This commit is contained in:
Karl Godard 2022-09-23 08:19:36 -07:00 committed by GitHub
parent 2e91c672f0
commit e89ec58e9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 67 additions and 56 deletions

View file

@ -15,19 +15,18 @@ export const useStyles = () => {
const cached = useMemo(() => {
const descriptionList: CSSObject = {
padding: `${euiTheme.size.base} ${euiTheme.size.s} `,
alignItems: 'flex-start',
};
const tabListTitle = {
width: '40%',
display: 'flex',
alignItems: 'baseline',
marginTop: '0px',
};
const tabListDescription = {
width: '60%',
display: 'flex',
alignItems: 'baseline',
marginTop: '0px',
};

View file

@ -81,49 +81,49 @@ export const useIOLines = (pages: ProcessEventsPage[] | undefined) => {
return { lines: processedLines, processStartMarkers: processedMarkers };
}
const pagesToProcess = pages.slice(cursor);
const events = pages.reduce(
(previous, current) => previous.concat(current.events || []),
[] as ProcessEvent[]
);
const eventsToProcess = events.slice(cursor);
const newMarkers: ProcessStartMarker[] = [];
let newLines: IOLine[] = [];
newLines = pagesToProcess.reduce((previous, current) => {
if (current.events) {
current.events.forEach((event) => {
const { process } = event;
if (process?.io?.text !== undefined && process.entity_id !== undefined) {
const splitLines = process.io.text.split(TTY_LINE_SPLITTER_REGEX);
const combinedLines = [splitLines[0]];
const previousProcessId =
previous[previous.length - 1]?.event.process?.entity_id ||
processedLines[processedLines.length - 1]?.event.process?.entity_id;
eventsToProcess.forEach((event, index) => {
const { process } = event;
if (process?.io?.text !== undefined && process.entity_id !== undefined) {
const previousProcessId =
newLines[newLines.length - 1]?.event?.process?.entity_id ||
processedLines[processedLines.length - 1]?.event.process?.entity_id;
if (previousProcessId !== process.entity_id) {
const processLineInfo: ProcessStartMarker = {
line: processedLines.length + previous.length,
event,
};
newMarkers.push(processLineInfo);
}
if (previousProcessId !== process.entity_id) {
const processLineInfo: ProcessStartMarker = {
line: processedLines.length + newLines.length,
event,
};
newMarkers.push(processLineInfo);
}
// delimiters e.g \r\n or cursor movements are merged with their line text
// we start on an odd number so that cursor movements happen at the start of each line
// this is needed for the search to work accurately
for (let i = 1; i < splitLines.length - 1; i = i + 2) {
combinedLines.push(splitLines[i] + splitLines[i + 1]);
}
const splitLines = process.io.text.split(TTY_LINE_SPLITTER_REGEX);
const combinedLines = [splitLines[0]];
const data: IOLine[] = combinedLines.map((line) => {
return {
event, // pointer to the event so it's easy to look up other details for the line
value: line,
};
});
// delimiters e.g \r\n or cursor movements are merged with their line text
// we start on an odd number so that cursor movements happen at the start of each line
// this is needed for the search to work accurately
for (let i = 1; i < splitLines.length - 1; i = i + 2) {
combinedLines.push(splitLines[i] + splitLines[i + 1]);
}
previous = previous.concat(data);
}
const data: IOLine[] = combinedLines.map((line) => {
return {
event, // pointer to the event so it's easy to look up other details for the line
value: line,
};
});
newLines = newLines.concat(data);
}
return previous;
}, newLines);
});
const lines = processedLines.concat(newLines);
const processStartMarkers = processedMarkers.concat(newMarkers);
@ -136,8 +136,10 @@ export const useIOLines = (pages: ProcessEventsPage[] | undefined) => {
setProcessedMarkers(processStartMarkers);
}
if (pages.length > cursor) {
setCursor(pages.length);
const newCursor = cursor + eventsToProcess.length;
if (newCursor > cursor) {
setCursor(newCursor);
}
return {
@ -279,13 +281,14 @@ export const useXtermPlayer = ({
useEffect(() => {
if (isPlaying) {
const timer = setTimeout(() => {
if (currentLine < lines.length - 1) {
setCurrentLine(Math.min(lines.length - 1, currentLine + TTY_LINES_PER_FRAME));
} else {
setIsPlaying(false);
}
render(currentLine, false);
if (currentLine === lines.length - 1) {
setIsPlaying(false);
} else {
const nextLine = Math.min(lines.length - 1, currentLine + TTY_LINES_PER_FRAME);
setCurrentLine(nextLine);
}
}, playSpeed);
return () => {

View file

@ -47,7 +47,8 @@ export const TTYPlayer = ({
const ref = useRef<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const { data, fetchNextPage, hasNextPage, isFetching } = useFetchIOEvents(sessionEntityId);
const { data, fetchNextPage, hasNextPage, isFetching, refetch } =
useFetchIOEvents(sessionEntityId);
const { lines, processStartMarkers } = useIOLines(data?.pages);
const [fontSize, setFontSize] = useState(DEFAULT_TTY_FONT_SIZE);
const [isPlaying, setIsPlaying] = useState(false);
@ -67,6 +68,13 @@ export const TTYPlayer = ({
const currentProcessEvent = lines[Math.min(lines.length - 1, currentLine)]?.event;
const tty = currentProcessEvent?.process?.tty;
useEffect(() => {
if (show) {
// refetch the most recent page when tty player is loaded
refetch({ refetchPage: (_page, index, allPages) => allPages.length - 1 === index });
}
}, [refetch, show]);
useEffect(() => {
if (
autoSeekToEntityId &&

View file

@ -20,7 +20,7 @@ export const useStyles = (tty?: Teletype, show?: boolean) => {
position: 'absolute',
top: 0,
opacity: show ? 1 : 0,
transition: 'opacity .3s',
transition: 'opacity .2s',
pointerEvents: show ? 'auto' : 'none',
width: '100%',
height: '100%',
@ -38,7 +38,7 @@ export const useStyles = (tty?: Teletype, show?: boolean) => {
};
const header: CSSObject = {
display: show ? 'block' : 'none',
visibility: show ? 'visible' : 'hidden',
backgroundColor: `${euiVars.euiFormBackgroundDisabledColor}`,
padding: `${size.m} ${size.base}`,
};

View file

@ -97,7 +97,7 @@ describe('TTYPlayerControls component', () => {
it('clicking on end button triggers onSeekLine', async () => {
renderResult = mockedContext.render(<TTYPlayerControls {...props} />);
renderResult.queryByTestId('sessionView:TTYPlayerControlsEnd')?.click();
expect(props.onSeekLine).toHaveBeenCalledWith(10);
expect(props.onSeekLine).toHaveBeenCalledWith(9);
});
it('render output markers', async () => {

View file

@ -75,7 +75,7 @@ export const TTYPlayerControls = ({
}, [onSeekLine]);
const seekToEnd = useCallback(() => {
onSeekLine(linesLength);
onSeekLine(linesLength - 1);
}, [linesLength, onSeekLine]);
const seekToPrevProcess = useCallback(() => {

View file

@ -32,7 +32,10 @@ export const TTYPlayerControlsMarkers = ({
onChange,
onSeekLine,
}: Props) => {
const progress = useMemo(() => (currentLine / linesLength) * 100, [currentLine, linesLength]);
const progress = useMemo(
() => (currentLine / (linesLength - 1)) * 100,
[currentLine, linesLength]
);
const styles = useStyles(progress);
@ -90,7 +93,7 @@ export const TTYPlayerControlsMarkers = ({
// markers positions are absolute, setting higher z-index on the selected one in case there
// are severals next to each other
const markerWrapperPositioning = {
left: `${(line / linesLength) * 100}%`,
left: `${(line / (linesLength - 1)) * 100}%`,
zIndex: selected ? 3 : 2,
};

View file

@ -85,7 +85,7 @@ export const useStyles = (progress: number) => {
"input[type='range']::-moz-range-thumb": customThumb,
'.euiRangeHighlight__progress': {
backgroundColor: euiVars.euiColorVis0_behindText,
width: progress + '%',
width: progress + '%!important',
borderBottomRightRadius: 0,
borderTopRightRadius: 0,
},

View file

@ -43,6 +43,7 @@ export const getTTYQueryPredicates = async (
{ term: { [EVENT_ACTION]: 'fork' } },
{ term: { [EVENT_ACTION]: 'exec' } },
{ term: { [EVENT_ACTION]: 'end' } },
{ term: { [EVENT_ACTION]: 'text_output' } },
],
must: [{ term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } }],
},
@ -56,12 +57,9 @@ export const getTTYQueryPredicates = async (
if (lastEventHits.length > 0) {
const lastEvent: ProcessEvent = lastEventHits[0]._source as ProcessEvent;
const sessionEnded = lastEvent.event?.action === EventAction.end && lastEvent['@timestamp'];
const lastEventTime = lastEvent['@timestamp'];
const rangeEnd =
sessionEnded && lastEventTime
? parse(lastEventTime)?.add(30, 'second').toISOString()
: new Date().toISOString();
(lastEventTime && parse(lastEventTime)?.toISOString()) || new Date().toISOString();
const range = [lastEvent?.process?.entry_leader?.start, rangeEnd];
const tty = lastEvent?.process?.entry_leader?.tty;
const hostId = lastEvent?.host?.id;