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 |
|--------|--------|
|
|
|
## 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({