From d4098536830043e3e145e7f27f5714bc2e953b0f Mon Sep 17 00:00:00 2001 From: Kristina Date: Fri, 24 Jan 2025 13:52:04 -0600 Subject: [PATCH] StatusHistory: Add pagination option (#99517) * first pass * Add to docs * Move pagination hook and styles to a shared util * Update docs/sources/panels-visualizations/visualizations/status-history/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --------- Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --- .../visualizations/status-history/index.md | 4 + .../x/StatusHistoryPanelCfg_types.gen.ts | 5 + .../state-timeline/StateTimelinePanel.tsx | 74 +------- .../plugins/panel/state-timeline/utils.tsx | 75 ++++++++ .../status-history/StatusHistoryPanel.tsx | 176 +++++++++--------- .../plugins/panel/status-history/module.tsx | 9 + .../plugins/panel/status-history/panelcfg.cue | 2 + .../panel/status-history/panelcfg.gen.ts | 5 + 8 files changed, 197 insertions(+), 153 deletions(-) create mode 100644 public/app/plugins/panel/state-timeline/utils.tsx diff --git a/docs/sources/panels-visualizations/visualizations/status-history/index.md b/docs/sources/panels-visualizations/visualizations/status-history/index.md index 3ca5df864e6..357461f3382 100644 --- a/docs/sources/panels-visualizations/visualizations/status-history/index.md +++ b/docs/sources/panels-visualizations/visualizations/status-history/index.md @@ -111,6 +111,10 @@ Controls whether values are rendered inside the value boxes. Auto will render va Controls the height of boxes. 1 = maximum space and 0 = minimum space. +### Page size (enable pagination) + +The **Page size** option lets you paginate the status history visualization to limit how many series are visible at once. This is useful when you have many series. + ### Column width Controls the width of boxes. 1 = maximum space and 0 = minimum space. diff --git a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts index 1efeeb3dec4..a6138868be9 100644 --- a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts @@ -17,6 +17,10 @@ export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui * Controls the column width */ colWidth?: number; + /** + * Enables pagination when > 0 + */ + perPage?: number; /** * Set the height of the rows */ @@ -29,6 +33,7 @@ export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui export const defaultOptions: Partial = { colWidth: 0.9, + perPage: 20, rowHeight: 0.9, showValue: ui.VisibilityMode.Auto, }; diff --git a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx index 4c57d757a05..9524beba351 100644 --- a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx +++ b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx @@ -1,12 +1,9 @@ -import { css } from '@emotion/css'; import { useMemo, useState } from 'react'; -import { useMeasure } from 'react-use'; -import { DashboardCursorSync, DataFrame, PanelProps } from '@grafana/data'; +import { DashboardCursorSync, PanelProps } from '@grafana/data'; import { AxisPlacement, EventBusPlugin, - Pagination, TooltipDisplayMode, TooltipPlugin2, usePanelContext, @@ -15,7 +12,6 @@ import { import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2'; import { TimelineChart } from 'app/core/components/TimelineChart/TimelineChart'; import { - makeFramePerSeries, prepareTimelineFields, prepareTimelineLegendItems, TimelineMode, @@ -26,73 +22,11 @@ import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin'; import { getTimezones } from '../timeseries/utils'; import { StateTimelineTooltip2 } from './StateTimelineTooltip2'; -import { Options, defaultOptions } from './panelcfg.gen'; +import { Options } from './panelcfg.gen'; +import { containerStyles, usePagination } from './utils'; interface TimelinePanelProps extends PanelProps {} -const styles = { - container: css({ - display: 'flex', - flexDirection: 'column', - }), - paginationContainer: css({ - display: 'flex', - justifyContent: 'center', - width: '100%', - }), - paginationElement: css({ - marginTop: '8px', - }), -}; - -function usePagination(frames?: DataFrame[], perPage?: number) { - const [currentPage, setCurrentPage] = useState(1); - - const [paginationWrapperRef, { height: paginationHeight, width: paginationWidth }] = useMeasure(); - - const pagedFrames = useMemo( - () => (!perPage || frames == null ? frames : makeFramePerSeries(frames)), - [frames, perPage] - ); - - if (!perPage || pagedFrames == null) { - return { - paginatedFrames: pagedFrames, - paginationRev: 'disabled', - paginationElement: undefined, - paginationHeight: 0, - }; - } - - 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); - const pageOffset = (currentPageCapped - 1) * perPage; - const currentPageFrames = pagedFrames.slice(pageOffset, pageOffset + perPage); - - // `paginationRev` needs to change value whenever any of the pagination settings changes. - // It's used in to trigger a reconfiguration of the underlying graphs (which is cached, - // hence an explicit nudge is required). - const paginationRev = `${currentPageCapped}/${perPage}`; - - const showSmallVersion = paginationWidth < 550; - const paginationElement = ( -
- -
- ); - - return { paginatedFrames: currentPageFrames, paginationRev, paginationElement, paginationHeight }; -} - /** * @alpha */ @@ -141,7 +75,7 @@ export const StateTimelinePanel = ({ const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations()); return ( -
+
(); + + const pagedFrames = useMemo( + () => (!perPage || frames == null ? frames : makeFramePerSeries(frames)), + [frames, perPage] + ); + + if (!perPage || pagedFrames == null) { + return { + paginatedFrames: pagedFrames, + paginationRev: 'disabled', + paginationElement: undefined, + paginationHeight: 0, + }; + } + + 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); + const pageOffset = (currentPageCapped - 1) * perPage; + const currentPageFrames = pagedFrames.slice(pageOffset, pageOffset + perPage); + + // `paginationRev` needs to change value whenever any of the pagination settings changes. + // It's used in to trigger a reconfiguration of the underlying graphs (which is cached, + // hence an explicit nudge is required). + const paginationRev = `${currentPageCapped}/${perPage}`; + + const showSmallVersion = paginationWidth < 550; + const paginationElement = ( +
+ +
+ ); + + return { paginatedFrames: currentPageFrames, paginationRev, paginationElement, paginationHeight }; +} diff --git a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx index 237f1469723..8ed1a266a27 100644 --- a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx +++ b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx @@ -18,6 +18,7 @@ import { } from 'app/core/components/TimelineChart/utils'; import { StateTimelineTooltip2 } from '../state-timeline/StateTimelineTooltip2'; +import { containerStyles, usePagination } from '../state-timeline/utils'; import { AnnotationsPlugin2 } from '../timeseries/plugins/AnnotationsPlugin2'; import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin'; import { getTimezones } from '../timeseries/utils'; @@ -53,14 +54,19 @@ export const StatusHistoryPanel = ({ [data.series, timeRange, theme] ); + const { paginatedFrames, paginationRev, paginationElement, paginationHeight } = usePagination( + frames, + options.perPage + ); + const legendItems = useMemo( - () => prepareTimelineLegendItems(frames, options.legend, theme), - [frames, options.legend, theme] + () => prepareTimelineLegendItems(paginatedFrames, options.legend, theme), + [paginatedFrames, options.legend, theme] ); const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]); - if (!frames || warn) { + if (!paginatedFrames || warn) { return (

