From 1fb40fd5fe976f4652d0158487639ef82c895f46 Mon Sep 17 00:00:00 2001 From: Shinwoo PARK Date: Sat, 9 May 2026 16:54:32 +0900 Subject: [PATCH] [verified] refactor: unify dashboard rate windows --- apps/web/src/App.tsx | 126 +++++++------ apps/web/src/lib/utils.ts | 175 ++++++++++++++++++ .../test/dashboard-capacity-windows.test.js | 20 ++ apps/web/test/dashboard-card-copy.test.js | 4 +- apps/web/test/mobile-overflow-guards.test.js | 9 +- apps/web/test/usage-window-helpers.test.js | 175 ++++++++++++++++++ 6 files changed, 445 insertions(+), 64 deletions(-) create mode 100644 apps/web/test/dashboard-capacity-windows.test.js create mode 100644 apps/web/test/usage-window-helpers.test.js diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 6183d2c..44b52e8 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -30,7 +30,7 @@ import { import { toast, Toaster } from 'sonner'; import { api } from '@/lib/api'; import { clearToken, getToken, setToken } from '@/lib/storage'; -import { flattenNumericMetrics, formatDate, titleizeMetric } from '@/lib/utils'; +import { summarizeUsageWindows, formatDate, formatDurationSeconds } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Card, @@ -568,10 +568,6 @@ function Dashboard() { onError: (error: Error) => toast.error(error.message), }); - const metricCards = useMemo(() => { - return flattenNumericMetrics(summaryQuery.data?.aggregatedUsage).slice(0, 6); - }, [summaryQuery.data?.aggregatedUsage]); - if (summaryQuery.isLoading || userQuery.isLoading) { return (
@@ -601,12 +597,27 @@ function Dashboard() { const summary = summaryQuery.data!; const user = userQuery.data!; - const firstMetric = metricCards[0]?.value ?? 0; - const secondMetric = metricCards[1]?.value ?? 0; - const progressValue = - firstMetric + secondMetric > 0 - ? (firstMetric / (firstMetric + secondMetric)) * 100 - : 0; + const usageWindows = summarizeUsageWindows( + summary.accounts.map((account) => account.usage), + ); + const windowCards = [ + { + title: 'Primary window', + tone: 'text-sky-300', + window: usageWindows.primary, + }, + { + title: 'Secondary window', + tone: 'text-violet-300', + window: usageWindows.secondary, + }, + ].filter( + (item): item is { + title: string; + tone: string; + window: NonNullable; + } => item.window !== null, + ); return (
@@ -647,25 +658,53 @@ function Dashboard() { Unified capacity -
-
-
- {firstMetric.toLocaleString()} -
-
- {titleizeMetric(metricCards[0]?.label ?? 'Primary metric')} -
+ {windowCards.length > 0 ? ( +
+ {windowCards.map((item) => { + const usedPercent = item.window.usedPercent; + const progressValue = + usedPercent !== null && usedPercent !== undefined + ? Math.max(0, Math.min(100, usedPercent)) + : 0; + + return ( +
+
+
+
{item.title}
+
+ {usedPercent !== null && usedPercent !== undefined + ? `${usedPercent.toFixed(0)}%` + : '—'} +
+
+ {item.window.accountCount + ? `Window data from ${item.window.accountCount} account${item.window.accountCount === 1 ? '' : 's'}` + : 'Used'} +
+
+
+ {item.window.limitWindowSeconds !== null ? ( +
{formatDurationSeconds(item.window.limitWindowSeconds)}
+ ) : null} +
+ Resets {formatDate(item.window.resetAt)} +
+
+
+ +
+ ); + })}
-
-
- {secondMetric.toLocaleString()} -
-
- {titleizeMetric(metricCards[1]?.label ?? 'Secondary metric')} -
+ ) : ( +
+ No rate-limit window data yet. Connect an OpenAI account and refresh to load Codex usage windows.
-
- + )}
Accounts: {summary.totals.totalAccounts} Healthy: {summary.totals.activeAccounts} @@ -705,35 +744,6 @@ function Dashboard() {
- - - Usage metrics - - - {metricCards.length === 0 ? ( -
- No usage data yet. Connect an OpenAI account and complete the sign-in flow to start refreshing. -
- ) : ( -
- {metricCards.map((metric) => ( -
-
- {titleizeMetric(metric.label)} -
-
- {metric.value.toLocaleString()} -
-
- ))} -
- )} -
-
- Connected OpenAI accounts diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index ae6ea3e..3f6df84 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -13,6 +13,181 @@ export function formatDate(value: string | null) { }).format(new Date(value)); } +type UsageWindowSummary = { + usedPercent: number | null; + limitWindowSeconds: number | null; + resetAt: string | null; +}; + +type AggregatedUsageWindowSummary = UsageWindowSummary & { + accountCount: number; +}; + +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +function toFiniteNumber(value: unknown) { + return typeof value === 'number' && Number.isFinite(value) ? value : null; +} + +function getPath(value: unknown, path: string[]) { + let current: unknown = value; + for (const segment of path) { + if (!isRecord(current) || !(segment in current)) { + return null; + } + current = current[segment]; + } + return current; +} + +function normalizeResetAt(value: unknown) { + if (typeof value === 'string' && value.trim()) { + return value; + } + + if (typeof value === 'number' && Number.isFinite(value)) { + return new Date(value * 1000).toISOString(); + } + + return null; +} + +function extractWindow(value: unknown, paths: string[][]): UsageWindowSummary | null { + for (const path of paths) { + const windowValue = getPath(value, path); + + if (!isRecord(windowValue)) { + continue; + } + + const summary = { + usedPercent: toFiniteNumber(windowValue.used_percent), + limitWindowSeconds: + toFiniteNumber(windowValue.limit_window_seconds) ?? + (toFiniteNumber(windowValue.window_minutes) !== null + ? toFiniteNumber(windowValue.window_minutes)! * 60 + : null), + resetAt: normalizeResetAt(windowValue.reset_at ?? windowValue.resets_at), + }; + + if ( + summary.usedPercent === null && + summary.limitWindowSeconds === null && + summary.resetAt === null + ) { + continue; + } + + return summary; + } + + return null; +} + +export function extractUsageWindows(value: unknown) { + return { + primary: extractWindow(value, [ + ['rate_limit', 'primary_window'], + ['rate_limits', 'primary'], + ['primary_window'], + ]), + secondary: extractWindow(value, [ + ['rate_limit', 'secondary_window'], + ['rate_limits', 'secondary'], + ['secondary_window'], + ]), + }; +} + +function average(values: number[]) { + if (values.length === 0) { + return null; + } + + return values.reduce((sum, value) => sum + value, 0) / values.length; +} + +function earliestDate(values: Array) { + const validDates = values + .filter((value): value is string => Boolean(value)) + .map((value) => ({ value, timestamp: Date.parse(value) })) + .filter((value) => Number.isFinite(value.timestamp)) + .sort((left, right) => left.timestamp - right.timestamp); + + return validDates[0]?.value ?? null; +} + +function summarizeWindowCollection( + windows: Array, +): AggregatedUsageWindowSummary | null { + const presentWindows = windows.filter( + (window): window is UsageWindowSummary => window !== null, + ); + + if (presentWindows.length === 0) { + return null; + } + + const usedPercentValues = presentWindows + .map((window) => window.usedPercent) + .filter((value): value is number => value !== null); + + const limitWindowValues = presentWindows + .map((window) => window.limitWindowSeconds) + .filter((value): value is number => value !== null); + + const uniqueLimitWindowValues = [...new Set(limitWindowValues)]; + + return { + accountCount: presentWindows.length, + usedPercent: average(usedPercentValues), + limitWindowSeconds: + limitWindowValues.length === presentWindows.length && + uniqueLimitWindowValues.length === 1 + ? uniqueLimitWindowValues[0] + : null, + resetAt: earliestDate(presentWindows.map((window) => window.resetAt)), + }; +} + +export function summarizeUsageWindows(values: Array) { + const extractedWindows = values.map((value) => extractUsageWindows(value)); + + return { + primary: summarizeWindowCollection( + extractedWindows.map((windows) => windows.primary), + ), + secondary: summarizeWindowCollection( + extractedWindows.map((windows) => windows.secondary), + ), + }; +} + +export function formatDurationSeconds(value: number | null) { + if (!value || value <= 0) { + return 'Unknown window'; + } + + if (value % 86_400 === 0) { + const days = value / 86_400; + return `${days}d window`; + } + + if (value % 3_600 === 0) { + const hours = value / 3_600; + return `${hours}h window`; + } + + if (value % 60 === 0) { + const minutes = value / 60; + return `${minutes}m window`; + } + + return `${value}s window`; +} + export function flattenNumericMetrics( value: unknown, path: string[] = [], diff --git a/apps/web/test/dashboard-capacity-windows.test.js b/apps/web/test/dashboard-capacity-windows.test.js new file mode 100644 index 0000000..0be1c3d --- /dev/null +++ b/apps/web/test/dashboard-capacity-windows.test.js @@ -0,0 +1,20 @@ +import { describe, expect, test } from 'bun:test'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8'); + +describe('dashboard unified capacity windows', () => { + test('shows primary and secondary windows inside unified capacity and removes usage metrics card', () => { + expect(appSource).toContain('Unified capacity'); + expect(appSource).toContain('Primary window'); + expect(appSource).toContain('Secondary window'); + expect(appSource).toContain('const windowCards = ['); + expect(appSource).toContain("} => item.window !== null,"); + expect(appSource).toContain('item.window.limitWindowSeconds !== null ? ('); + expect(appSource).not.toContain('Usage metrics'); + expect(appSource).not.toContain('flattenNumericMetrics(summaryQuery.data?.aggregatedUsage).slice(0, 6)'); + expect(appSource).not.toContain('const firstMetric = metricCards[0]?.value ?? 0;'); + expect(appSource).not.toContain('const secondMetric = metricCards[1]?.value ?? 0;'); + }); +}); diff --git a/apps/web/test/dashboard-card-copy.test.js b/apps/web/test/dashboard-card-copy.test.js index 60266bf..38dfad9 100644 --- a/apps/web/test/dashboard-card-copy.test.js +++ b/apps/web/test/dashboard-card-copy.test.js @@ -7,8 +7,10 @@ const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8'); describe('dashboard card copy', () => { test('removes verbose dashboard card descriptions and keeps concise labels', () => { expect(appSource).toContain('Unified capacity'); - expect(appSource).toContain('Usage metrics'); expect(appSource).toContain('Connected OpenAI accounts'); + expect(appSource).toContain('Primary window'); + expect(appSource).toContain('Secondary window'); + expect(appSource).not.toContain('Usage metrics'); expect(appSource).toContain(">Merged by default. Inspect each account below.<"); expect(appSource).not.toContain( 'Fast glance card for the first two numeric metrics extracted from the merged usage payload.', diff --git a/apps/web/test/mobile-overflow-guards.test.js b/apps/web/test/mobile-overflow-guards.test.js index 92d0333..762399d 100644 --- a/apps/web/test/mobile-overflow-guards.test.js +++ b/apps/web/test/mobile-overflow-guards.test.js @@ -5,11 +5,10 @@ import { join } from 'node:path'; const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8'); describe('mobile overflow guards', () => { - test('usage metrics cards allow long metric labels to wrap on mobile', () => { - expect(appSource).toContain('className="mt-6 min-w-0"'); - expect(appSource).toContain('className="grid gap-3 sm:grid-cols-2"'); - expect(appSource).toContain('className="min-w-0 rounded-2xl border border-white/10 bg-white/4 p-4"'); - expect(appSource).toContain('className="text-sm text-slate-400 break-words"'); + test('unified capacity window cards stay in a responsive two-column grid', () => { + expect(appSource).toContain('className="grid gap-4 md:grid-cols-2"'); + expect(appSource).toContain('className="rounded-2xl border border-white/10 bg-white/4 p-4"'); + expect(appSource).toContain('className="text-sm text-slate-400">{item.title}
'); }); test('connected account tabs no longer render a side-by-side payload column', () => { diff --git a/apps/web/test/usage-window-helpers.test.js b/apps/web/test/usage-window-helpers.test.js new file mode 100644 index 0000000..c2a4e1c --- /dev/null +++ b/apps/web/test/usage-window-helpers.test.js @@ -0,0 +1,175 @@ +import { describe, expect, test } from 'bun:test'; +import { + extractUsageWindows, + formatDurationSeconds, + summarizeUsageWindows, +} from '../src/lib/utils'; + +describe('usage window helpers', () => { + test('extracts primary and secondary windows from codex rate_limit payloads', () => { + const windows = extractUsageWindows({ + rate_limit: { + primary_window: { + used_percent: 14, + limit_window_seconds: 18_000, + reset_at: '2026-05-09T12:00:00.000Z', + }, + secondary_window: { + used_percent: 19, + limit_window_seconds: 604_800, + reset_at: '2026-05-16T12:00:00.000Z', + }, + }, + }); + + expect(windows).toEqual({ + primary: { + usedPercent: 14, + limitWindowSeconds: 18_000, + resetAt: '2026-05-09T12:00:00.000Z', + }, + secondary: { + usedPercent: 19, + limitWindowSeconds: 604_800, + resetAt: '2026-05-16T12:00:00.000Z', + }, + }); + }); + + test('formats canonical codex limit windows into readable labels', () => { + expect(formatDurationSeconds(18_000)).toBe('5h window'); + expect(formatDurationSeconds(604_800)).toBe('7d window'); + expect(formatDurationSeconds(null)).toBe('Unknown window'); + }); + + test('summarizes windows across multiple account payloads without summing percentages', () => { + const windows = summarizeUsageWindows([ + { + rate_limit: { + primary_window: { + used_percent: 20, + limit_window_seconds: 18_000, + reset_at: '2026-05-09T12:00:00.000Z', + }, + secondary_window: { + used_percent: 40, + limit_window_seconds: 604_800, + reset_at: '2026-05-16T12:00:00.000Z', + }, + }, + }, + { + rate_limit: { + primary_window: { + used_percent: 60, + limit_window_seconds: 18_000, + reset_at: '2026-05-09T10:00:00.000Z', + }, + secondary_window: { + used_percent: 80, + limit_window_seconds: 604_800, + reset_at: '2026-05-15T10:00:00.000Z', + }, + }, + }, + ]); + + expect(windows).toEqual({ + primary: { + accountCount: 2, + usedPercent: 40, + limitWindowSeconds: 18_000, + resetAt: '2026-05-09T10:00:00.000Z', + }, + secondary: { + accountCount: 2, + usedPercent: 60, + limitWindowSeconds: 604_800, + resetAt: '2026-05-15T10:00:00.000Z', + }, + }); + }); + + test('keeps window summaries null-safe for partial or inconsistent account data', () => { + const windows = summarizeUsageWindows([ + { + rate_limit: { + primary_window: { + limit_window_seconds: 18_000, + reset_at: '2026-05-09T12:00:00.000Z', + }, + }, + }, + { + rate_limit: { + primary_window: { + used_percent: 50, + reset_at: '2026-05-09T11:00:00.000Z', + }, + secondary_window: { + used_percent: 22, + limit_window_seconds: 604_800, + reset_at: '2026-05-16T12:00:00.000Z', + }, + }, + }, + { + rate_limit: { + secondary_window: { + used_percent: 44, + limit_window_seconds: 86_400, + reset_at: '2026-05-15T12:00:00.000Z', + }, + }, + }, + ]); + + expect(windows).toEqual({ + primary: { + accountCount: 2, + usedPercent: 50, + limitWindowSeconds: null, + resetAt: '2026-05-09T11:00:00.000Z', + }, + secondary: { + accountCount: 2, + usedPercent: 33, + limitWindowSeconds: null, + resetAt: '2026-05-15T12:00:00.000Z', + }, + }); + }); + + test('ignores empty window shells with no meaningful values', () => { + expect( + extractUsageWindows({ + rate_limit: { + primary_window: {}, + secondary_window: null, + }, + }), + ).toEqual({ primary: null, secondary: null }); + }); + + test('falls back to later candidate paths when earlier window shells are empty', () => { + expect( + extractUsageWindows({ + rate_limit: { + primary_window: {}, + }, + primary_window: { + used_percent: 42, + limit_window_seconds: 18_000, + reset_at: '2026-05-09T12:00:00.000Z', + }, + }), + ).toEqual({ + primary: { + usedPercent: 42, + limitWindowSeconds: 18_000, + resetAt: '2026-05-09T12:00:00.000Z', + }, + secondary: null, + }); + }); + });