StateTimeline: Add tooltip multi mode (#79944)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
Adela Almasan
2024-01-03 06:27:17 -06:00
committed by GitHub
co-authored by Leon Sorokin
parent 7d5432e10a
commit 552fa78564
3 changed files with 100 additions and 40 deletions
@@ -210,7 +210,10 @@ export const StateTimelinePanel = ({
alignedData={alignedFrame}
seriesIdx={seriesIdx}
timeZone={timeZone}
mode={options.tooltip.mode}
sortOrder={options.tooltip.sort}
isPinned={isPinned}
timeRange={timeRange}
/>
);
}}
@@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import React from 'react';
import {
arrayUtils,
DataFrame,
Field,
FieldType,
@@ -9,9 +10,11 @@ import {
getFieldDisplayName,
GrafanaTheme2,
LinkModel,
TimeRange,
TimeZone,
} from '@grafana/data';
import { useStyles2, useTheme2 } from '@grafana/ui';
import { SortOrder } from '@grafana/schema/dist/esm/common/common.gen';
import { TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui';
import { VizTooltipContent } from '@grafana/ui/src/components/VizTooltip/VizTooltipContent';
import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter';
import { VizTooltipHeader } from '@grafana/ui/src/components/VizTooltip/VizTooltipHeader';
@@ -28,6 +31,9 @@ interface StateTimelineTooltip2Props {
seriesIdx: number | null | undefined;
isPinned: boolean;
timeZone?: TimeZone;
timeRange: TimeRange;
mode?: TooltipDisplayMode;
sortOrder?: SortOrder;
}
export const StateTimelineTooltip2 = ({
@@ -36,6 +42,9 @@ export const StateTimelineTooltip2 = ({
dataIdxs,
seriesIdx,
timeZone,
timeRange,
mode = TooltipDisplayMode.Single,
sortOrder = SortOrder.None,
isPinned,
}: StateTimelineTooltip2Props) => {
const styles = useStyles2(getStyles);
@@ -65,54 +74,45 @@ export const StateTimelineTooltip2 = ({
return null;
}
const field = alignedData.fields[seriesIdx!];
const links: Array<LinkModel<Field>> = getDataLinks(field, datapointIdx);
let contentLabelValue: LabelValue[] = [];
const xField = alignedData.fields[0];
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, timeZone, theme });
const dataFrameFieldIndex = field.state?.origin;
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone, theme });
const value = field.values[datapointIdx!];
const display = fieldFmt(value);
const fieldDisplayName = dataFrameFieldIndex
? getFieldDisplayName(
data[dataFrameFieldIndex.frameIndex].fields[dataFrameFieldIndex.fieldIndex],
data[dataFrameFieldIndex.frameIndex],
data
)
: null;
const nextStateIdx = findNextStateIndex(field, datapointIdx!);
let nextStateTs;
if (nextStateIdx) {
nextStateTs = xField.values[nextStateIdx!];
}
const stateTs = xField.values[datapointIdx!];
let duration = nextStateTs && fmtDuration(nextStateTs - stateTs);
if (nextStateTs) {
duration = nextStateTs && fmtDuration(nextStateTs - stateTs);
}
let links: Array<LinkModel<Field>> = [];
const from = xFieldFmt(xField.values[datapointIdx!]).text;
const to = xFieldFmt(xField.values[nextStateIdx!]).text;
const getHeaderLabel = (): LabelValue => {
return {
label: '',
value: Boolean(to) ? to : from,
};
};
// Single mode
if (mode === TooltipDisplayMode.Single || isPinned) {
const field = alignedData.fields[seriesIdx!];
links = getDataLinks(field, datapointIdx);
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone, theme });
const value = field.values[datapointIdx!];
const display = fieldFmt(value);
const nextStateIdx = findNextStateIndex(field, datapointIdx!);
let nextStateTs;
if (nextStateIdx) {
nextStateTs = xField.values[nextStateIdx!];
}
const stateTs = xField.values[datapointIdx!];
let duration: string;
if (nextStateTs) {
duration = nextStateTs && fmtDuration(nextStateTs - stateTs);
} else {
const to = timeRange.to.valueOf();
duration = fmtDuration(to - stateTs);
}
const getContentLabelValue = (): LabelValue[] => {
const durationEntry: LabelValue[] = duration ? [{ label: 'Duration', value: duration }] : [];
return [
contentLabelValue = [
{
label: fieldDisplayName ?? '',
label: getFieldDisplayName(field),
value: display.text,
color: display.color,
colorIndicator: ColorIndicator.value,
@@ -120,6 +120,62 @@ export const StateTimelineTooltip2 = ({
},
...durationEntry,
];
}
if (mode === TooltipDisplayMode.Multi && !isPinned) {
const fields = alignedData.fields;
const sortIdx: unknown[] = [];
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
if (
!field ||
field === xField ||
field.type === FieldType.time ||
field.config.custom?.hideFrom?.tooltip ||
field.config.custom?.hideFrom?.viz
) {
continue;
}
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone, theme });
const v = field.values[dataIdxs[i]!];
const display = fieldFmt(v);
sortIdx.push(v);
contentLabelValue.push({
label: getFieldDisplayName(field),
value: display.text,
color: display.color,
colorIndicator: ColorIndicator.value,
colorPlacement: ColorPlacement.trailing,
isActive: seriesIdx === i,
});
}
if (sortOrder !== SortOrder.None) {
// create sort reference series array, as Array.sort() mutates the original array
const sortRef = [...contentLabelValue];
const sortFn = arrayUtils.sortValues(sortOrder);
contentLabelValue.sort((a, b) => {
// get compared values indices to retrieve raw values from sortIdx
const aIdx = sortRef.indexOf(a);
const bIdx = sortRef.indexOf(b);
return sortFn(sortIdx[aIdx], sortIdx[bIdx]);
});
}
}
const getHeaderLabel = (): LabelValue => {
return {
label: '',
value: from,
};
};
const getContentLabelValue = (): LabelValue[] => {
return contentLabelValue;
};
return (
@@ -5,6 +5,7 @@ import {
identityOverrideProcessor,
PanelPlugin,
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { VisibilityMode } from '@grafana/schema';
import { commonOptionsBuilder } from '@grafana/ui';
@@ -14,7 +15,7 @@ import { NullEditorSettings } from '../timeseries/config';
import { StateTimelinePanel } from './StateTimelinePanel';
import { timelinePanelChangedHandler } from './migrations';
import { Options, FieldConfig, defaultOptions, defaultFieldConfig } from './panelcfg.gen';
import { defaultFieldConfig, defaultOptions, FieldConfig, Options } from './panelcfg.gen';
import { StatTimelineSuggestionsSupplier } from './suggestions';
export const plugin = new PanelPlugin<Options, FieldConfig>(StateTimelinePanel)
@@ -121,7 +122,7 @@ export const plugin = new PanelPlugin<Options, FieldConfig>(StateTimelinePanel)
});
commonOptionsBuilder.addLegendOptions(builder, false);
commonOptionsBuilder.addTooltipOptions(builder, true);
commonOptionsBuilder.addTooltipOptions(builder, !config.featureToggles.newVizTooltips);
})
.setSuggestionsSupplier(new StatTimelineSuggestionsSupplier())
.setDataSupport({ annotations: true });