upgrade Logs panel
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
import { List } from 'react-window';
|
||||
|
||||
import { createTheme, dateTimeForTimeZone, rangeUtil } from '@grafana/data';
|
||||
import { LogsSortOrder } from '@grafana/schema';
|
||||
@@ -89,19 +89,19 @@ function setup(
|
||||
loadMore={loadMoreMock}
|
||||
infiniteScrollMode={infiniteScrollMode}
|
||||
>
|
||||
{({ getItemKey, itemCount, onItemsRendered, Renderer }) => (
|
||||
<VariableSizeList
|
||||
height={100}
|
||||
itemCount={itemCount}
|
||||
itemSize={() => virtualization.getLineHeight()}
|
||||
itemKey={getItemKey}
|
||||
layout="vertical"
|
||||
onItemsRendered={onItemsRendered}
|
||||
style={{ overflow: 'scroll' }}
|
||||
width="100%"
|
||||
>
|
||||
{Renderer}
|
||||
</VariableSizeList>
|
||||
{({ itemCount, onItemsRendered, Renderer }) => (
|
||||
<List
|
||||
rowComponent={Renderer}
|
||||
rowCount={itemCount}
|
||||
rowProps={{}}
|
||||
rowHeight={() => virtualization.getLineHeight()}
|
||||
onRowsRendered={onItemsRendered}
|
||||
style={{
|
||||
overflow: 'scroll',
|
||||
height: 100,
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState, MouseEvent } from 'react';
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState, MouseEvent, type JSX } from 'react';
|
||||
import { usePrevious } from 'react-use';
|
||||
import { ListChildComponentProps, ListOnItemsRenderedProps } from 'react-window';
|
||||
import { type RowComponentProps, type ListProps } from 'react-window';
|
||||
|
||||
import { AbsoluteTimeRange, LogsSortOrder, TimeRange } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
@@ -17,8 +17,8 @@ import { LogLineVirtualization } from './virtualization';
|
||||
interface ChildrenProps {
|
||||
itemCount: number;
|
||||
getItemKey: (index: number) => string;
|
||||
onItemsRendered: (props: ListOnItemsRenderedProps) => void;
|
||||
Renderer: (props: ListChildComponentProps) => ReactNode;
|
||||
onItemsRendered: ListProps<{}>['onRowsRendered'];
|
||||
Renderer: (props: RowComponentProps) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
@@ -200,7 +200,7 @@ export const InfiniteScroll = ({
|
||||
}, [onLoadMore]);
|
||||
|
||||
const Renderer = useCallback(
|
||||
({ index, style }: ListChildComponentProps) => {
|
||||
({ index, style }: RowComponentProps) => {
|
||||
if (!logs[index] && infiniteLoaderState !== 'idle') {
|
||||
return (
|
||||
<LogLineMessage
|
||||
@@ -248,12 +248,12 @@ export const InfiniteScroll = ({
|
||||
]
|
||||
);
|
||||
|
||||
const onItemsRendered = useCallback(
|
||||
(props: ListOnItemsRenderedProps) => {
|
||||
const onItemsRendered = useCallback<NonNullable<ListProps<{}>['onRowsRendered']>>(
|
||||
(props) => {
|
||||
if (!scrollElement) {
|
||||
return;
|
||||
}
|
||||
if (props.visibleStartIndex === 0) {
|
||||
if (props.startIndex === 0) {
|
||||
noScrollRef.current = scrollElement.scrollHeight <= scrollElement.clientHeight;
|
||||
}
|
||||
if (noScrollRef.current || infiniteLoaderState === 'loading' || infiniteLoaderState === 'out-of-bounds') {
|
||||
@@ -261,9 +261,9 @@ export const InfiniteScroll = ({
|
||||
}
|
||||
const lastLogIndex = logs.length - 1;
|
||||
const preScrollIndex = logs.length - 2;
|
||||
if (props.visibleStopIndex >= lastLogIndex) {
|
||||
if (props.stopIndex >= lastLogIndex) {
|
||||
setInfiniteLoaderState('pre-scroll-bottom');
|
||||
} else if (props.visibleStartIndex < preScrollIndex) {
|
||||
} else if (props.startIndex < preScrollIndex) {
|
||||
setInfiniteLoaderState('idle');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
|
||||
import { debounce } from 'lodash';
|
||||
import { Grammar } from 'prismjs';
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, MouseEvent } from 'react';
|
||||
import { Align, VariableSizeList } from 'react-window';
|
||||
import { Align, List, ListImperativeAPI, useListRef } from 'react-window';
|
||||
|
||||
import {
|
||||
CoreApp,
|
||||
@@ -282,10 +282,9 @@ const LogListComponent = ({
|
||||
const [processedLogs, setProcessedLogs] = useState<LogListModel[]>([]);
|
||||
const [listHeight, setListHeight] = useState(getListHeight(containerElement, app));
|
||||
const theme = useTheme2();
|
||||
const listRef = useRef<VariableSizeList | null>(null);
|
||||
const listRef = useListRef(null);
|
||||
const widthRef = useRef(containerElement.clientWidth);
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const virtualization = useMemo(() => new LogLineVirtualization(theme, fontSize), [theme, fontSize]);
|
||||
const dimensions = useMemo(
|
||||
() =>
|
||||
@@ -329,25 +328,29 @@ const LogListComponent = ({
|
||||
|
||||
// When log lines report size discrepancies, we debounce the calculation reset to give time to
|
||||
// use the smallest log index to reset the heights.
|
||||
const debouncedResetAfterIndex = useMemo(() => {
|
||||
return debounce((index: number) => {
|
||||
listRef.current?.resetAfterIndex(index);
|
||||
overflowIndexRef.current = Infinity;
|
||||
}, 0);
|
||||
}, []);
|
||||
// TODO do we need this?
|
||||
// const debouncedResetAfterIndex = useMemo(() => {
|
||||
// return debounce((index: number) => {
|
||||
// listRef.current?.resetAfterIndex(index);
|
||||
// overflowIndexRef.current = Infinity;
|
||||
// }, 0);
|
||||
// }, []);
|
||||
|
||||
const debouncedScrollToItem = useMemo(() => {
|
||||
return debounce((index: number, align?: Align) => {
|
||||
listRef.current?.scrollToItem(index, align);
|
||||
listRef.current?.scrollToRow({
|
||||
index,
|
||||
align,
|
||||
});
|
||||
}, 250);
|
||||
}, []);
|
||||
}, [listRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = eventBus.subscribe(ScrollToLogsEvent, (e: ScrollToLogsEvent) =>
|
||||
handleScrollToEvent(e, filteredLogs, listRef.current)
|
||||
);
|
||||
return () => subscription.unsubscribe();
|
||||
}, [eventBus, filteredLogs]);
|
||||
}, [eventBus, filteredLogs, listRef]);
|
||||
|
||||
useEffect(() => {
|
||||
setProcessedLogs(
|
||||
@@ -366,11 +369,13 @@ const LogListComponent = ({
|
||||
)
|
||||
);
|
||||
virtualization.resetLogLineSizes();
|
||||
listRef.current?.resetAfterIndex(0);
|
||||
// TODO do we need this?
|
||||
// listRef.current?.resetAfterIndex(0);
|
||||
}, [forceEscape, getFieldLinks, grammar, logs, prettifyJSON, sortOrder, timeZone, virtualization, wrapLogMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
listRef.current?.resetAfterIndex(0);
|
||||
// TODO do we need this?
|
||||
// listRef.current?.resetAfterIndex(0);
|
||||
}, [wrapLogMessage, showDetails, displayedFields, dedupStrategy]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -399,9 +404,10 @@ const LogListComponent = ({
|
||||
return;
|
||||
}
|
||||
overflowIndexRef.current = index < overflowIndexRef.current ? index : overflowIndexRef.current;
|
||||
debouncedResetAfterIndex(overflowIndexRef.current);
|
||||
// TODO do we need this?
|
||||
// debouncedResetAfterIndex(overflowIndexRef.current);
|
||||
},
|
||||
[debouncedResetAfterIndex, virtualization, widthContainer]
|
||||
[virtualization, widthContainer]
|
||||
);
|
||||
|
||||
const handleScrollPosition = useCallback(
|
||||
@@ -410,13 +416,13 @@ const LogListComponent = ({
|
||||
if (scrollToUID) {
|
||||
const index = processedLogs.findIndex((log) => log.uid === scrollToUID);
|
||||
if (index >= 0) {
|
||||
listRef.current?.scrollToItem(index, 'start');
|
||||
listRef.current?.scrollToRow({ index, align: 'start' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
listRef.current?.scrollToItem(initialScrollPosition === 'top' ? 0 : processedLogs.length - 1);
|
||||
listRef.current?.scrollToRow({ index: initialScrollPosition === 'top' ? 0 : processedLogs.length - 1 });
|
||||
},
|
||||
[initialScrollPosition, permalinkedLogId, processedLogs]
|
||||
[initialScrollPosition, permalinkedLogId, processedLogs, listRef]
|
||||
);
|
||||
|
||||
const handleLogLineClick = useCallback(
|
||||
@@ -503,7 +509,7 @@ const LogListComponent = ({
|
||||
logs={filteredLogs}
|
||||
loadMore={loadMore}
|
||||
onClick={handleLogLineClick}
|
||||
scrollElement={scrollRef.current}
|
||||
scrollElement={listRef.current?.element ?? null}
|
||||
showTime={showTime}
|
||||
sortOrder={sortOrder}
|
||||
timeRange={timeRange}
|
||||
@@ -512,12 +518,25 @@ const LogListComponent = ({
|
||||
virtualization={virtualization}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
>
|
||||
{({ getItemKey, itemCount, onItemsRendered, Renderer }) => (
|
||||
<VariableSizeList
|
||||
{({ itemCount, onItemsRendered, Renderer }) => (
|
||||
<List
|
||||
className={styles.logList}
|
||||
height={listHeight}
|
||||
itemCount={itemCount}
|
||||
itemSize={getLogLineSize.bind(null, virtualization, filteredLogs, widthContainer, displayedFields, {
|
||||
style={
|
||||
wrapLogMessage
|
||||
? {
|
||||
height: listHeight,
|
||||
width: '100%',
|
||||
overflowY: 'scroll',
|
||||
}
|
||||
: {
|
||||
height: listHeight,
|
||||
width: '100%',
|
||||
overflow: 'scroll',
|
||||
}
|
||||
}
|
||||
rowProps={{}}
|
||||
rowCount={itemCount}
|
||||
rowHeight={getLogLineSize.bind(null, virtualization, filteredLogs, widthContainer, displayedFields, {
|
||||
detailsMode,
|
||||
hasLogsWithErrors,
|
||||
hasSampledLogs,
|
||||
@@ -526,17 +545,11 @@ const LogListComponent = ({
|
||||
showTime,
|
||||
wrap: wrapLogMessage,
|
||||
})}
|
||||
itemKey={getItemKey}
|
||||
layout="vertical"
|
||||
onItemsRendered={onItemsRendered}
|
||||
outerRef={scrollRef}
|
||||
rowComponent={Renderer}
|
||||
onRowsRendered={onItemsRendered}
|
||||
overscanCount={5}
|
||||
ref={listRef}
|
||||
style={wrapLogMessage ? { overflowY: 'scroll' } : { overflow: 'scroll' }}
|
||||
width="100%"
|
||||
>
|
||||
{Renderer}
|
||||
</VariableSizeList>
|
||||
listRef={listRef}
|
||||
/>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
@@ -586,16 +599,23 @@ function getStyles(
|
||||
};
|
||||
}
|
||||
|
||||
function handleScrollToEvent(event: ScrollToLogsEvent, logs: LogListModel[], list: VariableSizeList | null) {
|
||||
function handleScrollToEvent(event: ScrollToLogsEvent, logs: LogListModel[], list: ListImperativeAPI | null) {
|
||||
if (event.payload.scrollTo === 'top') {
|
||||
list?.scrollTo(0);
|
||||
list?.scrollToRow({
|
||||
index: 0,
|
||||
});
|
||||
} else if (event.payload.scrollTo === 'bottom') {
|
||||
list?.scrollToItem(logs.length - 1);
|
||||
list?.scrollToRow({
|
||||
index: logs.length - 1,
|
||||
});
|
||||
} else {
|
||||
// uid
|
||||
const index = logs.findIndex((log) => log.uid === event.payload.scrollTo);
|
||||
if (index >= 0) {
|
||||
list?.scrollToItem(index, 'center');
|
||||
list?.scrollToRow({
|
||||
index,
|
||||
align: 'center',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { ChangeEvent, startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
import { type ListImperativeAPI } from 'react-window';
|
||||
|
||||
import { escapeRegex, GrafanaTheme2, shallowCompare } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
@@ -12,7 +12,7 @@ import { useLogListSearchContext } from './LogListSearchContext';
|
||||
import { LogListModel } from './processing';
|
||||
|
||||
interface Props {
|
||||
listRef: VariableSizeList | null;
|
||||
listRef: ListImperativeAPI | null;
|
||||
logs: LogListModel[];
|
||||
}
|
||||
|
||||
@@ -61,7 +61,10 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
|
||||
}
|
||||
const prev = currentResult > 0 ? currentResult - 1 : matches.length - 1;
|
||||
setCurrentResult(prev);
|
||||
listRef?.scrollToItem(logs.indexOf(matches[prev]), 'center');
|
||||
listRef?.scrollToRow({
|
||||
index: logs.indexOf(matches[prev]),
|
||||
align: 'center',
|
||||
});
|
||||
}, [currentResult, listRef, logs, matches]);
|
||||
|
||||
const nextResult = useCallback(() => {
|
||||
@@ -70,7 +73,10 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
|
||||
}
|
||||
const next = currentResult < matches.length - 1 ? currentResult + 1 : 0;
|
||||
setCurrentResult(next);
|
||||
listRef?.scrollToItem(logs.indexOf(matches[next]), 'center');
|
||||
listRef?.scrollToRow({
|
||||
index: logs.indexOf(matches[next]),
|
||||
align: 'center',
|
||||
});
|
||||
}, [currentResult, listRef, logs, matches]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -80,7 +86,10 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
|
||||
}
|
||||
if (!currentResult) {
|
||||
setCurrentResult(0);
|
||||
listRef?.scrollToItem(logs.indexOf(matches[0]), 'center');
|
||||
listRef?.scrollToRow({
|
||||
index: logs.indexOf(matches[0]),
|
||||
align: 'center',
|
||||
});
|
||||
}
|
||||
}, [currentResult, listRef, logs, matches]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user