From 9d8218688593719b1c84e8e57f15af9136f70307 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Fri, 4 Apr 2025 15:30:24 +0200 Subject: [PATCH] Alerting: Improve displaying small numbers in query preview (#103439) * Improve rendering of small numbers in the query preview * Use getSeriesValue in Expression component --- .../components/expressions/Expression.tsx | 4 +- .../components/expressions/util.test.ts | 41 +++++++++++++++---- .../unified/components/expressions/util.ts | 22 +++++++--- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/public/app/features/alerting/unified/components/expressions/Expression.tsx b/public/app/features/alerting/unified/components/expressions/Expression.tsx index d710c3831f9..3a4e0ce2141 100644 --- a/public/app/features/alerting/unified/components/expressions/Expression.tsx +++ b/public/app/features/alerting/unified/components/expressions/Expression.tsx @@ -35,7 +35,7 @@ import { Spacer } from '../Spacer'; import { AlertStateTag } from '../rules/AlertStateTag'; import { ExpressionStatusIndicator } from './ExpressionStatusIndicator'; -import { formatLabels, getSeriesLabels, getSeriesName, getSeriesValue, isEmptySeries } from './util'; +import { formatLabels, formatSeriesValue, getSeriesLabels, getSeriesName, getSeriesValue, isEmptySeries } from './util'; interface ExpressionProps { isAlertCondition?: boolean; @@ -438,7 +438,7 @@ function FrameRow({ frame, index, isAlertCondition, isRecordingRule }: FrameProp )} -
{value}
+
{formatSeriesValue(value)}
{shouldRenderSumary && ( <> {showFiring && } diff --git a/public/app/features/alerting/unified/components/expressions/util.test.ts b/public/app/features/alerting/unified/components/expressions/util.test.ts index f8cb2d39d23..9ceec0b4cc4 100644 --- a/public/app/features/alerting/unified/components/expressions/util.test.ts +++ b/public/app/features/alerting/unified/components/expressions/util.test.ts @@ -8,6 +8,7 @@ import { decodeGrafanaNamespace, encodeGrafanaNamespace, formatLabels, + formatSeriesValue, getSeriesLabels, getSeriesName, getSeriesValue, @@ -24,10 +25,6 @@ const DATA_FRAME: DataFrame = toDataFrame({ fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3] }], }); -const DATA_FRAME_LARGE_DECIMAL: DataFrame = toDataFrame({ - fields: [{ name: 'value', type: FieldType.number, values: [1.23456789] }], -}); - const DATA_FRAME_WITH_LABELS: DataFrame = toDataFrame({ fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3], labels: { __name__: 'my-series', foo: 'bar' } }], }); @@ -222,10 +219,6 @@ describe('getSeriesValue', () => { const name = getSeriesValue(DATA_FRAME); expect(name).toBe(1); }); - - it('should round values', () => { - expect(getSeriesValue(DATA_FRAME_LARGE_DECIMAL)).toBe(1.23457); - }); }); describe('getSeriesLabels', () => { @@ -237,3 +230,35 @@ describe('getSeriesLabels', () => { expect(getSeriesLabels(EMPTY_FRAME)).toStrictEqual({}); }); }); + +describe('formatSeriesValue', () => { + it('should convert non-numeric values to strings', () => { + expect(formatSeriesValue('string value')).toBe('string value'); + expect(formatSeriesValue(null)).toBe('null'); + expect(formatSeriesValue(undefined)).toBe('undefined'); + expect(formatSeriesValue({})).toBe('[object Object]'); + }); + + it('should return 5 significant digits in absolutely smaller than 1', () => { + expect(formatSeriesValue(0.00005123)).toBe('0.00005123'); + expect(formatSeriesValue(0.7894)).toBe('0.7894'); + expect(formatSeriesValue(-0.000051237676767)).toBe('-0.000051238'); + expect(formatSeriesValue(-0.25)).toBe('-0.25'); + }); + + it('should use standard notation for numbers absolutely larger than 1', () => { + expect(formatSeriesValue(1.0001)).toBe('1.0001'); + expect(formatSeriesValue(1.234)).toBe('1.234'); + expect(formatSeriesValue(76767345.9876)).toBe('76767345.9876'); + }); + + it('should round regular numbers to 5 decimal places', () => { + expect(formatSeriesValue(1.23456789)).toBe('1.23457'); + expect(formatSeriesValue(0.10000001)).toBe('0.1'); + expect(formatSeriesValue(-42.98765432)).toBe('-42.98765'); + }); + + it('should handle zero correctly', () => { + expect(formatSeriesValue(0)).toBe('0'); + }); +}); diff --git a/public/app/features/alerting/unified/components/expressions/util.ts b/public/app/features/alerting/unified/components/expressions/util.ts index 07d732eb27b..a74c54dd79f 100644 --- a/public/app/features/alerting/unified/components/expressions/util.ts +++ b/public/app/features/alerting/unified/components/expressions/util.ts @@ -15,20 +15,29 @@ import { isCloudRulesSource } from '../../utils/datasource'; */ const getSeriesName = (frame: DataFrame): string | undefined => { - const firstField = frame.fields[0]; + const firstField = frame.fields.at(0); const displayNameFromDS = firstField?.config?.displayNameFromDS; return displayNameFromDS ?? frame.name ?? firstField?.labels?.__name__; }; const getSeriesValue = (frame: DataFrame) => { - const value = frame.fields[0]?.values[0]; + return frame.fields.at(0)?.values.at(0); +}; - if (Number.isFinite(value)) { - return roundDecimals(value, 5); +const smallNumberFormatter = new Intl.NumberFormat(undefined, { + maximumSignificantDigits: 5, +}); + +const formatSeriesValue = (value: unknown): string => { + if (Number.isFinite(value) && typeof value === 'number') { + const absValue = Math.abs(value); + if (absValue < 1) { + return smallNumberFormatter.format(value); + } + return roundDecimals(value, 5).toString(10); } - - return value; + return String(value); }; const getSeriesLabels = (frame: DataFrame): Record => { @@ -103,5 +112,6 @@ export { getSeriesLabels, getSeriesName, getSeriesValue, + formatSeriesValue, isEmptySeries, };