{warn ?? 'No data found in response'}

@@ -69,99 +75,103 @@ export const StatusHistoryPanel = ({ } // Status grid requires some space between values - if (frames[0].length > width / 2) { + if (paginatedFrames[0].length > width / 2) { return (

Too many points to visualize properly.
- Update the query to return fewer points.
({frames[0].length} points received) + Update the query to return fewer points.
({paginatedFrames[0].length} points received)

); } return ( - - {(builder, alignedFrame) => { - return ( - <> - {cursorSync !== DashboardCursorSync.Off && ( - - )} - {options.tooltip.mode !== TooltipDisplayMode.None && ( - - alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] - } - render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => { - if (enableAnnotationCreation && timeRange2 != null) { - setNewAnnotationRange(timeRange2); - dismiss(); - return; +
+ + {(builder, alignedFrame) => { + return ( + <> + {cursorSync !== DashboardCursorSync.Off && ( + + )} + {options.tooltip.mode !== TooltipDisplayMode.None && ( + + alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] + } + render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => { + if (enableAnnotationCreation && timeRange2 != null) { + setNewAnnotationRange(timeRange2); + dismiss(); + return; + } - const annotate = () => { - let xVal = u.posToVal(u.cursor.left!, 'x'); + const annotate = () => { + let xVal = u.posToVal(u.cursor.left!, 'x'); - setNewAnnotationRange({ from: xVal, to: xVal }); - dismiss(); - }; + setNewAnnotationRange({ from: xVal, to: xVal }); + dismiss(); + }; - return ( - - ); - }} - maxWidth={options.tooltip.maxWidth} - /> - )} - {alignedFrame.fields[0].config.custom?.axisPlacement !== AxisPlacement.Hidden && ( - - )} - - - ); - }} - + return ( + + ); + }} + maxWidth={options.tooltip.maxWidth} + /> + )} + {alignedFrame.fields[0].config.custom?.axisPlacement !== AxisPlacement.Hidden && ( + + )} + + + ); + }} + + {paginationElement} +
); }; diff --git a/public/app/plugins/panel/status-history/module.tsx b/public/app/plugins/panel/status-history/module.tsx index 58ff47be783..722f8936199 100644 --- a/public/app/plugins/panel/status-history/module.tsx +++ b/public/app/plugins/panel/status-history/module.tsx @@ -82,6 +82,15 @@ export const plugin = new PanelPlugin(StatusHistoryPanel) max: 1, step: 0.01, }, + }) + .addNumberInput({ + path: 'perPage', + name: 'Page size (enable pagination)', + settings: { + min: 1, + step: 1, + integer: true, + }, }); commonOptionsBuilder.addLegendOptions(builder, false); diff --git a/public/app/plugins/panel/status-history/panelcfg.cue b/public/app/plugins/panel/status-history/panelcfg.cue index 55a96b501d2..f8bfc020da0 100644 --- a/public/app/plugins/panel/status-history/panelcfg.cue +++ b/public/app/plugins/panel/status-history/panelcfg.cue @@ -35,6 +35,8 @@ composableKinds: PanelCfg: { showValue: ui.VisibilityMode & (*"auto" | _) //Controls the column width colWidth?: float & <=1 | *0.9 + //Enables pagination when > 0 + perPage?: number & >=1 | *20 } @cuetsy(kind="interface") FieldConfig: { ui.AxisConfig diff --git a/public/app/plugins/panel/status-history/panelcfg.gen.ts b/public/app/plugins/panel/status-history/panelcfg.gen.ts index fac1c7e19df..366f69f05fd 100644 --- a/public/app/plugins/panel/status-history/panelcfg.gen.ts +++ b/public/app/plugins/panel/status-history/panelcfg.gen.ts @@ -15,6 +15,10 @@ export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui * Controls the column width */ colWidth?: number; + /** + * Enables pagination when > 0 + */ + perPage?: number; /** * Set the height of the rows */ @@ -27,6 +31,7 @@ export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui export const defaultOptions: Partial = { colWidth: 0.9, + perPage: 20, rowHeight: 0.9, showValue: ui.VisibilityMode.Auto, };