diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx index 11dce6fb066..1599446c9f4 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx @@ -33,6 +33,7 @@ import { parseResponseErrors, ResponseErrorRow, } from './OverviewErrors.constants' +import { formatMetricChange, formatMetricValue } from './OverviewMetrics.utils' import { OverviewTable } from './OverviewTable' import { AuthMetricsResponse, @@ -67,15 +68,6 @@ const StatCard = ({ tooltip?: string }) => { const router = useRouter() - let formattedCurrent = 'No data' - if (current !== null) { - if (suffix === 'ms') formattedCurrent = `${current.toFixed(2)}${suffix}` - else if (suffix === '%') formattedCurrent = `${current.toFixed(1)}${suffix}` - else formattedCurrent = `${Math.round(current).toLocaleString()}${suffix}` - } - const changeUnit = suffix === '%' ? ' pp' : '%' - const formattedChange = - change === null ? undefined : `${Number(change.toFixed(1)).toFixed(1)}${changeUnit}` const actions = [ { @@ -93,8 +85,8 @@ const StatCard = ({ className="pb-4" label={title} tooltip={tooltip} - diffValue={formattedChange} - value={formattedCurrent} + diffValue={formatMetricChange(change, suffix)} + value={formatMetricValue(current, suffix)} /> diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.utils.ts b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.utils.ts new file mode 100644 index 00000000000..bcdc7c0155e --- /dev/null +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.utils.ts @@ -0,0 +1,13 @@ +export function formatMetricValue(value: number | null, suffix = ''): string { + if (value === null) return 'No data' + if (suffix === 'ms') return `${value.toFixed(2)}${suffix}` + if (suffix === '%') return `${value.toFixed(1)}${suffix}` + return `${Math.round(value).toLocaleString()}${suffix}` +} + +export function formatMetricChange(change: number | null, suffix = ''): string | undefined { + if (change === null) return undefined + const unit = suffix === '%' ? ' pp' : '%' + const roundedChange = Number(change.toFixed(1)) + return `${roundedChange.toFixed(1)}${unit}` +} diff --git a/apps/studio/tests/components/Auth/OverviewMetrics.utils.test.ts b/apps/studio/tests/components/Auth/OverviewMetrics.utils.test.ts new file mode 100644 index 00000000000..2c07f9c0707 --- /dev/null +++ b/apps/studio/tests/components/Auth/OverviewMetrics.utils.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest' + +import { + formatMetricChange, + formatMetricValue, +} from '@/components/interfaces/Auth/Overview/OverviewMetrics.utils' + +describe('formatMetricValue', () => { + it.each(['', '%', 'ms'])('shows missing data without the %s suffix', (suffix) => { + expect(formatMetricValue(null, suffix)).toBe('No data') + }) + + it.each([ + [0, '%', '0.0%'], + [0.2, '%', '0.2%'], + [99.96, '%', '100.0%'], + [0, 'ms', '0.00ms'], + [12.346, 'ms', '12.35ms'], + [12.4, '', '12'], + [12.6, '', '13'], + [12.6, ' users', '13 users'], + ])('formats %s with suffix %s as %s', (value, suffix, expected) => { + expect(formatMetricValue(value, suffix)).toBe(expected) + }) + + it('uses locale separators for counts by default', () => { + expect(formatMetricValue(12345.6)).toBe((12346).toLocaleString()) + }) +}) + +describe('formatMetricChange', () => { + it.each(['', '%', 'ms'])('omits missing changes with suffix %s', (suffix) => { + expect(formatMetricChange(null, suffix)).toBeUndefined() + }) + + it.each([ + [0.2, '%', '0.2 pp'], + [-0.2, '%', '-0.2 pp'], + [12.36, '%', '12.4 pp'], + [25, '', '25.0%'], + [-25, 'ms', '-25.0%'], + [0, '%', '0.0 pp'], + [-0, '%', '0.0 pp'], + [-0.01, '%', '0.0 pp'], + [-0.01, '', '0.0%'], + ])('formats change %s with suffix %s as %s', (change, suffix, expected) => { + expect(formatMetricChange(change, suffix)).toBe(expected) + }) + + it('uses relative percentage changes by default', () => { + expect(formatMetricChange(25)).toBe('25.0%') + }) +})