fix: close monitor workbench final review gaps
This commit is contained in:
@@ -135,6 +135,15 @@ test("monitor workspace filters before paging and counts findings beyond page si
|
||||
assert.equal(body.runs.nextCursor, null);
|
||||
});
|
||||
|
||||
test("monitor latest failure excludes running and unknown states", async () => {
|
||||
const target = service();
|
||||
await target.ingest({ ...input("failed-terminal", "2026-07-12T10:00:00.000Z"), status: "failed" });
|
||||
await target.ingest({ ...input("running-newer", "2026-07-12T12:00:00.000Z"), status: "running" });
|
||||
await target.ingest({ ...input("unknown-newest", "2026-07-12T13:00:00.000Z"), status: "unknown" });
|
||||
const body = await (await target.fetch(new Request("http://local/api/monitor/workspace?sentinelId=sentinel-a&node=NC01&lane=v03"))).json() as any;
|
||||
assert.equal(body.summary.latestFailureAt, "2026-07-12T10:00:00.000Z");
|
||||
});
|
||||
|
||||
test("monitor workspace projects cadence freshness and health only from runtime metadata", async () => {
|
||||
const store = createInMemoryMonitorCentralStore();
|
||||
const normalized = normalizeMonitorCentralIngest(input("run-metadata"));
|
||||
|
||||
@@ -460,7 +460,7 @@ export function createInMemoryMonitorCentralStore(): MonitorCentralStore {
|
||||
const hasMore = candidates.length > options.limit;
|
||||
const items = candidates.slice(0, options.limit).map((run) => ({ ...run, findingCount: findingCounts.get(identityKey(run)) ?? 0 }));
|
||||
const last = items.at(-1);
|
||||
return { items, nextCursor: hasMore && last ? cursor(last) : null, totalRuns: all.length, findingCount: filteredRecords.reduce((total, item) => total + item.findings.length, 0), statusCounts: countBy(all, (run) => run.status), latestSuccessAt: all.find((run) => run.status === "succeeded")?.updatedAt ?? null, latestFailureAt: all.find((run) => run.status !== "succeeded")?.updatedAt ?? null };
|
||||
return { items, nextCursor: hasMore && last ? cursor(last) : null, totalRuns: all.length, findingCount: filteredRecords.reduce((total, item) => total + item.findings.length, 0), statusCounts: countBy(all, (run) => run.status), latestSuccessAt: all.find((run) => run.status === "succeeded")?.updatedAt ?? null, latestFailureAt: all.find((run) => monitorRunFailed(run.status))?.updatedAt ?? null };
|
||||
},
|
||||
async runs(scope, options) { return sortRuns([...records.values()].filter((item) => matches(scope, item)).map(runFrom)).filter((run) => afterRunCursor(run, options.cursor)).slice(0, options.limit); },
|
||||
async run(scope, runId) {
|
||||
@@ -531,6 +531,7 @@ function runFrom(value: NormalizedMonitorCentralIngest): MonitorRun { return { s
|
||||
function sortRuns(runs: readonly MonitorRun[]): MonitorRun[] { return [...runs].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt) || right.runId.localeCompare(left.runId)); }
|
||||
function afterRunCursor(run: MonitorRun, cursorValue: RunCursor | null): boolean { return !cursorValue || run.updatedAt < cursorValue.updatedAt || (run.updatedAt === cursorValue.updatedAt && run.runId < cursorValue.runId); }
|
||||
function monitorWorkspaceMatches(run: MonitorRun, options: MonitorWorkspaceQueryOptions): boolean { return (!options.status || run.status === options.status) && (!options.from || run.updatedAt >= options.from) && (!options.to || run.updatedAt <= options.to); }
|
||||
function monitorRunFailed(status: string): boolean { return status === "failed"; }
|
||||
function compareFindings(left: Record<string, unknown>, right: Record<string, unknown>): number { return String(right.updatedAt).localeCompare(String(left.updatedAt)) || String(right.runId).localeCompare(String(left.runId)) || Number(right.findingIndex) - Number(left.findingIndex); }
|
||||
function afterFindingCursor(finding: Record<string, unknown>, cursorValue: FindingCursor | null): boolean { if (!cursorValue) return true; return String(finding.updatedAt) < cursorValue.updatedAt || (finding.updatedAt === cursorValue.updatedAt && (String(finding.runId) < cursorValue.runId || (finding.runId === cursorValue.runId && Number(finding.findingIndex) < cursorValue.findingIndex))); }
|
||||
export function monitorCentralStableJson(value: unknown): string { if (value === undefined) return "null"; if (Array.isArray(value)) return `[${value.map(monitorCentralStableJson).join(",")}]`; if (value && typeof value === "object") return `{${Object.entries(value as Record<string, unknown>).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${monitorCentralStableJson(item)}`).join(",")}}`; const json = JSON.stringify(value); return json === undefined ? "null" : json; }
|
||||
@@ -551,7 +552,7 @@ async function queryMonitorWorkspace(sql: postgres.Sql, scope: MonitorScope, opt
|
||||
const predicate = filters.join(" and ");
|
||||
const [pageRows, summaryRows, statusRows] = await Promise.all([
|
||||
sql.unsafe(`select r.sentinel_id, r.node, r.lane, r.run_id, r.updated_at, r.status, r.payload_hash, count(f.finding_index)::int as finding_count from monitor.runs r left join monitor.findings f using (sentinel_id, node, lane, run_id) where ${predicate} ${cursorClause} group by r.sentinel_id, r.node, r.lane, r.run_id, r.updated_at, r.status, r.payload_hash order by r.updated_at desc, r.run_id desc limit $${values.length}`, values),
|
||||
sql.unsafe(`select count(*)::int as total_runs, coalesce(sum(finding_count),0)::int as finding_count, max(updated_at) filter (where status = 'succeeded') as latest_success_at, max(updated_at) filter (where status <> 'succeeded') as latest_failure_at from (select r.status, r.updated_at, count(f.finding_index)::int as finding_count from monitor.runs r left join monitor.findings f using (sentinel_id, node, lane, run_id) where ${predicate} group by r.sentinel_id, r.node, r.lane, r.run_id, r.status, r.updated_at) filtered`, baseValues),
|
||||
sql.unsafe(`select count(*)::int as total_runs, coalesce(sum(finding_count),0)::int as finding_count, max(updated_at) filter (where status = 'succeeded') as latest_success_at, max(updated_at) filter (where status = 'failed') as latest_failure_at from (select r.status, r.updated_at, count(f.finding_index)::int as finding_count from monitor.runs r left join monitor.findings f using (sentinel_id, node, lane, run_id) where ${predicate} group by r.sentinel_id, r.node, r.lane, r.run_id, r.status, r.updated_at) filtered`, baseValues),
|
||||
sql.unsafe(`select r.status, count(*)::int as status_count from monitor.runs r where ${predicate} group by r.status`, baseValues),
|
||||
]);
|
||||
const hasMore = pageRows.length > options.limit;
|
||||
|
||||
@@ -24,3 +24,10 @@ test("monitor route rejects invalid tab status time range and malformed encoding
|
||||
assert.equal(readMonitorRoute("https://monitor.example.test/monitor?from=2026-07-13T05:00:00Z&to=2026-07-13T04:00:00Z").reason, "invalid_time_range");
|
||||
assert.equal(readMonitorRoute("https://monitor.example.test/monitor/runs/%E0%A4%A").reason, "malformed_url_encoding");
|
||||
});
|
||||
|
||||
test("monitor route roundtrips diagnostic detail", () => {
|
||||
const route = readMonitorRoute("https://monitor.example.test/monitor/runs/run-1/diagnostics/runtime?sentinelId=s1&node=NC01&lane=v03&tab=diagnostics");
|
||||
assert.equal(route.detailType, "diagnostic");
|
||||
assert.equal(route.detailId, "runtime");
|
||||
assert.equal(monitorRouteHref(route), "/monitor/runs/run-1/diagnostics/runtime?sentinelId=s1&node=NC01&lane=v03&tab=diagnostics");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user