mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Logs: Show older logs button when infinite scroll is enabled and sort order is descending (#91060)
* LogsNavigation: show older logs button when the order is descending * LogsNavigation: adjust styles for showing only older logs button * Logs Navigation: revert changes * Infinite scroll: add older logs button * Older logs button: show only in explore * chore: add unit test * Formatting * Update public/app/features/logs/components/InfiniteScroll.test.tsx Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * Chore: add missing translation * Chore: move the button a tiny bit --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
This commit is contained in:
parent
f852bf684a
commit
40c6f741c0
@ -938,6 +938,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
rows={logRows}
|
||||
scrollElement={logsContainerRef.current}
|
||||
sortOrder={logsSortOrder}
|
||||
app={CoreApp.Explore}
|
||||
>
|
||||
<LogRows
|
||||
pinnedLogs={pinnedLogs}
|
||||
@ -1053,6 +1054,7 @@ const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean, tableHeight: n
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
position: 'relative',
|
||||
}),
|
||||
logsTable: css({
|
||||
maxHeight: `${tableHeight}px`,
|
||||
|
@ -214,6 +214,7 @@ const getStyles = (theme: GrafanaTheme2, oldestLogsFirst: boolean) => {
|
||||
return {
|
||||
navContainer: css`
|
||||
max-height: ${navContainerHeight};
|
||||
${oldestLogsFirst ? 'width: 58px;' : ''}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
${config.featureToggles.logsInfiniteScrolling
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { LogRowModel, dateTimeForTimeZone } from '@grafana/data';
|
||||
import { CoreApp, LogRowModel, dateTimeForTimeZone } from '@grafana/data';
|
||||
import { convertRawToRange } from '@grafana/data/src/datetime/rangeutil';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { LogsSortOrder } from '@grafana/schema';
|
||||
@ -51,7 +52,13 @@ function ScrollWithWrapper({ children, ...props }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function setup(loadMoreMock: () => void, startPosition: number, rows: LogRowModel[], order: LogsSortOrder) {
|
||||
function setup(
|
||||
loadMoreMock: () => void,
|
||||
startPosition: number,
|
||||
rows: LogRowModel[],
|
||||
order: LogsSortOrder,
|
||||
app?: CoreApp
|
||||
) {
|
||||
const { element, events } = getMockElement(startPosition);
|
||||
|
||||
function scrollTo(position: number) {
|
||||
@ -84,6 +91,7 @@ function setup(loadMoreMock: () => void, startPosition: number, rows: LogRowMode
|
||||
scrollElement={element as unknown as HTMLDivElement}
|
||||
loadMoreLogs={loadMoreMock}
|
||||
topScrollEnabled
|
||||
app={app}
|
||||
>
|
||||
<div data-testid="contents" style={{ height: 100 }} />
|
||||
</InfiniteScroll>
|
||||
@ -267,6 +275,28 @@ describe('InfiniteScroll', () => {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
describe('In Explore', () => {
|
||||
test('Requests older logs from the oldest timestamp', async () => {
|
||||
const loadMoreMock = jest.fn();
|
||||
const rows = createLogRows(
|
||||
absoluteRange.from + 2 * SCROLLING_THRESHOLD,
|
||||
absoluteRange.to - 2 * SCROLLING_THRESHOLD
|
||||
);
|
||||
setup(loadMoreMock, 0, rows, LogsSortOrder.Ascending, CoreApp.Explore);
|
||||
|
||||
expect(await screen.findByTestId('contents')).toBeInTheDocument();
|
||||
|
||||
await screen.findByText('Older logs');
|
||||
|
||||
await userEvent.click(screen.getByText('Older logs'));
|
||||
|
||||
expect(loadMoreMock).toHaveBeenCalledWith({
|
||||
from: absoluteRange.from,
|
||||
to: rows[0].timeEpochMs,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createLogRows(from: number, to: number) {
|
||||
@ -292,6 +322,7 @@ function getMockElement(scrollTop: number) {
|
||||
clientHeight: 40,
|
||||
scrollTop,
|
||||
scrollTo: jest.fn(),
|
||||
scroll: jest.fn(),
|
||||
};
|
||||
|
||||
return { element, events };
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { AbsoluteTimeRange, LogRowModel, TimeRange } from '@grafana/data';
|
||||
import { AbsoluteTimeRange, CoreApp, LogRowModel, TimeRange } from '@grafana/data';
|
||||
import { convertRawToRange, isRelativeTime, isRelativeTimeRange } from '@grafana/data/src/datetime/rangeutil';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { LogsSortOrder, TimeZone } from '@grafana/schema';
|
||||
import { Button, Icon } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { LoadingIndicator } from './LoadingIndicator';
|
||||
|
||||
export type Props = {
|
||||
app?: CoreApp;
|
||||
children: ReactNode;
|
||||
loading: boolean;
|
||||
loadMoreLogs?: (range: AbsoluteTimeRange) => void;
|
||||
@ -21,6 +24,7 @@ export type Props = {
|
||||
};
|
||||
|
||||
export const InfiniteScroll = ({
|
||||
app,
|
||||
children,
|
||||
loading,
|
||||
loadMoreLogs,
|
||||
@ -135,10 +139,37 @@ export const InfiniteScroll = ({
|
||||
const hideTopMessage = sortOrder === LogsSortOrder.Descending && isRelativeTime(range.raw.to);
|
||||
const hideBottomMessage = sortOrder === LogsSortOrder.Ascending && isRelativeTime(range.raw.to);
|
||||
|
||||
const loadOlderLogs = useCallback(() => {
|
||||
//If we are not on the last page, use next page's range
|
||||
reportInteraction('grafana_explore_logs_infinite_pagination_clicked', {
|
||||
pageType: 'olderLogsButton',
|
||||
});
|
||||
const newRange = canScrollTop(getVisibleRange(rows), range, timeZone, sortOrder);
|
||||
if (!newRange) {
|
||||
setUpperOutOfRange(true);
|
||||
return;
|
||||
}
|
||||
setUpperOutOfRange(false);
|
||||
loadMoreLogs?.(newRange);
|
||||
setUpperLoading(true);
|
||||
scrollElement?.scroll({
|
||||
behavior: 'auto',
|
||||
top: 0,
|
||||
});
|
||||
}, [loadMoreLogs, range, rows, scrollElement, sortOrder, timeZone]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{upperLoading && <LoadingIndicator adjective={sortOrder === LogsSortOrder.Descending ? 'newer' : 'older'} />}
|
||||
{!hideTopMessage && upperOutOfRange && outOfRangeMessage}
|
||||
{sortOrder === LogsSortOrder.Ascending && app === CoreApp.Explore && (
|
||||
<Button className={styles.navButton} variant="secondary" onClick={loadOlderLogs} disabled={loading}>
|
||||
<div className={styles.navButtonContent}>
|
||||
<Icon name="angle-up" size="lg" />
|
||||
<Trans i18nKey="logs.infinite-scroll.older-logs">Older logs</Trans>
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
{children}
|
||||
{!hideBottomMessage && lowerOutOfRange && outOfRangeMessage}
|
||||
{lowerLoading && <LoadingIndicator adjective={sortOrder === LogsSortOrder.Descending ? 'older' : 'newer'} />}
|
||||
@ -151,6 +182,26 @@ const styles = {
|
||||
textAlign: 'center',
|
||||
padding: 0.25,
|
||||
}),
|
||||
navButton: css({
|
||||
width: '58px',
|
||||
height: '68px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
lineHeight: '1',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: -3,
|
||||
zIndex: 1,
|
||||
}),
|
||||
navButtonContent: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}),
|
||||
};
|
||||
|
||||
const outOfRangeMessage = (
|
||||
|
@ -1056,6 +1056,11 @@
|
||||
"new-to-question": "New to Grafana?"
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"infinite-scroll": {
|
||||
"older-logs": "Older logs"
|
||||
}
|
||||
},
|
||||
"migrate-to-cloud": {
|
||||
"build-snapshot": {
|
||||
"description": "This tool can migrate some resources from this installation to your cloud stack. To get started, you'll need to create a snapshot of this installation. Creating a snapshot typically takes less than two minutes. The snapshot is stored alongside this Grafana installation.",
|
||||
|
@ -1056,6 +1056,11 @@
|
||||
"new-to-question": "Ńęŵ ŧő Ğřäƒäʼnä?"
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"infinite-scroll": {
|
||||
"older-logs": "Øľđęř ľőģş"
|
||||
}
|
||||
},
|
||||
"migrate-to-cloud": {
|
||||
"build-snapshot": {
|
||||
"description": "Ŧĥįş ŧőőľ čäʼn mįģřäŧę şőmę řęşőūřčęş ƒřőm ŧĥįş įʼnşŧäľľäŧįőʼn ŧő yőūř čľőūđ şŧäčĸ. Ŧő ģęŧ şŧäřŧęđ, yőū'ľľ ʼnęęđ ŧő čřęäŧę ä şʼnäpşĥőŧ őƒ ŧĥįş įʼnşŧäľľäŧįőʼn. Cřęäŧįʼnģ ä şʼnäpşĥőŧ ŧypįčäľľy ŧäĸęş ľęşş ŧĥäʼn ŧŵő mįʼnūŧęş. Ŧĥę şʼnäpşĥőŧ įş şŧőřęđ äľőʼnģşįđę ŧĥįş Ğřäƒäʼnä įʼnşŧäľľäŧįőʼn.",
|
||||
|
Loading…
Reference in New Issue
Block a user