fix(dashboard): scope auth monitor summary by provider

- switch live header and summary cards to provider-scoped totals in detail view
- keep the provider grid on global totals when no provider is selected
- add a regression test for provider detail metric scoping
This commit is contained in:
Tam Nhu Tran
2026-04-03 18:59:54 -04:00
parent e69a58c74f
commit 5746e43a36
2 changed files with 200 additions and 9 deletions
@@ -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() {
<span>Updated {timeSinceUpdate || 'now'}</span>
</div>
<span className="text-muted-foreground/50">|</span>
<span>{t('authMonitor.accountsCount', { count: accounts.length })}</span>
<span className="font-mono">{totalRequests.toLocaleString()} req</span>
<span>{t('authMonitor.accountsCount', { count: displayedAccountCount })}</span>
<span className="font-mono">{displayedTotalRequests.toLocaleString()} req</span>
</div>
</div>
@@ -157,29 +165,29 @@ export function AuthMonitor() {
<SummaryCard
icon={<Activity className="w-4 h-4" />}
label={t('authMonitor.accounts')}
value={accounts.length}
value={displayedAccountCount}
color="var(--accent)"
/>
<SummaryCard
icon={<CheckCircle2 className="w-4 h-4" />}
label={t('authMonitor.success')}
value={totalSuccess.toLocaleString()}
value={displayedSuccess.toLocaleString()}
color={STATUS_COLORS.success}
/>
<SummaryCard
icon={<XCircle className="w-4 h-4" />}
label={t('authMonitor.failed')}
value={totalFailure.toLocaleString()}
color={totalFailure > 0 ? STATUS_COLORS.failed : undefined}
value={displayedFailure.toLocaleString()}
color={displayedFailure > 0 ? STATUS_COLORS.failed : undefined}
/>
<SummaryCard
icon={<Activity className="w-4 h-4" />}
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
}
@@ -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;
}) => (
<div>
<button type="button" onClick={onBack}>
Back to providers
</button>
<div data-testid="provider-detail">{providerData.displayName}</div>
</div>
),
}));
vi.mock(import('react-i18next'), async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
useTranslation: () => ({
t: (key: string, options?: { count?: number }) => {
const translations: Record<string, string> = {
'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(<AuthMonitor />);
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();
});
});