From a6c9a9db92057a778080099a9d2d4d259d31ad39 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:23:05 -0600 Subject: [PATCH] StatusHistory: Add tooltip multi mode (#78703) --- .../VizTooltip/VizTooltipContent.tsx | 4 +- .../components/VizTooltip/VizTooltipRow.tsx | 13 ++- .../src/components/VizTooltip/types.ts | 4 +- .../status-history/StatusHistoryPanel.tsx | 2 + .../status-history/StatusHistoryTooltip2.tsx | 101 +++++++++++++----- .../plugins/panel/status-history/module.tsx | 3 +- .../app/plugins/panel/status-history/utils.ts | 20 ++++ 7 files changed, 117 insertions(+), 30 deletions(-) create mode 100644 public/app/plugins/panel/status-history/utils.ts diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx index ebd08a5cd28..b76177b2e90 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx @@ -20,7 +20,7 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
{contentLabelValue?.map((labelValue, i) => { - const { label, value, color, colorIndicator } = labelValue; + const { label, value, color, colorIndicator, colorPlacement, isActive } = labelValue; return ( ); diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx index 74dd5de919c..9fdd2d051cd 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx @@ -7,7 +7,7 @@ import { useStyles2 } from '../../themes'; import { Tooltip } from '../Tooltip'; import { VizTooltipColorIndicator } from './VizTooltipColorIndicator'; -import { LabelValue } from './types'; +import { ColorPlacement, LabelValue } from './types'; interface Props extends LabelValue { justify?: string; @@ -21,6 +21,7 @@ export const VizTooltipRow = ({ value, color, colorIndicator, + colorPlacement = ColorPlacement.leading, justify = 'flex-start', colorFirst = true, isActive = false, @@ -65,12 +66,20 @@ export const VizTooltipRow = ({ )}
- {color && !colorFirst && } + {color && !colorFirst && colorPlacement === ColorPlacement.leading && ( + + )}
{value}
+ {color && !colorFirst && colorPlacement === ColorPlacement.trailing && ( + <> +   + + + )}
); diff --git a/packages/grafana-ui/src/components/VizTooltip/types.ts b/packages/grafana-ui/src/components/VizTooltip/types.ts index 615ad81b331..80591ca9eab 100644 --- a/packages/grafana-ui/src/components/VizTooltip/types.ts +++ b/packages/grafana-ui/src/components/VizTooltip/types.ts @@ -10,7 +10,7 @@ export enum ColorIndicator { marker_lg = 'marker_lg', } -export enum LabelValuePlacement { +export enum ColorPlacement { hidden = 'hidden', leading = 'leading', trailing = 'trailing', @@ -21,4 +21,6 @@ export interface LabelValue { value: string | number | null; color?: string; colorIndicator?: ColorIndicator; + colorPlacement?: ColorPlacement; + isActive?: boolean; } diff --git a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx index 67514d22153..377ce3c0231 100644 --- a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx +++ b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx @@ -233,6 +233,8 @@ export const StatusHistoryPanel = ({ alignedData={alignedFrame} seriesIdx={seriesIdx} timeZone={timeZone} + mode={options.tooltip.mode} + sortOrder={options.tooltip.sort} isPinned={isPinned} /> ); diff --git a/public/app/plugins/panel/status-history/StatusHistoryTooltip2.tsx b/public/app/plugins/panel/status-history/StatusHistoryTooltip2.tsx index 94380a76d71..16c52153dfd 100644 --- a/public/app/plugins/panel/status-history/StatusHistoryTooltip2.tsx +++ b/public/app/plugins/panel/status-history/StatusHistoryTooltip2.tsx @@ -10,12 +10,17 @@ import { GrafanaTheme2, TimeZone, LinkModel, + FieldType, + arrayUtils, } from '@grafana/data'; +import { SortOrder, TooltipDisplayMode } from '@grafana/schema'; import { useStyles2 } 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'; -import { LabelValue } from '@grafana/ui/src/components/VizTooltip/types'; +import { ColorIndicator, ColorPlacement, LabelValue } from '@grafana/ui/src/components/VizTooltip/types'; + +import { getDataLinks } from './utils'; interface StatusHistoryTooltipProps { data: DataFrame[]; @@ -24,6 +29,8 @@ interface StatusHistoryTooltipProps { seriesIdx: number | null | undefined; timeZone: TimeZone; isPinned: boolean; + mode?: TooltipDisplayMode; + sortOrder?: SortOrder; } function fmt(field: Field, val: number): string { @@ -39,7 +46,8 @@ export const StatusHistoryTooltip2 = ({ dataIdxs, alignedData, seriesIdx, - timeZone, + mode = TooltipDisplayMode.Single, + sortOrder = SortOrder.None, isPinned, }: StatusHistoryTooltipProps) => { const styles = useStyles2(getStyles); @@ -51,27 +59,76 @@ export const StatusHistoryTooltip2 = ({ return null; } + let contentLabelValue: LabelValue[] = []; + const xField = alignedData.fields[0]; - const field = alignedData.fields[seriesIdx!]; + let links: Array> = []; - const links: Array> = []; - const linkLookup = new Set(); + // Single mode + if (mode === TooltipDisplayMode.Single || isPinned) { + const field = alignedData.fields[seriesIdx!]; + links = getDataLinks(field, datapointIdx); - if (field.getLinks) { - const v = field.values[datapointIdx]; - const disp = field.display ? field.display(v) : { text: `${v}`, numeric: +v }; - field.getLinks({ calculatedValue: disp, valueRowIndex: datapointIdx }).forEach((link) => { - const key = `${link.title}/${link.href}`; - if (!linkLookup.has(key)) { - links.push(link); - linkLookup.add(key); - } - }); + const fieldFmt = field.display || getDisplayProcessor(); + const value = field.values[datapointIdx!]; + const display = fieldFmt(value); + + contentLabelValue = [ + { + label: getFieldDisplayName(field), + value: fmt(field, field.values[datapointIdx]), + color: display.color, + colorIndicator: ColorIndicator.value, + colorPlacement: ColorPlacement.trailing, + }, + ]; } - const fieldFmt = field.display || getDisplayProcessor(); - const value = field.values[datapointIdx!]; - const display = fieldFmt(value); + if (mode === TooltipDisplayMode.Multi && !isPinned) { + const frame = alignedData; + const fields = frame.fields; + const sortIdx: unknown[] = []; + + for (let i = 0; i < fields.length; i++) { + const field = frame.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(); + const v = field.values[datapointIdx!]; + const display = fieldFmt(v); + + sortIdx.push(v); + contentLabelValue.push({ + label: getFieldDisplayName(field), + value: fmt(field, field.values[datapointIdx]), + 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 { @@ -81,13 +138,7 @@ export const StatusHistoryTooltip2 = ({ }; const getContentLabelValue = () => { - return [ - { - label: getFieldDisplayName(field), - value: fmt(field, field.values[datapointIdx]), - color: display.color, - }, - ]; + return contentLabelValue; }; return ( diff --git a/public/app/plugins/panel/status-history/module.tsx b/public/app/plugins/panel/status-history/module.tsx index 9266f326231..0fb5decdfb3 100644 --- a/public/app/plugins/panel/status-history/module.tsx +++ b/public/app/plugins/panel/status-history/module.tsx @@ -1,4 +1,5 @@ import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { VisibilityMode } from '@grafana/schema'; import { commonOptionsBuilder } from '@grafana/ui'; @@ -80,6 +81,6 @@ export const plugin = new PanelPlugin(StatusHistoryPanel) }); commonOptionsBuilder.addLegendOptions(builder, false); - commonOptionsBuilder.addTooltipOptions(builder, true); + commonOptionsBuilder.addTooltipOptions(builder, !config.featureToggles.newVizTooltips); }) .setSuggestionsSupplier(new StatusHistorySuggestionsSupplier()); diff --git a/public/app/plugins/panel/status-history/utils.ts b/public/app/plugins/panel/status-history/utils.ts new file mode 100644 index 00000000000..4f26288ba03 --- /dev/null +++ b/public/app/plugins/panel/status-history/utils.ts @@ -0,0 +1,20 @@ +import { Field, LinkModel } from '@grafana/data'; + +export const getDataLinks = (field: Field, datapointIdx: number) => { + const links: Array> = []; + const linkLookup = new Set(); + + if (field.getLinks) { + const v = field.values[datapointIdx]; + const disp = field.display ? field.display(v) : { text: `${v}`, numeric: +v }; + field.getLinks({ calculatedValue: disp, valueRowIndex: datapointIdx }).forEach((link) => { + const key = `${link.title}/${link.href}`; + if (!linkLookup.has(key)) { + links.push(link); + linkLookup.add(key); + } + }); + } + + return links; +};