From 4dc4c285f1418cc2b39b37a23426075085feec14 Mon Sep 17 00:00:00 2001
From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com>
Date: Fri, 15 Dec 2023 19:17:49 -0600
Subject: [PATCH] Heatmap: Update tooltip UX (#79429)
---
.../VizTooltip/VizTooltipContent.tsx | 10 +-
.../panel/heatmap/HeatmapHoverView.tsx | 112 ++++++++----------
.../heatmap/tooltip/tooltipUtils.test.ts | 8 +-
.../plugins/panel/heatmap/tooltip/utils.ts | 15 ++-
4 files changed, 71 insertions(+), 74 deletions(-)
diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx
index f131472b71a..c7558bcf288 100644
--- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx
+++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx
@@ -10,7 +10,7 @@ import { LabelValue } from './types';
interface Props {
contentLabelValue: LabelValue[];
- customContent?: ReactElement | null;
+ customContent?: ReactElement[];
}
export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) => {
@@ -35,7 +35,13 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
);
})}
- {customContent &&
{customContent}
}
+ {customContent?.map((content, i) => {
+ return (
+
+ {content}
+
+ );
+ })}
);
};
diff --git a/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx b/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx
index 23a88663a3a..13bec6d02ef 100644
--- a/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx
+++ b/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx
@@ -20,7 +20,7 @@ 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 { ColorIndicator, LabelValue } from '@grafana/ui/src/components/VizTooltip/types';
+import { ColorIndicator, ColorPlacement, LabelValue } from '@grafana/ui/src/components/VizTooltip/types';
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
@@ -28,7 +28,7 @@ import { DataHoverView } from 'app/features/visualization/data-hover/DataHoverVi
import { HeatmapData } from './fields';
import { renderHistogram } from './renderHistogram';
-import { getSparseCellMinMax, formatMilliseconds, getFieldFromData, getHoverCellColor } from './tooltip/utils';
+import { getSparseCellMinMax, getFieldFromData, getHoverCellColor, formatMilliseconds } from './tooltip/utils';
interface Props {
dataIdxs: Array;
@@ -213,37 +213,64 @@ const HeatmapHoverCell = ({
const { cellColor, colorPalette } = getHoverCellColor(data, index);
- const getLabelValue = (): LabelValue[] => {
+ const getContentLabels = (): LabelValue[] => {
+ if (nonNumericOrdinalDisplay) {
+ return [{ label: 'Name', value: nonNumericOrdinalDisplay }];
+ }
+
+ switch (data.yLayout) {
+ case HeatmapCellLayout.unknown:
+ return [{ label: '', value: yDisp(yBucketMin) }];
+ }
+
return [
{
- label: getFieldDisplayName(countField, data.heatmap),
- value: data.display!(count),
- color: cellColor ?? '#FFF',
- colorIndicator: ColorIndicator.value,
+ label: 'Bucket',
+ value: `${yDisp(yBucketMin)}` + '-' + `${yDisp(yBucketMax)}`,
},
];
};
const getHeaderLabel = (): LabelValue => {
- if (nonNumericOrdinalDisplay) {
- return { label: 'Name', value: nonNumericOrdinalDisplay };
- }
-
- switch (data.yLayout) {
- case HeatmapCellLayout.unknown:
- return { label: '', value: yDisp(yBucketMin) };
- }
-
return {
- label: 'Bucket',
- value: `${yDisp(yBucketMin)}` + '-' + `${yDisp(yBucketMax)}`,
+ label: '',
+ value: xDisp(xBucketMax)!,
};
};
- // Color scale
- const getCustomValueDisplay = (): ReactElement | null => {
+ const getContentLabelValue = (): LabelValue[] => {
+ const fromToInt: LabelValue[] = interval ? [{ label: 'Duration', value: formatMilliseconds(interval) }] : [];
+
+ return [
+ {
+ label: getFieldDisplayName(countField, data.heatmap),
+ value: data.display!(count),
+ color: cellColor ?? '#FFF',
+ colorPlacement: ColorPlacement.trailing,
+ colorIndicator: ColorIndicator.value,
+ },
+ ...getContentLabels(),
+ ...fromToInt,
+ ];
+ };
+
+ const getCustomContent = () => {
+ let content: ReactElement[] = [];
+ // Histogram
+ if (showHistogram) {
+ content.push(
+
+ );
+ }
+
+ // Color scale
if (colorPalette && showColorScale) {
- return (
+ content.push(
{
- let fromToInt = [
- {
- label: 'From',
- value: xDisp(xBucketMin)!,
- },
- ];
-
- if (data.xLayout !== HeatmapCellLayout.unknown) {
- fromToInt.push({ label: 'To', value: xDisp(xBucketMax)! });
-
- if (interval) {
- const formattedString = formatMilliseconds(interval);
- fromToInt.push({ label: 'Interval', value: formattedString });
- }
- }
-
- return fromToInt;
- };
-
- const getCustomContent = (): ReactElement | null => {
- if (showHistogram) {
- return (
-
- );
- }
-
- return null;
+ return content;
};
// @TODO remove this when adding annotations support
@@ -299,11 +291,7 @@ const HeatmapHoverCell = ({
return (
-
+
{isPinned && }
diff --git a/public/app/plugins/panel/heatmap/tooltip/tooltipUtils.test.ts b/public/app/plugins/panel/heatmap/tooltip/tooltipUtils.test.ts
index 4ef58686791..cbc0706c4a2 100644
--- a/public/app/plugins/panel/heatmap/tooltip/tooltipUtils.test.ts
+++ b/public/app/plugins/panel/heatmap/tooltip/tooltipUtils.test.ts
@@ -4,19 +4,19 @@ describe('heatmap tooltip utils', () => {
it('converts ms to appropriate unit', async () => {
let msToFormat = 10;
let formatted = formatMilliseconds(msToFormat);
- expect(formatted).toBe('10 milliseconds');
+ expect(formatted).toBe('10 ms');
msToFormat = 1000;
formatted = formatMilliseconds(msToFormat);
- expect(formatted).toBe('1 second');
+ expect(formatted).toBe('1 s');
msToFormat = 1000 * 120;
formatted = formatMilliseconds(msToFormat);
- expect(formatted).toBe('2 minutes');
+ expect(formatted).toBe('2 m');
msToFormat = 1000 * 60 * 60;
formatted = formatMilliseconds(msToFormat);
- expect(formatted).toBe('1 hour');
+ expect(formatted).toBe('1 h');
msToFormat = 1000 * 60 * 60 * 24;
formatted = formatMilliseconds(msToFormat);
diff --git a/public/app/plugins/panel/heatmap/tooltip/utils.ts b/public/app/plugins/panel/heatmap/tooltip/utils.ts
index 78b633ade0b..58114245d4e 100644
--- a/public/app/plugins/panel/heatmap/tooltip/utils.ts
+++ b/public/app/plugins/panel/heatmap/tooltip/utils.ts
@@ -27,16 +27,18 @@ const conversions: Record = {
month: 1000 * 60 * 60 * 24 * 30,
week: 1000 * 60 * 60 * 24 * 7,
day: 1000 * 60 * 60 * 24,
- hour: 1000 * 60 * 60,
- minute: 1000 * 60,
- second: 1000,
- millisecond: 1,
+ h: 1000 * 60 * 60,
+ m: 1000 * 60,
+ s: 1000,
+ ms: 1,
};
+const noPluralize = new Set(['ms', 's', 'm', 'h']);
+
// @TODO: display "~ 1 year/month"?
export const formatMilliseconds = (milliseconds: number) => {
let value = 1;
- let unit = 'millisecond';
+ let unit = 'ms';
for (unit in conversions) {
if (milliseconds >= conversions[unit]) {
@@ -45,7 +47,8 @@ export const formatMilliseconds = (milliseconds: number) => {
}
}
- const unitString = value === 1 ? unit : unit + 's';
+ const plural = value !== 1 && !noPluralize.has(unit);
+ const unitString = plural ? unit + 's' : unit;
return `${value} ${unitString}`;
};