diff --git a/ui/src/components/monitoring/auth-monitor/index.tsx b/ui/src/components/monitoring/auth-monitor/index.tsx index 04f05f5a..615410dd 100644 --- a/ui/src/components/monitoring/auth-monitor/index.tsx +++ b/ui/src/components/monitoring/auth-monitor/index.tsx @@ -22,6 +22,7 @@ import { useAuthMonitorData } from './hooks'; import { LivePulse } from './components/live-pulse'; import { ProviderCard } from './components/provider-card'; import { SummaryCard } from './components/summary-card'; +import { getSuccessRate } from './utils'; const STORAGE_KEY = 'auth-monitor-selected-provider'; @@ -78,6 +79,13 @@ export function AuthMonitor() { const selectedProviderData = effectiveProvider ? providerStats.find((ps) => ps.provider === effectiveProvider) : null; + const displayedAccountCount = selectedProviderData?.accountCount ?? accounts.length; + const displayedSuccess = selectedProviderData?.successCount ?? totalSuccess; + const displayedFailure = selectedProviderData?.failureCount ?? totalFailure; + const displayedTotalRequests = selectedProviderData?.totalRequests ?? totalRequests; + const displayedSuccessRate = selectedProviderData + ? getSuccessRate(selectedProviderData.successCount, selectedProviderData.failureCount) + : overallSuccessRate; const handlePauseToggle = (accountIds: string[], paused: boolean) => { if ( @@ -147,8 +155,8 @@ export function AuthMonitor() { Updated {timeSinceUpdate || 'now'} | - {t('authMonitor.accountsCount', { count: accounts.length })} - {totalRequests.toLocaleString()} req + {t('authMonitor.accountsCount', { count: displayedAccountCount })} + {displayedTotalRequests.toLocaleString()} req @@ -157,29 +165,29 @@ export function AuthMonitor() { } label={t('authMonitor.accounts')} - value={accounts.length} + value={displayedAccountCount} color="var(--accent)" /> } label={t('authMonitor.success')} - value={totalSuccess.toLocaleString()} + value={displayedSuccess.toLocaleString()} color={STATUS_COLORS.success} /> } label={t('authMonitor.failed')} - value={totalFailure.toLocaleString()} - color={totalFailure > 0 ? STATUS_COLORS.failed : undefined} + value={displayedFailure.toLocaleString()} + color={displayedFailure > 0 ? STATUS_COLORS.failed : undefined} /> } label={t('authMonitor.successRate')} - value={`${overallSuccessRate}%`} + value={`${displayedSuccessRate}%`} color={ - overallSuccessRate === 100 + displayedSuccessRate === 100 ? STATUS_COLORS.success - : overallSuccessRate >= 95 + : displayedSuccessRate >= 95 ? STATUS_COLORS.degraded : STATUS_COLORS.failed } diff --git a/ui/tests/unit/ui/components/monitoring/auth-monitor/auth-monitor.test.tsx b/ui/tests/unit/ui/components/monitoring/auth-monitor/auth-monitor.test.tsx new file mode 100644 index 00000000..c808338d --- /dev/null +++ b/ui/tests/unit/ui/components/monitoring/auth-monitor/auth-monitor.test.tsx @@ -0,0 +1,183 @@ +import { render, screen, userEvent, within } from '@tests/setup/test-utils'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { AuthMonitor } from '@/components/monitoring/auth-monitor'; +import type { AuthMonitorData } from '@/components/monitoring/auth-monitor/hooks'; + +vi.mock('@/components/account-flow-viz', () => ({ + AccountFlowViz: ({ + providerData, + onBack, + }: { + providerData: { displayName: string }; + onBack: () => void; + }) => ( +
+ +
{providerData.displayName}
+
+ ), +})); + +vi.mock(import('react-i18next'), async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + useTranslation: () => ({ + t: (key: string, options?: { count?: number }) => { + const translations: Record = { + 'authMonitor.accounts': 'Accounts', + 'authMonitor.success': 'Success', + 'authMonitor.failed': 'Failed', + 'authMonitor.successRate': 'Success Rate', + }; + + if (key === 'authMonitor.accountsCount') { + return `${options?.count ?? 0} accounts`; + } + + return translations[key] ?? key; + }, + }), + }; +}); + +const { useAuthMonitorDataMock } = vi.hoisted(() => ({ + useAuthMonitorDataMock: vi.fn<() => AuthMonitorData>(), +})); + +vi.mock('@/components/monitoring/auth-monitor/hooks', () => ({ + useAuthMonitorData: useAuthMonitorDataMock, +})); + +function getSummaryRow() { + const accountsLabel = screen.getByText('Accounts'); + const summaryRow = accountsLabel.parentElement?.parentElement?.parentElement; + + if (!summaryRow) { + throw new Error('Unable to locate the auth monitor summary row.'); + } + + return summaryRow; +} + +function getSummaryCard(label: string) { + const labelNode = within(getSummaryRow()).getByText(label); + const card = labelNode.closest('div')?.parentElement; + + if (!card) { + throw new Error(`Unable to locate summary card for label "${label}".`); + } + + return card; +} + +const providerStats = [ + { + provider: 'gemini', + displayName: 'Google Gemini', + totalRequests: 15, + successCount: 12, + failureCount: 3, + accountCount: 2, + accounts: [ + { + id: 'gemini-primary', + email: 'gemini-primary@example.com', + tokenFile: '/tmp/gemini-primary.json', + provider: 'gemini', + displayName: 'Google Gemini', + isDefault: true, + successCount: 10, + failureCount: 2, + color: '#4285F4', + }, + { + id: 'gemini-secondary', + email: 'gemini-secondary@example.com', + tokenFile: '/tmp/gemini-secondary.json', + provider: 'gemini', + displayName: 'Google Gemini', + isDefault: false, + successCount: 2, + failureCount: 1, + color: '#34A853', + }, + ], + }, + { + provider: 'codex', + displayName: 'OpenAI Codex', + totalRequests: 27, + successCount: 23, + failureCount: 4, + accountCount: 2, + accounts: [ + { + id: 'codex-primary', + email: 'codex-primary@example.com', + tokenFile: '/tmp/codex-primary.json', + provider: 'codex', + displayName: 'OpenAI Codex', + isDefault: true, + successCount: 15, + failureCount: 2, + color: '#10a37f', + }, + { + id: 'codex-secondary', + email: 'codex-secondary@example.com', + tokenFile: '/tmp/codex-secondary.json', + provider: 'codex', + displayName: 'OpenAI Codex', + isDefault: false, + successCount: 8, + failureCount: 2, + color: '#0f766e', + }, + ], + }, +] satisfies AuthMonitorData['providerStats']; + +const authMonitorData: AuthMonitorData = { + accounts: providerStats.flatMap((provider) => provider.accounts), + totalSuccess: 35, + totalFailure: 7, + totalRequests: 42, + providerStats, + overallSuccessRate: 83, + isLoading: false, + error: null, + timeSinceUpdate: '2s ago', +}; + +describe('AuthMonitor', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(window.localStorage.getItem).mockReturnValue(null); + useAuthMonitorDataMock.mockReturnValue(authMonitorData); + }); + + it('scopes header and summary metrics to the selected provider detail view', async () => { + render(); + + expect(screen.getByText('4 accounts')).toBeInTheDocument(); + expect(screen.getByText('42 req')).toBeInTheDocument(); + expect(within(getSummaryCard('Accounts')).getByText('4')).toBeInTheDocument(); + expect(within(getSummaryCard('Success')).getByText('35')).toBeInTheDocument(); + expect(within(getSummaryCard('Failed')).getByText('7')).toBeInTheDocument(); + expect(within(getSummaryCard('Success Rate')).getByText('83%')).toBeInTheDocument(); + + await userEvent.click(screen.getByRole('button', { name: /Google Gemini/i })); + + expect(screen.getByTestId('provider-detail')).toHaveTextContent('Google Gemini'); + expect(screen.getByText('2 accounts')).toBeInTheDocument(); + expect(screen.getByText('15 req')).toBeInTheDocument(); + expect(within(getSummaryCard('Accounts')).getByText('2')).toBeInTheDocument(); + expect(within(getSummaryCard('Success')).getByText('12')).toBeInTheDocument(); + expect(within(getSummaryCard('Failed')).getByText('3')).toBeInTheDocument(); + expect(within(getSummaryCard('Success Rate')).getByText('80%')).toBeInTheDocument(); + }); +});