mirror of
https://github.com/grafana/grafana.git
synced 2024-11-21 16:38:03 -06:00
Logs: Allow scroll to reach the bottom of the log list before loading more (#96668)
* Infinite scroll: use timestamps to improve scrolling experience * Add unit tests
This commit is contained in:
parent
3af87261a8
commit
aace94438e
@ -74,11 +74,14 @@ function setup(
|
||||
wheel(-1);
|
||||
}
|
||||
}
|
||||
function wheel(deltaY: number) {
|
||||
function wheel(deltaY: number, timeStamp?: number) {
|
||||
element.scrollTop += deltaY;
|
||||
|
||||
act(() => {
|
||||
const event = new WheelEvent('wheel', { deltaY });
|
||||
if (timeStamp) {
|
||||
jest.spyOn(event, 'timeStamp', 'get').mockReturnValue(timeStamp);
|
||||
}
|
||||
events['wheel'](event);
|
||||
});
|
||||
}
|
||||
@ -164,7 +167,7 @@ describe('InfiniteScroll', () => {
|
||||
|
||||
test.each([
|
||||
['up', -5, 0],
|
||||
['down', 5, 100],
|
||||
['down', 5, 60],
|
||||
])(
|
||||
'Requests more logs when moving the mousewheel %s',
|
||||
async (_: string, deltaY: number, startPosition: number) => {
|
||||
@ -273,6 +276,49 @@ describe('InfiniteScroll', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Chain of events', () => {
|
||||
test('Ingnores chains of events', async () => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const { wheel } = setup(loadMoreMock, 57, rows, order);
|
||||
|
||||
expect(await screen.findByTestId('contents')).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, rows, order);
|
||||
|
||||
expect(await screen.findByTestId('contents')).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, rows, order);
|
||||
|
||||
expect(await screen.findByTestId('contents')).toBeInTheDocument();
|
||||
|
||||
for (let i = 0; i <= 25; i++) {
|
||||
wheel(1, 399 * i + 399);
|
||||
}
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ReactNode, MutableRefObject, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { AbsoluteTimeRange, CoreApp, LogRowModel, TimeRange } from '@grafana/data';
|
||||
import { convertRawToRange, isRelativeTime, isRelativeTimeRange } from '@grafana/data/src/datetime/rangeutil';
|
||||
@ -41,6 +41,8 @@ export const InfiniteScroll = ({
|
||||
const [lowerLoading, setLowerLoading] = useState(false);
|
||||
const rowsRef = useRef<LogRowModel[]>(rows);
|
||||
const lastScroll = useRef<number>(scrollElement?.scrollTop || 0);
|
||||
const lastEvent = useRef<Event | WheelEvent | null>(null);
|
||||
const countRef = useRef(0);
|
||||
|
||||
// Reset messages when range/order/rows change
|
||||
useEffect(() => {
|
||||
@ -84,12 +86,14 @@ export const InfiniteScroll = ({
|
||||
if (!scrollElement || !loadMoreLogs || !rows.length || loading || !config.featureToggles.logsInfiniteScrolling) {
|
||||
return;
|
||||
}
|
||||
event.stopImmediatePropagation();
|
||||
const scrollDirection = shouldLoadMore(event, scrollElement, lastScroll.current);
|
||||
const scrollDirection = shouldLoadMore(event, lastEvent.current, countRef, scrollElement, lastScroll.current);
|
||||
lastEvent.current = event;
|
||||
lastScroll.current = scrollElement.scrollTop;
|
||||
if (scrollDirection === ScrollDirection.NoScroll) {
|
||||
return;
|
||||
} else if (scrollDirection === ScrollDirection.Top && topScrollEnabled) {
|
||||
}
|
||||
event.stopImmediatePropagation();
|
||||
if (scrollDirection === ScrollDirection.Top && topScrollEnabled) {
|
||||
scrollTop();
|
||||
} else if (scrollDirection === ScrollDirection.Bottom) {
|
||||
scrollBottom();
|
||||
@ -215,7 +219,13 @@ enum ScrollDirection {
|
||||
Bottom = 1,
|
||||
NoScroll = 0,
|
||||
}
|
||||
function shouldLoadMore(event: Event | WheelEvent, element: HTMLDivElement, lastScroll: number): ScrollDirection {
|
||||
function shouldLoadMore(
|
||||
event: Event | WheelEvent,
|
||||
lastEvent: Event | WheelEvent | null,
|
||||
countRef: MutableRefObject<number>,
|
||||
element: HTMLDivElement,
|
||||
lastScroll: number
|
||||
): ScrollDirection {
|
||||
// Disable behavior if there is no scroll
|
||||
if (element.scrollHeight <= element.clientHeight) {
|
||||
return ScrollDirection.NoScroll;
|
||||
@ -224,13 +234,54 @@ function shouldLoadMore(event: Event | WheelEvent, element: HTMLDivElement, last
|
||||
if (delta === 0) {
|
||||
return ScrollDirection.NoScroll;
|
||||
}
|
||||
|
||||
const scrollDirection = delta < 0 ? ScrollDirection.Top : ScrollDirection.Bottom;
|
||||
const diff =
|
||||
scrollDirection === ScrollDirection.Top
|
||||
? element.scrollTop
|
||||
: element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
|
||||
return diff <= 1 ? scrollDirection : ScrollDirection.NoScroll;
|
||||
if (diff > 1) {
|
||||
return ScrollDirection.NoScroll;
|
||||
}
|
||||
|
||||
if (lastEvent && shouldIgnoreChainOfEvents(event, lastEvent, countRef)) {
|
||||
return ScrollDirection.NoScroll;
|
||||
}
|
||||
|
||||
return scrollDirection;
|
||||
}
|
||||
|
||||
function shouldIgnoreChainOfEvents(
|
||||
event: Event | WheelEvent,
|
||||
lastEvent: Event | WheelEvent,
|
||||
countRef: MutableRefObject<number>
|
||||
) {
|
||||
const deltaTime = event.timeStamp - lastEvent.timeStamp;
|
||||
// Not a chain of events
|
||||
if (deltaTime > 500) {
|
||||
countRef.current = 0;
|
||||
return false;
|
||||
}
|
||||
countRef.current++;
|
||||
// Likely trackpad
|
||||
if (deltaTime < 100) {
|
||||
// User likely to want more results
|
||||
if (countRef.current >= 180) {
|
||||
countRef.current = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Likely mouse wheel
|
||||
if (deltaTime < 400) {
|
||||
// User likely to want more results
|
||||
if (countRef.current >= 25) {
|
||||
countRef.current = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getVisibleRange(rows: LogRowModel[]) {
|
||||
|
Loading…
Reference in New Issue
Block a user