diff --git a/vehicle-data-platform/apps/web/src/pages/Quality.tsx b/vehicle-data-platform/apps/web/src/pages/Quality.tsx index 1a1d19f5..1ea28934 100644 --- a/vehicle-data-platform/apps/web/src/pages/Quality.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Quality.tsx @@ -142,6 +142,9 @@ export function Quality({ const drillProtocol = (protocol: string) => { applyFilters({ ...filters, protocol }); }; + const drillIssueType = (issueType: string) => { + applyFilters({ ...filters, issueType }); + }; return (
@@ -205,6 +208,24 @@ export function Quality({ ]} /> + + qualityIssueLabel(row.name) }, + { title: '问题数', dataIndex: 'count', width: 120 }, + { + title: '操作', + width: 160, + render: (_: unknown, row: { name: string }) => ( + + ) + } + ]} + /> +
{ const nextFilters = values as Record; 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 28f2a924..bd7a9c52 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -1657,6 +1657,76 @@ test('drills into quality issues by protocol bucket', async () => { expect(window.location.hash).toBe('#/quality?protocol=JT808'); }); +test('drills into quality issues by issue type bucket', async () => { + window.history.replaceState(null, '', '/#/quality'); + const fetchMock = 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, redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/quality/summary')) { + return { + ok: true, + json: async () => ({ + data: { + issueVehicleCount: 2, + issueRecordCount: 4, + errorCount: 2, + warningCount: 2, + protocols: [{ name: 'JT808', count: 4 }], + issueTypes: [ + { name: 'VIN_MISSING', count: 2 }, + { name: 'LINK_GAP', count: 2 } + ] + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/quality/issues')) { + return { + ok: true, + json: async () => ({ + data: { + items: [], + total: 4, + limit: 20, + offset: 0 + }, + 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: '查看 VIN 缺失质量问题' })); + + await waitFor(() => { + expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/issues?issueType=VIN_MISSING&limit=20&offset=0'), undefined); + }); + expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/summary?issueType=VIN_MISSING'), undefined); + expect(window.location.hash).toBe('#/quality?issueType=VIN_MISSING'); +}); + test('applies shareable quality filters from hash', async () => { window.history.replaceState(null, '', '/#/quality?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE'); const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {