mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
StatusHistory: Add tooltip multi mode (#78703)
This commit is contained in:
parent
a5377f85ce
commit
a6c9a9db92
@ -20,7 +20,7 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
|
|||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<div>
|
<div>
|
||||||
{contentLabelValue?.map((labelValue, i) => {
|
{contentLabelValue?.map((labelValue, i) => {
|
||||||
const { label, value, color, colorIndicator } = labelValue;
|
const { label, value, color, colorIndicator, colorPlacement, isActive } = labelValue;
|
||||||
return (
|
return (
|
||||||
<VizTooltipRow
|
<VizTooltipRow
|
||||||
key={i}
|
key={i}
|
||||||
@ -28,7 +28,9 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
|
|||||||
value={value}
|
value={value}
|
||||||
color={color}
|
color={color}
|
||||||
colorIndicator={colorIndicator}
|
colorIndicator={colorIndicator}
|
||||||
|
colorPlacement={colorPlacement}
|
||||||
colorFirst={false}
|
colorFirst={false}
|
||||||
|
isActive={isActive}
|
||||||
justify={'space-between'}
|
justify={'space-between'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import { useStyles2 } from '../../themes';
|
|||||||
import { Tooltip } from '../Tooltip';
|
import { Tooltip } from '../Tooltip';
|
||||||
|
|
||||||
import { VizTooltipColorIndicator } from './VizTooltipColorIndicator';
|
import { VizTooltipColorIndicator } from './VizTooltipColorIndicator';
|
||||||
import { LabelValue } from './types';
|
import { ColorPlacement, LabelValue } from './types';
|
||||||
|
|
||||||
interface Props extends LabelValue {
|
interface Props extends LabelValue {
|
||||||
justify?: string;
|
justify?: string;
|
||||||
@ -21,6 +21,7 @@ export const VizTooltipRow = ({
|
|||||||
value,
|
value,
|
||||||
color,
|
color,
|
||||||
colorIndicator,
|
colorIndicator,
|
||||||
|
colorPlacement = ColorPlacement.leading,
|
||||||
justify = 'flex-start',
|
justify = 'flex-start',
|
||||||
colorFirst = true,
|
colorFirst = true,
|
||||||
isActive = false,
|
isActive = false,
|
||||||
@ -65,12 +66,20 @@ export const VizTooltipRow = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.valueWrapper}>
|
<div className={styles.valueWrapper}>
|
||||||
{color && !colorFirst && <VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />}
|
{color && !colorFirst && colorPlacement === ColorPlacement.leading && (
|
||||||
|
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />
|
||||||
|
)}
|
||||||
<Tooltip content={value ? value.toString() : ''} interactive={false} show={showValueTooltip}>
|
<Tooltip content={value ? value.toString() : ''} interactive={false} show={showValueTooltip}>
|
||||||
<div className={cx(styles.value, isActive)} onMouseEnter={onMouseEnterValue} onMouseLeave={onMouseLeaveValue}>
|
<div className={cx(styles.value, isActive)} onMouseEnter={onMouseEnterValue} onMouseLeave={onMouseLeaveValue}>
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{color && !colorFirst && colorPlacement === ColorPlacement.trailing && (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,7 @@ export enum ColorIndicator {
|
|||||||
marker_lg = 'marker_lg',
|
marker_lg = 'marker_lg',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LabelValuePlacement {
|
export enum ColorPlacement {
|
||||||
hidden = 'hidden',
|
hidden = 'hidden',
|
||||||
leading = 'leading',
|
leading = 'leading',
|
||||||
trailing = 'trailing',
|
trailing = 'trailing',
|
||||||
@ -21,4 +21,6 @@ export interface LabelValue {
|
|||||||
value: string | number | null;
|
value: string | number | null;
|
||||||
color?: string;
|
color?: string;
|
||||||
colorIndicator?: ColorIndicator;
|
colorIndicator?: ColorIndicator;
|
||||||
|
colorPlacement?: ColorPlacement;
|
||||||
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -233,6 +233,8 @@ export const StatusHistoryPanel = ({
|
|||||||
alignedData={alignedFrame}
|
alignedData={alignedFrame}
|
||||||
seriesIdx={seriesIdx}
|
seriesIdx={seriesIdx}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
|
mode={options.tooltip.mode}
|
||||||
|
sortOrder={options.tooltip.sort}
|
||||||
isPinned={isPinned}
|
isPinned={isPinned}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -10,12 +10,17 @@ import {
|
|||||||
GrafanaTheme2,
|
GrafanaTheme2,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
LinkModel,
|
LinkModel,
|
||||||
|
FieldType,
|
||||||
|
arrayUtils,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import { SortOrder, TooltipDisplayMode } from '@grafana/schema';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { VizTooltipContent } from '@grafana/ui/src/components/VizTooltip/VizTooltipContent';
|
import { VizTooltipContent } from '@grafana/ui/src/components/VizTooltip/VizTooltipContent';
|
||||||
import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter';
|
import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter';
|
||||||
import { VizTooltipHeader } from '@grafana/ui/src/components/VizTooltip/VizTooltipHeader';
|
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 {
|
interface StatusHistoryTooltipProps {
|
||||||
data: DataFrame[];
|
data: DataFrame[];
|
||||||
@ -24,6 +29,8 @@ interface StatusHistoryTooltipProps {
|
|||||||
seriesIdx: number | null | undefined;
|
seriesIdx: number | null | undefined;
|
||||||
timeZone: TimeZone;
|
timeZone: TimeZone;
|
||||||
isPinned: boolean;
|
isPinned: boolean;
|
||||||
|
mode?: TooltipDisplayMode;
|
||||||
|
sortOrder?: SortOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmt(field: Field, val: number): string {
|
function fmt(field: Field, val: number): string {
|
||||||
@ -39,7 +46,8 @@ export const StatusHistoryTooltip2 = ({
|
|||||||
dataIdxs,
|
dataIdxs,
|
||||||
alignedData,
|
alignedData,
|
||||||
seriesIdx,
|
seriesIdx,
|
||||||
timeZone,
|
mode = TooltipDisplayMode.Single,
|
||||||
|
sortOrder = SortOrder.None,
|
||||||
isPinned,
|
isPinned,
|
||||||
}: StatusHistoryTooltipProps) => {
|
}: StatusHistoryTooltipProps) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
@ -51,27 +59,76 @@ export const StatusHistoryTooltip2 = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let contentLabelValue: LabelValue[] = [];
|
||||||
|
|
||||||
const xField = alignedData.fields[0];
|
const xField = alignedData.fields[0];
|
||||||
const field = alignedData.fields[seriesIdx!];
|
let links: Array<LinkModel<Field>> = [];
|
||||||
|
|
||||||
const links: Array<LinkModel<Field>> = [];
|
// Single mode
|
||||||
const linkLookup = new Set<string>();
|
if (mode === TooltipDisplayMode.Single || isPinned) {
|
||||||
|
const field = alignedData.fields[seriesIdx!];
|
||||||
|
links = getDataLinks(field, datapointIdx);
|
||||||
|
|
||||||
if (field.getLinks) {
|
const fieldFmt = field.display || getDisplayProcessor();
|
||||||
const v = field.values[datapointIdx];
|
const value = field.values[datapointIdx!];
|
||||||
const disp = field.display ? field.display(v) : { text: `${v}`, numeric: +v };
|
const display = fieldFmt(value);
|
||||||
field.getLinks({ calculatedValue: disp, valueRowIndex: datapointIdx }).forEach((link) => {
|
|
||||||
const key = `${link.title}/${link.href}`;
|
contentLabelValue = [
|
||||||
if (!linkLookup.has(key)) {
|
{
|
||||||
links.push(link);
|
label: getFieldDisplayName(field),
|
||||||
linkLookup.add(key);
|
value: fmt(field, field.values[datapointIdx]),
|
||||||
}
|
color: display.color,
|
||||||
});
|
colorIndicator: ColorIndicator.value,
|
||||||
|
colorPlacement: ColorPlacement.trailing,
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldFmt = field.display || getDisplayProcessor();
|
if (mode === TooltipDisplayMode.Multi && !isPinned) {
|
||||||
const value = field.values[datapointIdx!];
|
const frame = alignedData;
|
||||||
const display = fieldFmt(value);
|
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 => {
|
const getHeaderLabel = (): LabelValue => {
|
||||||
return {
|
return {
|
||||||
@ -81,13 +138,7 @@ export const StatusHistoryTooltip2 = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getContentLabelValue = () => {
|
const getContentLabelValue = () => {
|
||||||
return [
|
return contentLabelValue;
|
||||||
{
|
|
||||||
label: getFieldDisplayName(field),
|
|
||||||
value: fmt(field, field.values[datapointIdx]),
|
|
||||||
color: display.color,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { VisibilityMode } from '@grafana/schema';
|
import { VisibilityMode } from '@grafana/schema';
|
||||||
import { commonOptionsBuilder } from '@grafana/ui';
|
import { commonOptionsBuilder } from '@grafana/ui';
|
||||||
|
|
||||||
@ -80,6 +81,6 @@ export const plugin = new PanelPlugin<Options, FieldConfig>(StatusHistoryPanel)
|
|||||||
});
|
});
|
||||||
|
|
||||||
commonOptionsBuilder.addLegendOptions(builder, false);
|
commonOptionsBuilder.addLegendOptions(builder, false);
|
||||||
commonOptionsBuilder.addTooltipOptions(builder, true);
|
commonOptionsBuilder.addTooltipOptions(builder, !config.featureToggles.newVizTooltips);
|
||||||
})
|
})
|
||||||
.setSuggestionsSupplier(new StatusHistorySuggestionsSupplier());
|
.setSuggestionsSupplier(new StatusHistorySuggestionsSupplier());
|
||||||
|
20
public/app/plugins/panel/status-history/utils.ts
Normal file
20
public/app/plugins/panel/status-history/utils.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Field, LinkModel } from '@grafana/data';
|
||||||
|
|
||||||
|
export const getDataLinks = (field: Field, datapointIdx: number) => {
|
||||||
|
const links: Array<LinkModel<Field>> = [];
|
||||||
|
const linkLookup = new Set<string>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user