feat(workbench): integrate runtime data and map tooling
This commit is contained in:
@@ -1,15 +1,375 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
formatHeaderDataTime,
|
||||
getMsUntilNextHeaderDataTimeTick
|
||||
getMsUntilNextHeaderDataTimeTick,
|
||||
resolveWorkbenchOperationalDate,
|
||||
resolveWorkbenchRuntimeData
|
||||
} from "./use-workbench-runtime-data";
|
||||
import type { ScheduledConditionRecord, ScheduledWorkOrderItem } from "../types";
|
||||
|
||||
const SEPTEMBER_17 = new Date("2026-09-17T02:30:00Z");
|
||||
const SEPTEMBER_18 = new Date("2026-09-18T02:30:00Z");
|
||||
|
||||
const mockCondition: ScheduledConditionRecord = {
|
||||
id: "mock-condition",
|
||||
kind: "condition",
|
||||
taskId: "scada-diagnosis",
|
||||
sessionId: "scheduled-condition-mock",
|
||||
title: "SCADA 数据诊断",
|
||||
summary: "mock",
|
||||
status: "error",
|
||||
riskLevel: "critical",
|
||||
scheduledAt: "2026-09-17T10:00:00+08:00",
|
||||
updatedAt: Date.parse("2026-09-17T10:00:00+08:00")
|
||||
};
|
||||
|
||||
const backendCondition: ScheduledConditionRecord = {
|
||||
...mockCondition,
|
||||
id: "schedule-run-1",
|
||||
taskId: "backend-schedule",
|
||||
sessionId: "scheduled-condition-schedule-run-1",
|
||||
title: "演示 合成低压调查",
|
||||
modelName: "TJWater scheduler"
|
||||
};
|
||||
|
||||
const mockWorkOrder: ScheduledWorkOrderItem = {
|
||||
id: "mock-work-order",
|
||||
kind: "work_order",
|
||||
code: "WO-MOCK",
|
||||
scheduledAt: "2026-09-17T11:00:00+08:00",
|
||||
title: "演示工单",
|
||||
summary: "mock",
|
||||
status: "pending",
|
||||
riskLevel: "normal",
|
||||
updatedAt: Date.parse("2026-09-17T10:00:00+08:00"),
|
||||
detail: "mock",
|
||||
durationMinutes: 30,
|
||||
source: "演示",
|
||||
dispatcher: "调度中心",
|
||||
priority: "normal",
|
||||
subtasks: [{
|
||||
id: "mock-subtask-1",
|
||||
title: "现场复核",
|
||||
summary: "复核设备状态并回传。",
|
||||
status: "pending",
|
||||
scheduledAt: "2026-09-17T11:00:00+08:00",
|
||||
location: "V-1",
|
||||
assignee: "现场班组",
|
||||
durationMinutes: 30,
|
||||
replyWindowMinutes: 15,
|
||||
stages: [{
|
||||
id: "dispatch",
|
||||
sequence: 1,
|
||||
title: "派发",
|
||||
status: "current",
|
||||
owner: "调度中心",
|
||||
timeLabel: "10:45",
|
||||
description: "派发现场复核任务。"
|
||||
}],
|
||||
replyRequirements: ["回传设备状态"]
|
||||
}]
|
||||
};
|
||||
|
||||
describe("workbench runtime data", () => {
|
||||
it("snaps the header time to the current five-minute interval", () => {
|
||||
expect(formatHeaderDataTime(new Date(2026, 6, 22, 15, 43, 31))).toBe("2026-07-22 15:40");
|
||||
});
|
||||
|
||||
it("preserves the backend observation minute", () => {
|
||||
expect(formatHeaderDataTime(new Date(2026, 6, 22, 15, 43, 31), false)).toBe("2026-07-22 15:43");
|
||||
});
|
||||
|
||||
it("schedules the next header refresh on the next interval boundary", () => {
|
||||
expect(getMsUntilNextHeaderDataTimeTick(new Date(2026, 6, 22, 15, 43, 30))).toBe(90_000);
|
||||
});
|
||||
|
||||
it("keeps mock conditions and shows no work orders when backend storage is not configured", () => {
|
||||
const resolved = resolveWorkbenchRuntimeData([mockCondition], {
|
||||
availability: "not_configured",
|
||||
source: "none",
|
||||
scenarioAvailability: "not_configured",
|
||||
artifactAvailability: "not_configured",
|
||||
scheduleAvailability: "not_configured",
|
||||
workOrderAvailability: "not_configured",
|
||||
alertAvailability: "not_configured",
|
||||
generatedAt: "2026-09-17T02:30:00Z",
|
||||
conditions: [],
|
||||
alerts: [],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: []
|
||||
}, { operationalDate: SEPTEMBER_17 });
|
||||
|
||||
expect(resolved.runtimeDataSource).toBe("mock");
|
||||
expect(resolved.scheduledConditions).toEqual([mockCondition]);
|
||||
expect(resolved.workbenchAlerts).toHaveLength(1);
|
||||
expect(resolved.scenarioDataSource).toBe("mock");
|
||||
expect(resolved.artifactDataSource).toBe("mock");
|
||||
expect(resolved.workOrderDataSource).toBe("none");
|
||||
});
|
||||
|
||||
it("keeps mock mode separate from the selected backend history day", () => {
|
||||
const backendAlert = {
|
||||
id: "scheduled-condition-alert-schedule-run-1",
|
||||
title: "演示 测试待复核",
|
||||
description: "运行失败",
|
||||
time: "10:25"
|
||||
};
|
||||
const backendSnapshot = {
|
||||
availability: "ready" as const,
|
||||
source: "scheduled_tasks" as const,
|
||||
scenarioAvailability: "not_configured" as const,
|
||||
artifactAvailability: "not_configured" as const,
|
||||
scheduleAvailability: "not_configured" as const,
|
||||
workOrderAvailability: "not_configured" as const,
|
||||
alertAvailability: "not_configured" as const,
|
||||
generatedAt: "2026-09-17T02:30:00Z",
|
||||
conditions: [backendCondition],
|
||||
alerts: [backendAlert],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: []
|
||||
};
|
||||
const resolved = resolveWorkbenchRuntimeData([mockCondition], backendSnapshot, {
|
||||
operationalDate: SEPTEMBER_17,
|
||||
runtimeMode: "history"
|
||||
});
|
||||
|
||||
expect(resolved.runtimeDataSource).toBe("mock");
|
||||
expect(resolved.scheduledConditions).toEqual([mockCondition]);
|
||||
expect(resolved.workbenchAlerts).not.toEqual([backendAlert]);
|
||||
expect(resolved.workbenchAlerts[0]?.id).toBe("scheduled-condition-alert-mock-condition");
|
||||
expect(resolved.alertDataSource).toBe("mock");
|
||||
expect(resolved.scenarioDataSource).toBe("mock");
|
||||
expect(resolved.workOrderDataSource).toBe("none");
|
||||
|
||||
const mockResolved = resolveWorkbenchRuntimeData([mockCondition], backendSnapshot, {
|
||||
operationalDate: SEPTEMBER_17,
|
||||
runtimeMode: "demo"
|
||||
});
|
||||
expect(mockResolved.runtimeDataSource).toBe("mock");
|
||||
expect(mockResolved.scheduledConditions).toEqual([mockCondition]);
|
||||
expect(mockResolved.workOrderDataSource).toBe("none");
|
||||
});
|
||||
|
||||
it("anchors history mode to the latest day that contains backend work orders", () => {
|
||||
const olderWorkOrder = {
|
||||
...mockWorkOrder,
|
||||
id: "older-backend-work-order",
|
||||
scheduledAt: "2026-08-03T10:05:00+08:00"
|
||||
};
|
||||
const latestWorkOrder = {
|
||||
...mockWorkOrder,
|
||||
id: "latest-backend-work-order",
|
||||
scheduledAt: "2026-08-17T17:40:00+08:00"
|
||||
};
|
||||
const futureWorkOrder = {
|
||||
...mockWorkOrder,
|
||||
id: "future-backend-work-order",
|
||||
scheduledAt: "2026-09-20T10:00:00+08:00"
|
||||
};
|
||||
const now = new Date("2026-09-18T02:30:00Z");
|
||||
const operationalDate = resolveWorkbenchOperationalDate({
|
||||
availability: "ready",
|
||||
source: "scheduled_tasks",
|
||||
scenarioAvailability: "ready",
|
||||
artifactAvailability: "ready",
|
||||
scheduleAvailability: "ready",
|
||||
workOrderAvailability: "ready",
|
||||
alertAvailability: "ready",
|
||||
generatedAt: "2026-09-18T02:30:00Z",
|
||||
conditions: [backendCondition],
|
||||
alerts: [],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: [olderWorkOrder, latestWorkOrder, futureWorkOrder]
|
||||
}, { runtimeMode: "history", now });
|
||||
|
||||
expect(operationalDate.toISOString()).toBe("2026-08-17T09:40:00.000Z");
|
||||
expect(resolveWorkbenchOperationalDate({
|
||||
availability: "ready",
|
||||
source: "scheduled_tasks",
|
||||
scenarioAvailability: "ready",
|
||||
artifactAvailability: "ready",
|
||||
scheduleAvailability: "ready",
|
||||
workOrderAvailability: "ready",
|
||||
alertAvailability: "ready",
|
||||
generatedAt: "2026-09-18T02:30:00Z",
|
||||
conditions: [],
|
||||
alerts: [],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: [olderWorkOrder, latestWorkOrder, futureWorkOrder]
|
||||
}, { runtimeMode: "demo", now }).toISOString()).toBe("2026-08-17T09:40:00.000Z");
|
||||
});
|
||||
|
||||
it("keeps current-day mock conditions when backend schedules are configured", () => {
|
||||
const currentMockCondition = {
|
||||
...mockCondition,
|
||||
id: "mock-current-day",
|
||||
scheduledAt: "2026-09-18T10:00:00+08:00"
|
||||
};
|
||||
const resolved = resolveWorkbenchRuntimeData([currentMockCondition], {
|
||||
availability: "ready",
|
||||
source: "scheduled_tasks",
|
||||
scenarioAvailability: "ready",
|
||||
artifactAvailability: "ready",
|
||||
scheduleAvailability: "ready",
|
||||
workOrderAvailability: "not_configured",
|
||||
alertAvailability: "not_configured",
|
||||
generatedAt: "2026-09-18T02:30:00Z",
|
||||
conditions: [backendCondition],
|
||||
alerts: [],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: []
|
||||
}, { operationalDate: SEPTEMBER_18 });
|
||||
|
||||
expect(resolved.scheduledConditions).toEqual([currentMockCondition]);
|
||||
expect(resolved.runtimeDataSource).toBe("mock");
|
||||
});
|
||||
|
||||
it("ignores current-day backend conditions and keeps demo conditions", () => {
|
||||
const currentMockCondition = {
|
||||
...mockCondition,
|
||||
id: "mock-current-day",
|
||||
scheduledAt: "2026-09-18T10:00:00+08:00"
|
||||
};
|
||||
const currentBackendCondition = {
|
||||
...backendCondition,
|
||||
id: "backend-current-day",
|
||||
scheduledAt: "2026-09-18T10:05:00+08:00"
|
||||
};
|
||||
const resolved = resolveWorkbenchRuntimeData([currentMockCondition], {
|
||||
availability: "ready",
|
||||
source: "scheduled_tasks",
|
||||
scenarioAvailability: "not_configured",
|
||||
artifactAvailability: "not_configured",
|
||||
scheduleAvailability: "not_configured",
|
||||
workOrderAvailability: "not_configured",
|
||||
alertAvailability: "not_configured",
|
||||
generatedAt: "2026-09-18T02:30:00Z",
|
||||
conditions: [currentBackendCondition],
|
||||
alerts: [],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: []
|
||||
}, { operationalDate: SEPTEMBER_18 });
|
||||
|
||||
expect(resolved.scheduledConditions).toEqual([currentMockCondition]);
|
||||
expect(resolved.runtimeDataSource).toBe("mock");
|
||||
});
|
||||
|
||||
it("treats an empty backend day as authoritative when backend mode is explicit", () => {
|
||||
const resolved = resolveWorkbenchRuntimeData([mockCondition, mockWorkOrder], {
|
||||
availability: "ready",
|
||||
source: "none",
|
||||
scenarioAvailability: "not_configured",
|
||||
artifactAvailability: "not_configured",
|
||||
scheduleAvailability: "not_configured",
|
||||
workOrderAvailability: "ready",
|
||||
alertAvailability: "not_configured",
|
||||
generatedAt: "2026-09-17T02:30:00Z",
|
||||
conditions: [],
|
||||
alerts: [],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: []
|
||||
}, { operationalDate: SEPTEMBER_17, runtimeMode: "backend" });
|
||||
|
||||
expect(resolved.scheduledConditions).toEqual([mockCondition]);
|
||||
expect(resolved.runtimeDataSource).toBe("mock");
|
||||
expect(resolved.workOrderDataSource).toBe("backend");
|
||||
});
|
||||
|
||||
it("uses a provisioned alert list while other sections keep their mock fallback", () => {
|
||||
const backendAlert = {
|
||||
id: "backend-alert-alarm-1",
|
||||
title: "泵站通信告警",
|
||||
description: "来自正式告警同步适配器。",
|
||||
severity: "warning" as const,
|
||||
time: "10:29"
|
||||
};
|
||||
const resolved = resolveWorkbenchRuntimeData([mockCondition], {
|
||||
availability: "ready",
|
||||
source: "none",
|
||||
scenarioAvailability: "not_configured",
|
||||
artifactAvailability: "not_configured",
|
||||
scheduleAvailability: "not_configured",
|
||||
workOrderAvailability: "not_configured",
|
||||
alertAvailability: "ready",
|
||||
generatedAt: "2026-09-17T02:30:00Z",
|
||||
conditions: [],
|
||||
alerts: [backendAlert],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: []
|
||||
}, { operationalDate: SEPTEMBER_17 });
|
||||
|
||||
expect(resolved.scheduledConditions).toEqual([mockCondition]);
|
||||
expect(resolved.runtimeDataSource).toBe("mock");
|
||||
expect(resolved.workbenchAlerts).toEqual([backendAlert]);
|
||||
});
|
||||
|
||||
it("does not invent demo alerts when the backend alert partition is empty", () => {
|
||||
const resolved = resolveWorkbenchRuntimeData([mockCondition], {
|
||||
availability: "ready",
|
||||
source: "none",
|
||||
scenarioAvailability: "not_configured",
|
||||
artifactAvailability: "not_configured",
|
||||
scheduleAvailability: "ready",
|
||||
workOrderAvailability: "not_configured",
|
||||
alertAvailability: "ready",
|
||||
generatedAt: "2026-09-17T02:30:00Z",
|
||||
conditions: [],
|
||||
alerts: [],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: []
|
||||
}, { operationalDate: SEPTEMBER_17 });
|
||||
|
||||
expect(resolved.alertDataSource).toBe("backend");
|
||||
expect(resolved.workbenchAlerts).toEqual([]);
|
||||
});
|
||||
|
||||
it("uses valid backend records retained by a degraded partition", () => {
|
||||
const backendWorkOrder = { ...mockWorkOrder, id: "backend-work-order", code: "WO-BACKEND" };
|
||||
const backendAlert = {
|
||||
id: "backend-alert-1",
|
||||
title: "后端任务告警",
|
||||
description: "合法记录与另一条坏记录同时存在。",
|
||||
severity: "warning" as const,
|
||||
time: "09-18 10:29"
|
||||
};
|
||||
const resolved = resolveWorkbenchRuntimeData([mockCondition, mockWorkOrder], {
|
||||
availability: "degraded",
|
||||
source: "none",
|
||||
scenarioAvailability: "not_configured",
|
||||
artifactAvailability: "not_configured",
|
||||
scheduleAvailability: "not_configured",
|
||||
workOrderAvailability: "degraded",
|
||||
alertAvailability: "degraded",
|
||||
generatedAt: "2026-09-18T02:30:00Z",
|
||||
conditions: [],
|
||||
alerts: [backendAlert],
|
||||
scenarios: [],
|
||||
latestAnalysisArtifact: null,
|
||||
simulationOverlay: null,
|
||||
workOrders: [backendWorkOrder]
|
||||
}, { operationalDate: SEPTEMBER_17 });
|
||||
|
||||
expect(resolved.workbenchAlerts).toEqual([backendAlert]);
|
||||
expect(resolved.alertDataSource).toBe("backend");
|
||||
expect(resolved.scheduledConditions).toEqual([mockCondition, backendWorkOrder]);
|
||||
expect(resolved.workOrderDataSource).toBe("backend");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user