refactor(ui): converge semi operations workspace

This commit is contained in:
lingniu
2026-07-19 18:56:35 +08:00
parent 0f51e3d375
commit 2c93999417
5 changed files with 198 additions and 17 deletions

View File

@@ -605,6 +605,48 @@ test('sourceReadiness reads ops source readiness plan', async () => {
expect(result.sources[0].protocol).toBe('GB32960');
});
test('normalizes nullable operations evidence arrays at the API boundary', async () => {
const fetchMock = vi.spyOn(globalThis, 'fetch')
.mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
linkHealth: null,
kafkaLag: 0,
activeConnections: 10,
capacityFindings: null,
redisOnlineKeys: 5,
tdengineWritable: true,
mysqlWritable: true,
runtime: { platformRelease: 'test', dataMode: 'production', requestTimeoutMs: 5000 }
}
})
} as Response)
.mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
totalVehicles: 1024,
boundVehicles: 1024,
identityRequiredVehicles: 0,
onlineVehicles: 234,
kafkaLag: 0,
activeConnections: 10,
redisOnlineKeys: 5,
platformRelease: 'test',
sources: null
}
})
} as Response);
const [health, readiness] = await Promise.all([api.opsHealth(), api.sourceReadiness()]);
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(health.linkHealth).toEqual([]);
expect(health.capacityFindings).toEqual([]);
expect(readiness.sources).toEqual([]);
});
test('api errors include backend message, detail, and trace id', async () => {
vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: false,

View File

@@ -179,6 +179,21 @@ function withSignal(init: RequestInit | undefined, signal?: AbortSignal) {
return signal ? { ...init, signal } : init;
}
function normalizeOpsHealth(value: OpsHealth): OpsHealth {
return {
...value,
linkHealth: Array.isArray(value.linkHealth) ? value.linkHealth : [],
capacityFindings: Array.isArray(value.capacityFindings) ? value.capacityFindings : []
};
}
function normalizeSourceReadiness(value: SourceReadinessPlan): SourceReadinessPlan {
return {
...value,
sources: Array.isArray(value.sources) ? value.sources : []
};
}
async function responseErrorMessage(response: Response) {
try {
const envelope = (await response.json()) as ApiErrorEnvelope;
@@ -347,6 +362,6 @@ export const api = {
alertEvents: (params = new URLSearchParams()) => request<Page<QualityIssueRow>>(`/api/alert-events?${params.toString()}`),
alertEventNotificationPlan: (params = new URLSearchParams()) => request<QualityNotificationPlan>(`/api/alert-events/notification-plan?${params.toString()}`),
reverseGeocode: (params = new URLSearchParams(), signal?: AbortSignal) => request<MapReverseGeocode>(`/api/map/reverse-geocode?${params.toString()}`, withSignal(undefined, signal)),
opsHealth: (signal?: AbortSignal) => request<OpsHealth>('/api/ops/health', withSignal(undefined, signal)),
sourceReadiness: (signal?: AbortSignal) => request<SourceReadinessPlan>('/api/ops/source-readiness', withSignal(undefined, signal))
opsHealth: (signal?: AbortSignal) => request<OpsHealth>('/api/ops/health', withSignal(undefined, signal)).then(normalizeOpsHealth),
sourceReadiness: (signal?: AbortSignal) => request<SourceReadinessPlan>('/api/ops/source-readiness', withSignal(undefined, signal)).then(normalizeSourceReadiness)
};