New Log Context: Scroll and resize improvements (#109143)
* LogList: keep scroll position after scrolling top * Infinite Scroll: add unit tests * Remove log * Remove log * LogList: unify height calculation * LogList: manage list height with resize observer * LogLineDetails: report app with details displayed
This commit is contained in:
@@ -107,11 +107,12 @@ function setup(
|
||||
return { element, events, scrollTo, wheel };
|
||||
}
|
||||
|
||||
const originalState = config.featureToggles.logsInfiniteScrolling;
|
||||
beforeAll(() => {
|
||||
config.featureToggles.logsInfiniteScrolling = true;
|
||||
});
|
||||
afterAll(() => {
|
||||
config.featureToggles.logsInfiniteScrolling = false;
|
||||
config.featureToggles.logsInfiniteScrolling = originalState;
|
||||
});
|
||||
|
||||
describe('InfiniteScroll', () => {
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
|
||||
import { createTheme, dateTimeForTimeZone, rangeUtil } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { LogsSortOrder } from '@grafana/schema';
|
||||
|
||||
import { ScrollDirection, SCROLLING_THRESHOLD } from '../InfiniteScroll';
|
||||
import { createLogLine } from '../mocks/logRow';
|
||||
|
||||
import { InfiniteScroll, InfiniteScrollMode, Props } from './InfiniteScroll';
|
||||
import { LogListModel } from './processing';
|
||||
import { LogLineVirtualization } from './virtualization';
|
||||
|
||||
const defaultTz = 'browser';
|
||||
|
||||
const absoluteRange = {
|
||||
from: 1702578600000,
|
||||
to: 1702578900000,
|
||||
};
|
||||
const defaultRange = rangeUtil.convertRawToRange({
|
||||
from: dateTimeForTimeZone(defaultTz, absoluteRange.from),
|
||||
to: dateTimeForTimeZone(defaultTz, absoluteRange.to),
|
||||
});
|
||||
|
||||
const theme = createTheme();
|
||||
const virtualization = new LogLineVirtualization(theme, 'default');
|
||||
const defaultProps: Omit<Props, 'children' | 'scrollElement'> = {
|
||||
loadMore: jest.fn(),
|
||||
timeRange: defaultRange,
|
||||
logs: [],
|
||||
sortOrder: LogsSortOrder.Descending,
|
||||
timeZone: 'browser',
|
||||
displayedFields: [],
|
||||
handleOverflow: jest.fn(),
|
||||
infiniteScrollMode: 'interval',
|
||||
onClick: jest.fn(),
|
||||
setInitialScrollPosition: jest.fn(),
|
||||
showTime: false,
|
||||
virtualization,
|
||||
wrapLogMessage: false,
|
||||
};
|
||||
|
||||
function setup(
|
||||
loadMoreMock: () => void,
|
||||
startPosition: number,
|
||||
logs: LogListModel[],
|
||||
order: LogsSortOrder,
|
||||
infiniteScrollMode: InfiniteScrollMode = 'interval'
|
||||
) {
|
||||
const { element, events } = getMockElement(startPosition);
|
||||
|
||||
function scrollTo(position: number, timeStamp?: number) {
|
||||
element.scrollTop = position;
|
||||
|
||||
act(() => {
|
||||
const event = new Event('scroll');
|
||||
if (timeStamp) {
|
||||
jest.spyOn(event, 'timeStamp', 'get').mockReturnValue(timeStamp);
|
||||
}
|
||||
events['scroll'](event);
|
||||
});
|
||||
|
||||
// When scrolling top, we wait for the user to reach the top, and then for a new scrolling event
|
||||
// in the same direction before triggering a new query.
|
||||
if (position === 0) {
|
||||
wheel(-1);
|
||||
}
|
||||
}
|
||||
function wheel(deltaY: number, timeStamp?: number) {
|
||||
element.scrollTop += deltaY;
|
||||
if (element.scrollTop < 0) {
|
||||
element.scrollTop = 0;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
const event = new WheelEvent('wheel', { deltaY });
|
||||
if (timeStamp) {
|
||||
jest.spyOn(event, 'timeStamp', 'get').mockReturnValue(timeStamp);
|
||||
}
|
||||
events['wheel'](event);
|
||||
});
|
||||
}
|
||||
|
||||
render(
|
||||
<InfiniteScroll
|
||||
{...defaultProps}
|
||||
sortOrder={order}
|
||||
logs={logs}
|
||||
scrollElement={element as unknown as HTMLDivElement}
|
||||
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>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
);
|
||||
|
||||
return { element, events, scrollTo, wheel };
|
||||
}
|
||||
|
||||
const originalState = config.featureToggles.logsInfiniteScrolling;
|
||||
beforeAll(() => {
|
||||
config.featureToggles.logsInfiniteScrolling = true;
|
||||
});
|
||||
afterAll(() => {
|
||||
config.featureToggles.logsInfiniteScrolling = originalState;
|
||||
});
|
||||
|
||||
describe('InfiniteScroll', () => {
|
||||
describe.each([LogsSortOrder.Descending, LogsSortOrder.Ascending])(
|
||||
'When the sort order is descending',
|
||||
(order: LogsSortOrder) => {
|
||||
let logs: LogListModel[];
|
||||
beforeEach(() => {
|
||||
logs = createLogs(absoluteRange.from + 2 * SCROLLING_THRESHOLD, absoluteRange.to - 2 * SCROLLING_THRESHOLD);
|
||||
});
|
||||
|
||||
test.each([
|
||||
['top', 10, 0],
|
||||
['bottom', 50, 60],
|
||||
])(
|
||||
'Requests more logs when scrolling %s',
|
||||
async (direction: string, startPosition: number, endPosition: number) => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { scrollTo } = setup(loadMoreMock, startPosition, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
scrollTo(endPosition - 1, 1);
|
||||
scrollTo(endPosition, 600);
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalled();
|
||||
expect(await screen.findByTestId('Spinner')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
['up', -5, 0],
|
||||
['down', 5, 60],
|
||||
])(
|
||||
'Requests more logs when moving the mousewheel %s',
|
||||
async (_: string, deltaY: number, startPosition: number) => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { wheel } = setup(loadMoreMock, startPosition, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
wheel(deltaY, 1);
|
||||
wheel(deltaY, 600);
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalled();
|
||||
expect(await screen.findByTestId('Spinner')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
test('Does not request more logs when there is no scroll', async () => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { scrollTo, element } = setup(loadMoreMock, 0, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
element.clientHeight = 40;
|
||||
element.scrollHeight = element.clientHeight;
|
||||
|
||||
scrollTo(39, 1);
|
||||
scrollTo(40, 600);
|
||||
|
||||
expect(loadMoreMock).not.toHaveBeenCalled();
|
||||
expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Requests newer logs from the most recent timestamp', async () => {
|
||||
const startPosition = order === LogsSortOrder.Descending ? 10 : 50; // Scroll top
|
||||
const endPosition = order === LogsSortOrder.Descending ? 0 : 60; // Scroll bottom
|
||||
|
||||
const loadMoreMock = jest.fn();
|
||||
const { scrollTo } = setup(loadMoreMock, startPosition, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
scrollTo(endPosition - 1, 1);
|
||||
scrollTo(endPosition, 600);
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalledWith(
|
||||
{
|
||||
from: logs[logs.length - 1].timeEpochMs,
|
||||
to: absoluteRange.to,
|
||||
},
|
||||
order === LogsSortOrder.Descending ? -1 : 1
|
||||
);
|
||||
});
|
||||
|
||||
test('Requests older logs from the oldest timestamp', async () => {
|
||||
const startPosition = order === LogsSortOrder.Ascending ? 10 : 50; // Scroll top
|
||||
const endPosition = order === LogsSortOrder.Ascending ? 0 : 60; // Scroll bottom
|
||||
|
||||
const loadMoreMock = jest.fn();
|
||||
const { scrollTo } = setup(loadMoreMock, startPosition, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
scrollTo(endPosition - 1, 1);
|
||||
scrollTo(endPosition, 600);
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalledWith(
|
||||
{
|
||||
from: absoluteRange.from,
|
||||
to: logs[0].timeEpochMs,
|
||||
},
|
||||
order === LogsSortOrder.Ascending ? -1 : 1
|
||||
);
|
||||
});
|
||||
|
||||
describe('With absolute range matching visible range', () => {
|
||||
test('It does not request more when scrolling bottom', async () => {
|
||||
// Visible range matches the current range
|
||||
const logs = createLogs(absoluteRange.from, absoluteRange.to);
|
||||
const loadMoreMock = jest.fn();
|
||||
const { scrollTo } = setup(loadMoreMock, 50, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
scrollTo(59, 1);
|
||||
scrollTo(60, 600);
|
||||
|
||||
expect(loadMoreMock).not.toHaveBeenCalled();
|
||||
expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument();
|
||||
expect(await screen.findByText('End of the selected time range.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('With relative range matching visible range', () => {
|
||||
test('It does not request more when scrolling bottom', async () => {
|
||||
// Visible range matches the current range
|
||||
const logs = createLogs(absoluteRange.from, absoluteRange.to);
|
||||
const loadMoreMock = jest.fn();
|
||||
const { scrollTo } = setup(loadMoreMock, 50, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
scrollTo(59, 1);
|
||||
scrollTo(60, 600);
|
||||
|
||||
expect(loadMoreMock).not.toHaveBeenCalled();
|
||||
expect(screen.queryByTestId('Spinner')).not.toBeInTheDocument();
|
||||
expect(await screen.findByText('End of the selected time range.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chain of events', () => {
|
||||
test('Ingnores chains of events', async () => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { wheel } = setup(loadMoreMock, 57, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
const timeStamps = [1, 2, 3, 4];
|
||||
timeStamps.forEach((timeStamp) => {
|
||||
wheel(1, timeStamp);
|
||||
});
|
||||
|
||||
expect(loadMoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Detects when chain of events ends', async () => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { wheel } = setup(loadMoreMock, 57, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
const timeStamps = [1, 2, 3, 600, 1];
|
||||
timeStamps.forEach((timeStamp) => {
|
||||
wheel(1, timeStamp);
|
||||
});
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Detects when the user wants to scroll', async () => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { wheel } = setup(loadMoreMock, 57, logs, order);
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
for (let i = 0; i <= 25; i++) {
|
||||
wheel(1, 399 * i + 399);
|
||||
}
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('With scroll mode unlimited', () => {
|
||||
test('Allows infinite scroll in the top direction', async () => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { wheel } = setup(loadMoreMock, 0, logs, order, 'unlimited');
|
||||
|
||||
expect(await screen.findByText('log line 1')).toBeInTheDocument();
|
||||
|
||||
wheel(-5, 1);
|
||||
wheel(-5, 600);
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalledWith(expect.anything(), ScrollDirection.Top);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function createLogs(from: number, to: number) {
|
||||
const rows = [
|
||||
createLogLine({ entry: 'log line 1', uid: 'log-1' }),
|
||||
createLogLine({ entry: 'log line 2', uid: 'log-22' }),
|
||||
];
|
||||
// Time field
|
||||
rows[0].dataFrame.fields[0].values = [from, to];
|
||||
rows[0].timeEpochMs = from;
|
||||
rows[1].dataFrame.fields[0].values = [from, to];
|
||||
rows[1].timeEpochMs = to;
|
||||
return rows;
|
||||
}
|
||||
|
||||
// JSDOM doesn't support layout, so we will mock the expected attribute values for the test cases.
|
||||
function getMockElement(scrollTop: number) {
|
||||
const events: Record<string, (e: Event | WheelEvent) => void> = {};
|
||||
const element = {
|
||||
addEventListener: (event: string, callback: (e: Event | WheelEvent) => void) => {
|
||||
events[event] = callback;
|
||||
},
|
||||
removeEventListener: jest.fn(),
|
||||
stopImmediatePropagation: jest.fn(),
|
||||
scrollHeight: 100,
|
||||
clientHeight: 40,
|
||||
scrollTop,
|
||||
scrollTo: jest.fn(),
|
||||
scroll: jest.fn(),
|
||||
};
|
||||
|
||||
return { element, events };
|
||||
}
|
||||
@@ -21,7 +21,7 @@ interface ChildrenProps {
|
||||
Renderer: (props: ListChildComponentProps) => ReactNode;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
export interface Props {
|
||||
children: (props: ChildrenProps) => ReactNode;
|
||||
displayedFields: string[];
|
||||
handleOverflow: (index: number, id: string, height?: number) => void;
|
||||
@@ -30,7 +30,7 @@ interface Props {
|
||||
logs: LogListModel[];
|
||||
onClick: (e: MouseEvent<HTMLElement>, log: LogListModel) => void;
|
||||
scrollElement: HTMLDivElement | null;
|
||||
setInitialScrollPosition: () => void;
|
||||
setInitialScrollPosition: (log?: LogListModel) => void;
|
||||
showTime: boolean;
|
||||
sortOrder: LogsSortOrder;
|
||||
timeRange: TimeRange;
|
||||
@@ -72,6 +72,7 @@ export const InfiniteScroll = ({
|
||||
const lastLogOfPage = useRef<string[]>([]);
|
||||
const styles = useStyles2(getStyles, virtualization);
|
||||
const resetStateTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const scrollToLogLineRef = useRef<LogListModel | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
// Logs have not changed, ignore effect
|
||||
@@ -84,6 +85,9 @@ export const InfiniteScroll = ({
|
||||
setInfiniteLoaderState(
|
||||
logs.length === prevLogs.length && infiniteScrollMode === 'interval' ? 'out-of-bounds' : 'idle'
|
||||
);
|
||||
if (scrollToLogLineRef.current) {
|
||||
setAutoScroll(true);
|
||||
}
|
||||
} else {
|
||||
lastLogOfPage.current = [];
|
||||
setAutoScroll(true);
|
||||
@@ -98,7 +102,8 @@ export const InfiniteScroll = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (autoScroll) {
|
||||
setInitialScrollPosition();
|
||||
setInitialScrollPosition(scrollToLogLineRef.current);
|
||||
scrollToLogLineRef.current = undefined;
|
||||
setAutoScroll(false);
|
||||
}
|
||||
}, [autoScroll, setInitialScrollPosition]);
|
||||
@@ -116,6 +121,7 @@ export const InfiniteScroll = ({
|
||||
if (scrollDirection === ScrollDirection.Bottom) {
|
||||
lastLogOfPage.current.push(logs[logs.length - 1].uid);
|
||||
} else {
|
||||
scrollToLogLineRef.current = logs[0];
|
||||
lastLogOfPage.current.push(logs[0].uid);
|
||||
}
|
||||
setInfiniteLoaderState('loading');
|
||||
|
||||
@@ -65,7 +65,7 @@ export const LogLineDetails = memo(({ containerElement, focusLogLine, logs, onRe
|
||||
LogLineDetails.displayName = 'LogLineDetails';
|
||||
|
||||
const LogLineDetailsTabs = memo(({ focusLogLine, logs }: Pick<Props, 'focusLogLine' | 'logs'>) => {
|
||||
const { closeDetails, noInteractions, showDetails, toggleDetails } = useLogListContext();
|
||||
const { app, closeDetails, noInteractions, showDetails, toggleDetails } = useLogListContext();
|
||||
const [currentLog, setCurrentLog] = useState(showDetails[0]);
|
||||
const previousShowDetails = usePrevious(showDetails);
|
||||
const styles = useStyles2(getStyles, 'sidebar');
|
||||
@@ -75,6 +75,7 @@ const LogLineDetailsTabs = memo(({ focusLogLine, logs }: Pick<Props, 'focusLogLi
|
||||
if (!noInteractions) {
|
||||
reportInteraction('logs_log_line_details_displayed', {
|
||||
mode: 'sidebar',
|
||||
app,
|
||||
});
|
||||
}
|
||||
// Once
|
||||
@@ -133,7 +134,7 @@ export interface InlineLogLineDetailsProps {
|
||||
}
|
||||
|
||||
export const InlineLogLineDetails = memo(({ logs, log }: InlineLogLineDetailsProps) => {
|
||||
const { noInteractions } = useLogListContext();
|
||||
const { app, noInteractions } = useLogListContext();
|
||||
const styles = useStyles2(getStyles, 'inline');
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -141,9 +142,10 @@ export const InlineLogLineDetails = memo(({ logs, log }: InlineLogLineDetailsPro
|
||||
if (!noInteractions) {
|
||||
reportInteraction('logs_log_line_details_displayed', {
|
||||
mode: 'inline',
|
||||
app,
|
||||
});
|
||||
}
|
||||
}, [noInteractions]);
|
||||
}, [app, noInteractions]);
|
||||
|
||||
const saveScroll = useCallback(() => {
|
||||
saveDetailsScrollPosition(log, scrollRef.current?.scrollTop ?? 0);
|
||||
|
||||
@@ -245,11 +245,7 @@ const LogListComponent = ({
|
||||
wrapLogMessage,
|
||||
} = useLogListContext();
|
||||
const [processedLogs, setProcessedLogs] = useState<LogListModel[]>([]);
|
||||
const [listHeight, setListHeight] = useState(
|
||||
app === CoreApp.Explore
|
||||
? Math.max(window.innerHeight * 0.8, containerElement.clientHeight)
|
||||
: containerElement.clientHeight
|
||||
);
|
||||
const [listHeight, setListHeight] = useState(getListHeight(containerElement, app));
|
||||
const theme = useTheme2();
|
||||
const listRef = useRef<VariableSizeList | null>(null);
|
||||
const widthRef = useRef(containerElement.clientWidth);
|
||||
@@ -327,28 +323,21 @@ const LogListComponent = ({
|
||||
listRef.current?.resetAfterIndex(0);
|
||||
}, [wrapLogMessage, showDetails, displayedFields, dedupStrategy]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = debounce(() => {
|
||||
setListHeight(
|
||||
(app === CoreApp.Explore
|
||||
? Math.max(window.innerHeight * 0.8, containerElement.clientHeight)
|
||||
: containerElement.clientHeight) - (searchVisible ? LOG_LIST_SEARCH_HEIGHT : 0)
|
||||
);
|
||||
}, 50);
|
||||
window.addEventListener('resize', handleResize);
|
||||
handleResize();
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, [app, containerElement.clientHeight, searchVisible]);
|
||||
useLayoutEffect(() => {
|
||||
if (widthRef.current !== widthContainer.clientWidth) {
|
||||
widthRef.current = widthContainer.clientWidth;
|
||||
debouncedResetAfterIndex(0);
|
||||
}
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (widthRef.current === widthContainer.clientWidth) {
|
||||
return;
|
||||
}
|
||||
widthRef.current = widthContainer.clientWidth;
|
||||
debouncedResetAfterIndex(0);
|
||||
});
|
||||
const handleResize = debounce(() => {
|
||||
setListHeight(getListHeight(containerElement, app, searchVisible));
|
||||
}, 50);
|
||||
const observer = new ResizeObserver(() => handleResize());
|
||||
observer.observe(containerElement);
|
||||
return () => observer.disconnect();
|
||||
}, [app, containerElement, searchVisible]);
|
||||
|
||||
const overflowIndexRef = useRef(Infinity);
|
||||
const handleOverflow = useCallback(
|
||||
@@ -365,16 +354,20 @@ const LogListComponent = ({
|
||||
[debouncedResetAfterIndex, virtualization, widthContainer]
|
||||
);
|
||||
|
||||
const handleScrollPosition = useCallback(() => {
|
||||
if (permalinkedLogId) {
|
||||
const index = processedLogs.findIndex((log) => log.uid === permalinkedLogId);
|
||||
if (index >= 0) {
|
||||
listRef.current?.scrollToItem(index, 'start');
|
||||
return;
|
||||
const handleScrollPosition = useCallback(
|
||||
(log?: LogListModel) => {
|
||||
const scrollToUID = log ? log.uid : permalinkedLogId;
|
||||
if (scrollToUID) {
|
||||
const index = processedLogs.findIndex((log) => log.uid === scrollToUID);
|
||||
if (index >= 0) {
|
||||
listRef.current?.scrollToItem(index, 'start');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
listRef.current?.scrollToItem(initialScrollPosition === 'top' ? 0 : processedLogs.length - 1);
|
||||
}, [initialScrollPosition, permalinkedLogId, processedLogs]);
|
||||
listRef.current?.scrollToItem(initialScrollPosition === 'top' ? 0 : processedLogs.length - 1);
|
||||
},
|
||||
[initialScrollPosition, permalinkedLogId, processedLogs]
|
||||
);
|
||||
|
||||
if (!containerElement || listHeight == null) {
|
||||
// Wait for container to be rendered
|
||||
@@ -552,3 +545,11 @@ function handleScrollToEvent(event: ScrollToLogsEvent, logs: LogListModel[], lis
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getListHeight(containerElement: HTMLDivElement, app: CoreApp, searchVisible = false) {
|
||||
return (
|
||||
(app === CoreApp.Explore
|
||||
? Math.max(window.innerHeight * 0.8, containerElement.clientHeight)
|
||||
: containerElement.clientHeight) - (searchVisible ? LOG_LIST_SEARCH_HEIGHT : 0)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user