feat(workbench): integrate runtime data and map tooling

This commit is contained in:
2026-09-22 10:45:46 +08:00
parent bf44cc0f25
commit bebcf2a06c
163 changed files with 15642 additions and 1312 deletions
+86 -1
View File
@@ -1,6 +1,30 @@
import type { Page } from "@playwright/test";
export async function mockScadaApi(page: Page, devices: unknown[] = []) {
const emptyRuntimeSnapshot = {
availability: "not_configured",
source: "none",
scenario_availability: "not_configured",
artifact_availability: "not_configured",
schedule_availability: "not_configured",
work_order_availability: "not_configured",
alert_availability: "not_configured",
generated_at: "2026-09-17T02:30:00Z",
conditions: [],
alerts: [],
scenarios: [],
latest_analysis: null,
work_orders: []
};
export async function mockScadaApi(
page: Page,
devices: unknown[] = [],
runtimeSnapshot: unknown = emptyRuntimeSnapshot,
readings: unknown[] = [],
projectPresentation?: { metadata: unknown; accessContext: unknown },
analysisRuns: Record<string, unknown> = {},
analysisHistory: unknown[] = []
) {
await page.route("https://tjwater-api.invalid/**", async (route) => {
const url = new URL(route.request().url());
if (url.pathname === "/api/v1/projects") {
@@ -22,6 +46,67 @@ export async function mockScadaApi(page: Page, devices: unknown[] = []) {
});
return;
}
if (url.pathname === "/api/v1/projects/current/metadata" && projectPresentation) {
await route.fulfill({
contentType: "application/json",
body: JSON.stringify(projectPresentation.metadata)
});
return;
}
if (url.pathname === "/api/v1/access-context" && projectPresentation) {
await route.fulfill({
contentType: "application/json",
body: JSON.stringify(projectPresentation.accessContext)
});
return;
}
if (url.pathname === "/api/v1/timeseries/scada-readings") {
await route.fulfill({
contentType: "application/json",
body: JSON.stringify(readings)
});
return;
}
if (url.pathname === "/api/v1/workbench/runtime") {
await route.fulfill({
contentType: "application/json",
body: JSON.stringify(runtimeSnapshot)
});
return;
}
if (url.pathname === "/api/v1/workbench/analysis-runs") {
const limit = Number(url.searchParams.get("limit") ?? 10);
const offset = Number(url.searchParams.get("offset") ?? 0);
const items = analysisHistory.slice(offset, offset + limit);
await route.fulfill({
contentType: "application/json",
body: JSON.stringify({
items,
limit,
offset,
next_offset: offset + limit < analysisHistory.length ? offset + limit : null
})
});
return;
}
const analysisRunMatch = url.pathname.match(/^\/api\/v1\/workbench\/analysis-runs\/([^/]+)$/);
if (analysisRunMatch) {
const runId = decodeURIComponent(analysisRunMatch[1]);
const analysisRun = analysisRuns[runId];
if (analysisRun) {
await route.fulfill({
contentType: "application/json",
body: JSON.stringify(analysisRun)
});
} else {
await route.fulfill({
status: 404,
contentType: "application/json",
body: JSON.stringify({ detail: "Analysis run not found" })
});
}
return;
}
await route.fulfill({ status: 404, contentType: "application/json", body: "{}" });
});
}