StateTimeline: Add unit tests for usePagination hook, reorganize/rename some files (#112532)

* StateTimeline: Add unit tests for usePagination hook

* reorganize styles

* StateTimelineTooltip2 -> StateTimelineTooltip
This commit is contained in:
Paul Marbach
2025-10-21 13:01:08 -04:00
committed by GitHub
parent 545b7bf8ff
commit 00652f1f96
6 changed files with 84 additions and 25 deletions
@@ -22,9 +22,10 @@ import { AnnotationsPlugin2 } from '../timeseries/plugins/AnnotationsPlugin2';
import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin';
import { getTimezones } from '../timeseries/utils';
import { StateTimelineTooltip2 } from './StateTimelineTooltip2';
import { StateTimelineTooltip } from './StateTimelineTooltip';
import { usePagination } from './hooks';
import { Options } from './panelcfg.gen';
import { containerStyles, usePagination } from './utils';
import { containerStyles } from './styles';
interface TimelinePanelProps extends PanelProps<Options> {}
@@ -78,7 +79,7 @@ export const StateTimelinePanel = ({
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
return (
<div className={containerStyles.container}>
<div className={containerStyles}>
<TimelineChart
theme={theme}
frames={paginatedFrames}
@@ -128,7 +129,7 @@ export const StateTimelinePanel = ({
};
return (
<StateTimelineTooltip2
<StateTimelineTooltip
series={alignedFrame}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
@@ -17,12 +17,12 @@ import { getFieldActions } from '../status-history/utils';
import { TimeSeriesTooltipProps } from '../timeseries/TimeSeriesTooltip';
import { isTooltipScrollable } from '../timeseries/utils';
interface StateTimelineTooltip2Props extends TimeSeriesTooltipProps {
interface StateTimelineTooltipProps extends TimeSeriesTooltipProps {
timeRange: TimeRange;
withDuration: boolean;
}
export const StateTimelineTooltip2 = ({
export const StateTimelineTooltip = ({
series,
dataIdxs,
seriesIdx,
@@ -35,7 +35,7 @@ export const StateTimelineTooltip2 = ({
maxHeight,
replaceVariables,
dataLinks,
}: StateTimelineTooltip2Props) => {
}: StateTimelineTooltipProps) => {
const xField = series.fields[0];
const dataIdx = seriesIdx != null ? dataIdxs[seriesIdx] : dataIdxs.find((idx) => idx != null);
@@ -0,0 +1,62 @@
import { render, renderHook, screen } from '@testing-library/react';
import { createDataFrame, FieldType } from '@grafana/data';
import { usePagination } from './hooks';
describe('StateTimelinePanel hooks', () => {
describe('usePagination', () => {
describe('empty value', () => {
it('returns the empty value if perPage is not set', () => {
const { result } = renderHook(() => usePagination([]));
expect(result.current).toEqual({
paginatedFrames: [],
paginationRev: 'disabled',
paginationElement: undefined,
paginationHeight: 0,
});
});
it('returns the empty value if frames are not set', () => {
const { result } = renderHook(() => usePagination(undefined, 5));
expect(result.current).toEqual({
paginatedFrames: undefined,
paginationRev: 'disabled',
paginationElement: undefined,
paginationHeight: 0,
});
});
});
it('returns the React element to be rendered for pagination', () => {
const frames = createDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
{ name: 'value-A', type: FieldType.number, values: [4, 5, 6] },
{ name: 'value-B', type: FieldType.number, values: [4, 5, 6] },
{ name: 'value-C', type: FieldType.number, values: [4, 5, 6] },
],
});
const { result } = renderHook(() => usePagination([frames], 2));
expect(result.current.paginatedFrames?.length).toBe(2);
});
const frame = createDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000] },
{ name: 'value-A', type: FieldType.number, values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] },
{ name: 'value-B', type: FieldType.number, values: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20] },
{ name: 'value-C', type: FieldType.number, values: [21, 22, 23, 24, 25, 26, 27, 28, 29, 30] },
],
});
const { result } = renderHook(() => usePagination([frame], 2));
render(result.current.paginationElement);
expect(screen.getByText('1')).toBeInTheDocument(); // current page
expect(screen.getByText('2')).toBeInTheDocument(); // last page
expect(screen.getByLabelText('next page')).not.toBeDisabled();
});
});
@@ -6,16 +6,7 @@ import { DataFrame } from '@grafana/data';
import { Pagination } from '@grafana/ui';
import { makeFramePerSeries } from 'app/core/components/TimelineChart/utils';
import { defaultOptions } from './panelcfg.gen';
export const containerStyles = {
container: css({
display: 'flex',
flexDirection: 'column',
}),
};
const styles = {
const paginationStyles = {
paginationContainer: css({
display: 'flex',
justifyContent: 'center',
@@ -45,8 +36,6 @@ export function usePagination(frames?: DataFrame[], perPage?: number) {
};
}
perPage ||= defaultOptions.perPage!;
const numberOfPages = Math.ceil(pagedFrames.length / perPage);
// `perPage` changing might lead to temporarily too large values of `currentPage`.
const currentPageCapped = Math.min(currentPage, numberOfPages);
@@ -60,9 +49,9 @@ export function usePagination(frames?: DataFrame[], perPage?: number) {
const showSmallVersion = paginationWidth < 550;
const paginationElement = (
<div className={styles.paginationContainer} ref={paginationWrapperRef}>
<div className={paginationStyles.paginationContainer} ref={paginationWrapperRef}>
<Pagination
className={styles.paginationElement}
className={paginationStyles.paginationElement}
currentPage={currentPageCapped}
numberOfPages={numberOfPages}
showSmallVersion={showSmallVersion}
@@ -0,0 +1,6 @@
import { css } from '@emotion/css';
export const containerStyles = css({
display: 'flex',
flexDirection: 'column',
});
@@ -19,8 +19,9 @@ import {
TimelineMode,
} from 'app/core/components/TimelineChart/utils';
import { StateTimelineTooltip2 } from '../state-timeline/StateTimelineTooltip2';
import { containerStyles, usePagination } from '../state-timeline/utils';
import { StateTimelineTooltip } from '../state-timeline/StateTimelineTooltip';
import { usePagination } from '../state-timeline/hooks';
import { containerStyles } from '../state-timeline/styles';
import { AnnotationsPlugin2 } from '../timeseries/plugins/AnnotationsPlugin2';
import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin';
import { getTimezones } from '../timeseries/utils';
@@ -91,7 +92,7 @@ export const StatusHistoryPanel = ({
}
return (
<div className={containerStyles.container}>
<div className={containerStyles}>
<TimelineChart
theme={theme}
frames={paginatedFrames}
@@ -141,7 +142,7 @@ export const StatusHistoryPanel = ({
};
return (
<StateTimelineTooltip2
<StateTimelineTooltip
series={alignedFrame}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}