fix(platform): keep dashboard metrics on preview failures
This commit is contained in:
@@ -127,21 +127,21 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
api.dashboardSummary(),
|
||||
api.vehicleServiceSummary(),
|
||||
api.vehicleCoverage(new URLSearchParams({ limit: '8' })),
|
||||
api.vehicleRealtime(new URLSearchParams({ limit: '8' })),
|
||||
api.qualityIssues(new URLSearchParams({ limit: '5' }))
|
||||
])
|
||||
.then(([nextSummary, nextServiceSummary, coveragePage, locationPage, qualityPage]) => {
|
||||
setSummary(nextSummary);
|
||||
setServiceSummary(nextServiceSummary);
|
||||
setCoverage(coveragePage.items);
|
||||
setLocations(locationPage.items);
|
||||
setQualityIssues(qualityPage.items);
|
||||
const tasks = [
|
||||
api.dashboardSummary().then(setSummary),
|
||||
api.vehicleServiceSummary().then(setServiceSummary),
|
||||
api.vehicleCoverage(new URLSearchParams({ limit: '8' })).then((page) => setCoverage(page.items)),
|
||||
api.vehicleRealtime(new URLSearchParams({ limit: '8' })).then((page) => setLocations(page.items)),
|
||||
api.qualityIssues(new URLSearchParams({ limit: '5' })).then((page) => setQualityIssues(page.items))
|
||||
];
|
||||
Promise.allSettled(tasks)
|
||||
.then((results) => {
|
||||
const failed = results.find((result) => result.status === 'rejected');
|
||||
if (failed?.status === 'rejected') {
|
||||
const reason = failed.reason;
|
||||
Toast.error(reason instanceof Error ? reason.message : '总览数据加载失败');
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => Toast.error(error.message))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -135,6 +135,79 @@ test('dashboard renders vehicle service summary metrics', async () => {
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/vehicle-service/summary', undefined);
|
||||
});
|
||||
|
||||
test('dashboard keeps core vehicle service metrics when preview requests fail', async () => {
|
||||
window.history.replaceState(null, '', '/#/dashboard');
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
const path = String(input);
|
||||
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/issues')) {
|
||||
return {
|
||||
ok: false,
|
||||
status: 500,
|
||||
json: async () => ({
|
||||
error: { message: '质量预览失败', detail: 'TDengine timeout' },
|
||||
traceId: 'trace-quality',
|
||||
timestamp: 1783094400000
|
||||
})
|
||||
} as Response;
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
data: { items: [], total: 0, limit: 8, offset: 0 },
|
||||
traceId: 'trace-test',
|
||||
timestamp: 1783094400000
|
||||
})
|
||||
} as Response;
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
|
||||
expect(await screen.findByText('总车辆')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('1,033').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText('单源车辆')).toBeInTheDocument();
|
||||
expect(screen.getByText('391')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('opens dashboard coverage from missing source distribution', async () => {
|
||||
window.history.replaceState(null, '', '/#/dashboard');
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
|
||||
@@ -93,7 +93,7 @@ Returns VIN-level realtime rows with canonical vehicle-level `serviceStatus`. Pr
|
||||
GET /api/vehicles/coverage?keyword=粤AG18312&serviceStatus=degraded&limit=20&offset=0
|
||||
```
|
||||
|
||||
Returns VIN-level source coverage rows for the vehicle service list. Each row includes the canonical vehicle-level `serviceStatus` so frontend, exports, and external integrations share the same health definition. `serviceStatus` accepts `healthy`, `degraded`, `offline`, and `identity_required`.
|
||||
Returns VIN-level source coverage rows for the vehicle service list. Each row includes the canonical vehicle-level `serviceStatus` so frontend, exports, and external integrations share the same health definition. `serviceStatus` accepts `healthy`, `degraded`, `offline`, `no_data`, and `identity_required`.
|
||||
|
||||
Coverage summary also exposes `noDataVehicles`, so UI can show vehicles that exist in identity binding but have no GB32960, JT808, or Yutong MQTT source evidence. `/api/vehicles/coverage?serviceStatus=no_data` returns those bound vehicles for follow-up source onboarding.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user