Files
grafana/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx
Kevin Putera 01fc31069f StateTimeline: Add pagination support (#89586)
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
2024-08-08 21:44:17 -05:00

225 lines
7.4 KiB
TypeScript

import { css } from '@emotion/css';
import { useMemo, useState } from 'react';
import { useMeasure } from 'react-use';
import { DashboardCursorSync, DataFrame, PanelProps } from '@grafana/data';
import {
EventBusPlugin,
Pagination,
TooltipDisplayMode,
TooltipPlugin2,
usePanelContext,
useTheme2,
} from '@grafana/ui';
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
import { TimelineChart } from 'app/core/components/TimelineChart/TimelineChart';
import {
makeFramePerSeries,
prepareTimelineFields,
prepareTimelineLegendItems,
TimelineMode,
} from 'app/core/components/TimelineChart/utils';
import { AnnotationsPlugin2 } from '../timeseries/plugins/AnnotationsPlugin2';
import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin';
import { getTimezones } from '../timeseries/utils';
import { StateTimelineTooltip2 } from './StateTimelineTooltip2';
import { Options, defaultOptions } from './panelcfg.gen';
interface TimelinePanelProps extends PanelProps<Options> {}
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<HTMLDivElement>();
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 = (
<div className={styles.paginationContainer} ref={paginationWrapperRef}>
<Pagination
className={styles.paginationElement}
currentPage={currentPageCapped}
numberOfPages={numberOfPages}
showSmallVersion={showSmallVersion}
onNavigate={setCurrentPage}
/>
</div>
);
return { paginatedFrames: currentPageFrames, paginationRev, paginationElement, paginationHeight };
}
/**
* @alpha
*/
export const StateTimelinePanel = ({
data,
timeRange,
timeZone,
options,
width,
height,
replaceVariables,
onChangeTimeRange,
}: TimelinePanelProps) => {
const theme = useTheme2();
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
const { sync, eventsScope, canAddAnnotations, dataLinkPostProcessor, eventBus } = usePanelContext();
const cursorSync = sync?.() ?? DashboardCursorSync.Off;
const { frames, warn } = useMemo(
() => prepareTimelineFields(data.series, options.mergeValues ?? true, timeRange, theme),
[data.series, options.mergeValues, timeRange, theme]
);
const { paginatedFrames, paginationRev, paginationElement, paginationHeight } = usePagination(
frames,
options.perPage
);
const legendItems = useMemo(
() => prepareTimelineLegendItems(paginatedFrames, options.legend, theme),
[paginatedFrames, options.legend, theme]
);
const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]);
if (!paginatedFrames || warn) {
return (
<div className="panel-empty">
<p>{warn ?? 'No data found in response'}</p>
</div>
);
}
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
return (
<div className={styles.container}>
<TimelineChart
theme={theme}
frames={paginatedFrames}
structureRev={data.structureRev}
paginationRev={paginationRev}
timeRange={timeRange}
timeZone={timezones}
width={width}
height={height - paginationHeight}
legendItems={legendItems}
{...options}
mode={TimelineMode.Changes}
replaceVariables={replaceVariables}
dataLinkPostProcessor={dataLinkPostProcessor}
cursorSync={cursorSync}
>
{(builder, alignedFrame) => {
return (
<>
{cursorSync !== DashboardCursorSync.Off && (
<EventBusPlugin config={builder} eventBus={eventBus} frame={alignedFrame} />
)}
{options.tooltip.mode !== TooltipDisplayMode.None && (
<TooltipPlugin2
config={builder}
hoverMode={
options.tooltip.mode === TooltipDisplayMode.Multi ? TooltipHoverMode.xAll : TooltipHoverMode.xOne
}
queryZoom={onChangeTimeRange}
syncMode={cursorSync}
syncScope={eventsScope}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync) => {
if (enableAnnotationCreation && timeRange2 != null) {
setNewAnnotationRange(timeRange2);
dismiss();
return;
}
const annotate = () => {
let xVal = u.posToVal(u.cursor.left!, 'x');
setNewAnnotationRange({ from: xVal, to: xVal });
dismiss();
};
return (
<StateTimelineTooltip2
series={alignedFrame}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
mode={viaSync ? TooltipDisplayMode.Multi : options.tooltip.mode}
sortOrder={options.tooltip.sort}
isPinned={isPinned}
timeRange={timeRange}
annotate={enableAnnotationCreation ? annotate : undefined}
withDuration={true}
maxHeight={options.tooltip.maxHeight}
/>
);
}}
maxWidth={options.tooltip.maxWidth}
/>
)}
{/* Renders annotations */}
<AnnotationsPlugin2
annotations={data.annotations ?? []}
config={builder}
timeZone={timeZone}
newRange={newAnnotationRange}
setNewRange={setNewAnnotationRange}
canvasRegionRendering={false}
/>
<OutsideRangePlugin config={builder} onChangeTimeRange={onChangeTimeRange} />
</>
);
}}
</TimelineChart>
{paginationElement}
</div>
);
};