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 |
|--------|--------|
| <img width="2160" height="652" alt="cleanshot_2026-07-29_at_02 15
53_2x"
src="https://github.com/user-attachments/assets/cfd6dbc2-f283-4379-a133-581c76990cb5"
/> | <img width="797" height="314" alt="Screenshot 2026-07-31 at 14 30
48"
src="https://github.com/user-attachments/assets/f5f1ace5-b6ef-43db-aebd-e10d31631013"
/> |



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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`.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
kemal.earth authored and GitHub committed 2026-07-31 16:20:37 +01:00
1 parent 9b51678fcf
commit bc95a2f19a
4 files changed
+55 -3

No files matched your search

@@ -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.
*
@@ -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
@@ -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)
@@ -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({