From d8bea4eb0cca76fe1530498bc1070ad6020894a5 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 09:05:23 +0800 Subject: [PATCH] feat(platform): add vehicle service posture drilldowns --- .../apps/web/src/pages/Dashboard.tsx | 3 + .../apps/web/src/test/App.test.tsx | 97 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index da669e11..61b2b7c8 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -181,10 +181,13 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on {vehicleServiceOnlineText(serviceSummary, summary)} + {formatCount(serviceSummary?.multiSourceVehicles)} 多源覆盖 + 0 ? 'orange' : 'green'}> {formatCount(summary?.issueVehicles)} 质量关注 + 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)} diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx index e6c3daea..fb749708 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -139,6 +139,16 @@ test('dashboard presents one vehicle service operating posture', async () => { window.history.replaceState(null, '', '/#/dashboard'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { const path = String(input); + if (path.includes('/api/ops/health')) { + return { + ok: true, + json: async () => ({ + data: { linkHealth: [], kafkaLag: 0, activeConnections: 0, capacityFindings: [], redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } if (path.includes('/api/dashboard/summary')) { return { ok: true, @@ -197,6 +207,93 @@ test('dashboard presents one vehicle service operating posture', async () => { expect(screen.getByText('7 质量关注')).toBeInTheDocument(); }); +test.each([ + { buttonName: '查看在线车辆', expectedHash: '#/vehicles?online=online' }, + { buttonName: '查看多源车辆', expectedHash: '#/vehicles?coverage=multi' }, + { buttonName: '查看质量关注', expectedHash: '#/quality' } +])('dashboard posture opens %s', async ({ buttonName, expectedHash }) => { + window.history.replaceState(null, '', '/#/dashboard'); + vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { + const path = String(input); + if (path.includes('/api/ops/health')) { + return { + ok: true, + json: async () => ({ + data: { linkHealth: [], kafkaLag: 0, activeConnections: 0, capacityFindings: [], redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/dashboard/summary')) { + return { + ok: true, + json: async () => ({ + data: { + onlineVehicles: 3, + activeToday: 4, + frameToday: 1286320, + issueVehicles: 7, + kafkaLag: 0, + protocols: [], + serviceStatuses: [], + linkHealth: [] + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/vehicle-service/summary')) { + return { + ok: true, + json: async () => ({ + data: { + totalVehicles: 1033, + boundVehicles: 1024, + onlineVehicles: 208, + singleSourceVehicles: 391, + multiSourceVehicles: 181, + noDataVehicles: 461, + identityRequiredVehicles: 9, + serviceStatuses: [], + protocols: [], + missingSources: [] + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/quality/summary')) { + return { + ok: true, + json: async () => ({ + data: { issueVehicleCount: 7, issueRecordCount: 9, errorCount: 1, warningCount: 8, protocols: [], issueTypes: [] }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + return { + ok: true, + json: async () => ({ + data: { items: [], total: 0, limit: 20, offset: 0 }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + }); + + render(); + + fireEvent.click(await screen.findByRole('button', { name: buttonName })); + + await waitFor(() => { + expect(window.location.hash).toBe(expectedHash); + }); +}); + test('dashboard keeps core vehicle service metrics when preview requests fail', async () => { window.history.replaceState(null, '', '/#/dashboard'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {