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(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
const tasks = [
|
||||||
api.dashboardSummary(),
|
api.dashboardSummary().then(setSummary),
|
||||||
api.vehicleServiceSummary(),
|
api.vehicleServiceSummary().then(setServiceSummary),
|
||||||
api.vehicleCoverage(new URLSearchParams({ limit: '8' })),
|
api.vehicleCoverage(new URLSearchParams({ limit: '8' })).then((page) => setCoverage(page.items)),
|
||||||
api.vehicleRealtime(new URLSearchParams({ limit: '8' })),
|
api.vehicleRealtime(new URLSearchParams({ limit: '8' })).then((page) => setLocations(page.items)),
|
||||||
api.qualityIssues(new URLSearchParams({ limit: '5' }))
|
api.qualityIssues(new URLSearchParams({ limit: '5' })).then((page) => setQualityIssues(page.items))
|
||||||
])
|
];
|
||||||
.then(([nextSummary, nextServiceSummary, coveragePage, locationPage, qualityPage]) => {
|
Promise.allSettled(tasks)
|
||||||
setSummary(nextSummary);
|
.then((results) => {
|
||||||
setServiceSummary(nextServiceSummary);
|
const failed = results.find((result) => result.status === 'rejected');
|
||||||
setCoverage(coveragePage.items);
|
if (failed?.status === 'rejected') {
|
||||||
setLocations(locationPage.items);
|
const reason = failed.reason;
|
||||||
setQualityIssues(qualityPage.items);
|
Toast.error(reason instanceof Error ? reason.message : '总览数据加载失败');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error: Error) => Toast.error(error.message))
|
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,79 @@ test('dashboard renders vehicle service summary metrics', async () => {
|
|||||||
expect(fetchMock).toHaveBeenCalledWith('/api/vehicle-service/summary', undefined);
|
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 () => {
|
test('opens dashboard coverage from missing source distribution', async () => {
|
||||||
window.history.replaceState(null, '', '/#/dashboard');
|
window.history.replaceState(null, '', '/#/dashboard');
|
||||||
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
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
|
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.
|
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