From bc95a2f19a9f41533bc0e14e89fd57bc4fb29d5a Mon Sep 17 00:00:00 2001 From: "kemal.earth" <606977+kemaldotearth@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:20:37 +0100 Subject: [PATCH] fix(studio): edge func exec time formatting in reports (#48539) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Fixes Edge Function Execution Time chart within our observability reports time formatting. This also fixes the non-hovered state which would lose the `ms` formatting. | Before | After | |--------|--------| | cleanshot_2026-07-29_at_02 15
53_2x | Screenshot 2026-07-31 at 14 30
48 | ## Summary by CodeRabbit * **New Features** * Improved execution-time chart formatting with clearer millisecond values, thousands separators, and configurable precision. * Chart highlights now support custom value formatting alongside existing number, percentage, and byte formats. * **Bug Fixes** * Non-finite execution-time values now display safely as `0ms`. --- .../components/ui/Charts/Charts.utils.tsx | 18 ++++++++++++ .../components/ui/Charts/ComposedChart.tsx | 4 +++ .../data/reports/v2/edge-functions.config.ts | 7 +++-- .../components/ui/Charts/Charts.utils.test.ts | 29 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/apps/studio/components/ui/Charts/Charts.utils.tsx b/apps/studio/components/ui/Charts/Charts.utils.tsx index 613c8ecc266..82f228e88d1 100644 --- a/apps/studio/components/ui/Charts/Charts.utils.tsx +++ b/apps/studio/components/ui/Charts/Charts.utils.tsx @@ -73,6 +73,24 @@ export const compactNumberFormatter = (num: number): string => { return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(num) } +/** + * Formats a duration in milliseconds with thousands separators and a unit suffix. + * + * @example + * millisecondFormatter(123) // "123ms" + * millisecondFormatter(90000) // "90,000ms" + * millisecondFormatter(1234.56) // "1,235ms" + * millisecondFormatter(1234.56, 2) // "1,234.56ms" + */ +export const millisecondFormatter = (value: number, precision = 0) => { + if (!Number.isFinite(value)) return '0ms' + + return `${value.toLocaleString('en-US', { + minimumFractionDigits: precision, + maximumFractionDigits: precision, + })}ms` +} + /** * Formats a percentage, trimming decimals at 100. * diff --git a/apps/studio/components/ui/Charts/ComposedChart.tsx b/apps/studio/components/ui/Charts/ComposedChart.tsx index b778bd848bc..90b1babe16e 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.tsx @@ -220,6 +220,10 @@ export function ComposedChart({ return value } + if (typeof format === 'function') { + return format(value) + } + if (shouldFormatBytes) { const bytesValue = isNetworkChart ? Math.abs(value) : value const formatted = isMemoryChart diff --git a/apps/studio/data/reports/v2/edge-functions.config.ts b/apps/studio/data/reports/v2/edge-functions.config.ts index a4c72987276..9d5d53d5f7c 100644 --- a/apps/studio/data/reports/v2/edge-functions.config.ts +++ b/apps/studio/data/reports/v2/edge-functions.config.ts @@ -12,6 +12,7 @@ import { isUnixMicro, unixMicroToIsoTimestamp, } from '@/components/interfaces/Settings/Logs/Logs.utils' +import { millisecondFormatter } from '@/components/ui/Charts/Charts.utils' import type { AnalyticsInterval } from '@/data/analytics/constants' import { analyticsLiteral, @@ -290,10 +291,10 @@ export const edgeFunctionReports = ({ defaultChartStyle: 'line', titleTooltip: 'Average execution time for edge functions.', YAxisProps: { - width: 50, - tickFormatter: (value: number) => `${value}ms`, + width: 68, + tickFormatter: (value: number) => millisecondFormatter(value), }, - format: (value: unknown) => `${Number(value).toFixed(0)}ms`, + format: (value: unknown) => millisecondFormatter(Number(value)), dataProvider: async () => { const sql = METRIC_SQL.ExecutionTime(interval, filters) const rawData = await fetchLogs(projectRef, sql, startDate, endDate) diff --git a/apps/studio/tests/components/ui/Charts/Charts.utils.test.ts b/apps/studio/tests/components/ui/Charts/Charts.utils.test.ts index caa6f1aec73..e3ab934afce 100644 --- a/apps/studio/tests/components/ui/Charts/Charts.utils.test.ts +++ b/apps/studio/tests/components/ui/Charts/Charts.utils.test.ts @@ -5,6 +5,7 @@ import { compactNumberFormatter, formatPercentage, isFloat, + millisecondFormatter, numberFormatter, precisionFormatter, useStacked, @@ -138,6 +139,34 @@ describe('compactNumberFormatter', () => { }) }) +describe('millisecondFormatter', () => { + it('appends the ms unit', () => { + expect(millisecondFormatter(0)).toBe('0ms') + expect(millisecondFormatter(123)).toBe('123ms') + }) + + it('adds thousands separators', () => { + expect(millisecondFormatter(1000)).toBe('1,000ms') + expect(millisecondFormatter(90000)).toBe('90,000ms') + expect(millisecondFormatter(1_234_567)).toBe('1,234,567ms') + }) + + it('rounds to whole milliseconds by default', () => { + expect(millisecondFormatter(1234.56)).toBe('1,235ms') + expect(millisecondFormatter(0.4)).toBe('0ms') + }) + + it('respects an explicit precision', () => { + expect(millisecondFormatter(1234.56, 2)).toBe('1,234.56ms') + expect(millisecondFormatter(84.3, 2)).toBe('84.30ms') + }) + + it('falls back to 0ms for non-finite values', () => { + expect(millisecondFormatter(NaN)).toBe('0ms') + expect(millisecondFormatter(Infinity)).toBe('0ms') + }) +}) + test('useStacked', () => { const { result } = renderHook(() => useStacked({