fix(ui): clarify filtered flow viz metrics

This commit is contained in:
Tam Nhu Tran
2026-04-09 17:51:37 -04:00
parent 3687de0625
commit 94eda996e3
5 changed files with 52 additions and 18 deletions
@@ -57,7 +57,6 @@ export function FlowVizHeader({
{pausedAccountsCount > 0 && ( {pausedAccountsCount > 0 && (
<button <button
onClick={onTogglePausedAccounts} onClick={onTogglePausedAccounts}
aria-pressed={!showPausedAccounts}
className={cn( className={cn(
'flex items-center gap-1.5 text-xs font-medium transition-all duration-200 px-3 py-1.5 rounded-md border shadow-sm', 'flex items-center gap-1.5 text-xs font-medium transition-all duration-200 px-3 py-1.5 rounded-md border shadow-sm',
showPausedAccounts showPausedAccounts
@@ -212,6 +212,8 @@ export function AccountFlowViz({
providerColor={providerColor} providerColor={providerColor}
totalRequests={totalRequests} totalRequests={totalRequests}
maxRequests={maxRequests} maxRequests={maxRequests}
showVisibleMetrics={!showPausedAccounts && pausedAccountsCount > 0}
hiddenPausedCount={showPausedAccounts ? 0 : pausedAccountsCount}
isDragging={draggingId === 'provider'} isDragging={draggingId === 'provider'}
offset={getOffset('provider')} offset={getOffset('provider')}
hoveredAccount={hoveredAccount} hoveredAccount={hoveredAccount}
@@ -14,6 +14,8 @@ interface ProviderCardProps {
providerColor: string; providerColor: string;
totalRequests: number; totalRequests: number;
maxRequests: number; maxRequests: number;
showVisibleMetrics?: boolean;
hiddenPausedCount?: number;
isDragging: boolean; isDragging: boolean;
offset: DragOffset; offset: DragOffset;
hoveredAccount: number | null; hoveredAccount: number | null;
@@ -30,6 +32,8 @@ export function ProviderCard({
providerColor, providerColor,
totalRequests, totalRequests,
maxRequests, maxRequests,
showVisibleMetrics = false,
hiddenPausedCount = 0,
isDragging, isDragging,
offset, offset,
hoveredAccount, hoveredAccount,
@@ -126,11 +130,15 @@ export function ProviderCard({
<div className="space-y-2 relative z-10"> <div className="space-y-2 relative z-10">
<div className="flex justify-between items-center text-xs"> <div className="flex justify-between items-center text-xs">
<span className="text-muted-foreground">{t('flowViz.totalRequests')}</span> <span className="text-muted-foreground">
{showVisibleMetrics ? t('flowViz.visibleTotalRequests') : t('flowViz.totalRequests')}
</span>
<span className="text-foreground font-mono">{totalRequests.toLocaleString()}</span> <span className="text-foreground font-mono">{totalRequests.toLocaleString()}</span>
</div> </div>
<div className="flex justify-between items-center text-xs"> <div className="flex justify-between items-center text-xs">
<span className="text-muted-foreground">{t('flowViz.accounts')}</span> <span className="text-muted-foreground">
{showVisibleMetrics ? t('flowViz.visibleAccounts') : t('flowViz.accounts')}
</span>
<span className="text-foreground font-mono">{accounts.length}</span> <span className="text-foreground font-mono">{accounts.length}</span>
</div> </div>
<div className="w-full bg-muted dark:bg-zinc-800/50 h-1 rounded-full mt-2 overflow-hidden"> <div className="w-full bg-muted dark:bg-zinc-800/50 h-1 rounded-full mt-2 overflow-hidden">
@@ -142,6 +150,11 @@ export function ProviderCard({
}} }}
/> />
</div> </div>
{showVisibleMetrics && hiddenPausedCount > 0 && (
<div className="text-[10px] text-muted-foreground">
{t('flowViz.excludingPausedAccounts', { count: hiddenPausedCount })}
</div>
)}
</div> </div>
</div> </div>
); );
+12
View File
@@ -675,6 +675,9 @@ const resources = {
hideDetails: 'Hide Details', hideDetails: 'Hide Details',
showPausedAccounts: 'Show Paused ({{count}})', showPausedAccounts: 'Show Paused ({{count}})',
hidePausedAccounts: 'Hide Paused ({{count}})', hidePausedAccounts: 'Hide Paused ({{count}})',
visibleTotalRequests: 'Visible Requests',
visibleAccounts: 'Visible Accounts',
excludingPausedAccounts: 'Excluding {{count}} paused',
resetLayout: 'Reset layout', resetLayout: 'Reset layout',
provider: 'Provider', provider: 'Provider',
totalRequests: 'Total Requests', totalRequests: 'Total Requests',
@@ -1990,6 +1993,9 @@ const resources = {
hideDetails: '隐藏详情', hideDetails: '隐藏详情',
showPausedAccounts: '显示已暂停 ({{count}})', showPausedAccounts: '显示已暂停 ({{count}})',
hidePausedAccounts: '隐藏已暂停 ({{count}})', hidePausedAccounts: '隐藏已暂停 ({{count}})',
visibleTotalRequests: '可见请求数',
visibleAccounts: '可见账号',
excludingPausedAccounts: '已排除 {{count}} 个已暂停',
resetLayout: '重置布局', resetLayout: '重置布局',
provider: '提供商', provider: '提供商',
totalRequests: '总请求数', totalRequests: '总请求数',
@@ -3351,6 +3357,9 @@ const resources = {
hideDetails: 'Ẩn chi tiết', hideDetails: 'Ẩn chi tiết',
showPausedAccounts: 'Hiện tạm dừng ({{count}})', showPausedAccounts: 'Hiện tạm dừng ({{count}})',
hidePausedAccounts: 'Ẩn tạm dừng ({{count}})', hidePausedAccounts: 'Ẩn tạm dừng ({{count}})',
visibleTotalRequests: 'Yêu cầu hiển thị',
visibleAccounts: 'Tài khoản hiển thị',
excludingPausedAccounts: 'Đang loại trừ {{count}} tài khoản tạm dừng',
resetLayout: 'Đặt lại bố cục', resetLayout: 'Đặt lại bố cục',
provider: 'Nhà cung cấp', provider: 'Nhà cung cấp',
totalRequests: 'Tổng số yêu cầu', totalRequests: 'Tổng số yêu cầu',
@@ -4737,6 +4746,9 @@ const resources = {
hideDetails: '詳細を隠す', hideDetails: '詳細を隠す',
showPausedAccounts: '一時停止を表示 ({{count}})', showPausedAccounts: '一時停止を表示 ({{count}})',
hidePausedAccounts: '一時停止を非表示 ({{count}})', hidePausedAccounts: '一時停止を非表示 ({{count}})',
visibleTotalRequests: '表示中のリクエスト',
visibleAccounts: '表示中のアカウント',
excludingPausedAccounts: '一時停止 {{count}} 件を除外中',
resetLayout: 'レイアウトをリセット', resetLayout: 'レイアウトをリセット',
provider: 'プロバイダー', provider: 'プロバイダー',
totalRequests: '総リクエスト数', totalRequests: '総リクエスト数',
@@ -15,6 +15,11 @@ vi.mock(import('react-i18next'), async (importOriginal) => {
'flowViz.hideDetails': 'Hide Details', 'flowViz.hideDetails': 'Hide Details',
'flowViz.backToProviders': 'Back to providers', 'flowViz.backToProviders': 'Back to providers',
'flowViz.resetLayout': 'Reset layout', 'flowViz.resetLayout': 'Reset layout',
'flowViz.provider': 'Provider',
'flowViz.totalRequests': 'Total Requests',
'flowViz.accounts': 'Accounts',
'flowViz.visibleTotalRequests': 'Visible Requests',
'flowViz.visibleAccounts': 'Visible Accounts',
}; };
if (key === 'flowViz.showPausedAccounts') { if (key === 'flowViz.showPausedAccounts') {
@@ -25,6 +30,10 @@ vi.mock(import('react-i18next'), async (importOriginal) => {
return `Hide Paused (${options?.count ?? 0})`; return `Hide Paused (${options?.count ?? 0})`;
} }
if (key === 'flowViz.excludingPausedAccounts') {
return `Excluding ${options?.count ?? 0} paused`;
}
return translations[key] ?? key; return translations[key] ?? key;
}, },
}), }),
@@ -48,10 +57,8 @@ vi.mock('@/components/account/flow-viz/account-card', () => ({
), ),
})); }));
vi.mock('@/components/account/flow-viz/provider-card', () => ({ vi.mock('@/components/shared/provider-icon', () => ({
ProviderCard: ({ providerData }: { providerData: ProviderData }) => ( ProviderIcon: ({ provider }: { provider: string }) => <div>{provider}</div>,
<div data-provider-node>{providerData.accounts.length} visible accounts</div>
),
})); }));
vi.mock('@/components/account/flow-viz/connection-timeline', () => ({ vi.mock('@/components/account/flow-viz/connection-timeline', () => ({
@@ -115,30 +122,31 @@ describe('AccountFlowViz paused account visibility', () => {
expect(screen.getByRole('button', { name: 'Hide Paused (1)' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Hide Paused (1)' })).toBeInTheDocument();
expect(screen.getByText('active@company.com (active)')).toBeInTheDocument(); expect(screen.getByText('active@company.com (active)')).toBeInTheDocument();
expect(screen.getByText('paused@company.com (paused)')).toBeInTheDocument(); expect(screen.getByText('paused@company.com (paused)')).toBeInTheDocument();
expect(screen.getByText('2 visible accounts')).toBeInTheDocument(); expect(screen.getByText('Total Requests')).toBeInTheDocument();
expect(screen.getByText('Accounts')).toBeInTheDocument();
expect(screen.getByText('7')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();
expect(screen.getByText('7 timeline events')).toBeInTheDocument(); expect(screen.getByText('7 timeline events')).toBeInTheDocument();
expect(screen.getByText('paths:paused-account,active-account')).toBeInTheDocument(); expect(screen.getByText('paths:paused-account,active-account')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: 'Hide Paused (1)' })); await userEvent.click(screen.getByRole('button', { name: 'Hide Paused (1)' }));
expect(screen.getByRole('button', { name: 'Show Paused (1)' })).toHaveAttribute( expect(screen.getByRole('button', { name: 'Show Paused (1)' })).toBeInTheDocument();
'aria-pressed',
'true'
);
expect(screen.getByText('active@company.com (active)')).toBeInTheDocument(); expect(screen.getByText('active@company.com (active)')).toBeInTheDocument();
expect(screen.queryByText('paused@company.com (paused)')).not.toBeInTheDocument(); expect(screen.queryByText('paused@company.com (paused)')).not.toBeInTheDocument();
expect(screen.getByText('1 visible accounts')).toBeInTheDocument(); expect(screen.getByText('Visible Requests')).toBeInTheDocument();
expect(screen.getByText('Visible Accounts')).toBeInTheDocument();
expect(screen.getByText('Excluding 1 paused')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
expect(screen.getByText('1')).toBeInTheDocument();
expect(screen.getByText('3 timeline events')).toBeInTheDocument(); expect(screen.getByText('3 timeline events')).toBeInTheDocument();
expect(screen.getByText('paths:active-account')).toBeInTheDocument(); expect(screen.getByText('paths:active-account')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: 'Show Paused (1)' })); await userEvent.click(screen.getByRole('button', { name: 'Show Paused (1)' }));
expect(screen.getByRole('button', { name: 'Hide Paused (1)' })).toHaveAttribute( expect(screen.getByRole('button', { name: 'Hide Paused (1)' })).toBeInTheDocument();
'aria-pressed',
'false'
);
expect(screen.getByText('paused@company.com (paused)')).toBeInTheDocument(); expect(screen.getByText('paused@company.com (paused)')).toBeInTheDocument();
expect(screen.getByText('2 visible accounts')).toBeInTheDocument(); expect(screen.queryByText('Excluding 1 paused')).not.toBeInTheDocument();
}); });
it('ignores hidden paused-account drag offsets when deciding whether reset layout should stay visible', async () => { it('ignores hidden paused-account drag offsets when deciding whether reset layout should stay visible', async () => {