fix: make console graph demo usable
This commit is contained in:
@@ -161,6 +161,119 @@ describe("useLifecycleExplorer", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps deployment validation when a run selection happens before validation returns", async () => {
|
||||
let releaseValidation: (() => void) | null = null;
|
||||
const validationGate = new Promise<void>((resolve) => {
|
||||
releaseValidation = resolve;
|
||||
});
|
||||
|
||||
mockCallOperation.mockImplementation(async (operation: string) => {
|
||||
if (operation === "workflow.deployments.validate") {
|
||||
await validationGate;
|
||||
return {
|
||||
ok: true,
|
||||
operation,
|
||||
interpreted: {
|
||||
deploymentId: "report.default",
|
||||
artifactId: "report",
|
||||
artifactVersion: 1,
|
||||
status: "runnable",
|
||||
diagnostics: [],
|
||||
nextActions: {
|
||||
canContinue: true,
|
||||
canSaveNow: null,
|
||||
recommendedNextTool: null,
|
||||
reason: "deployment is runnable",
|
||||
patchExamples: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
exchange: { request: {}, response: {} },
|
||||
equivalentCli: "uv run wf deploy validate report.default",
|
||||
durationMs: 5,
|
||||
};
|
||||
}
|
||||
if (operation === "workflow.deployments.inspect") {
|
||||
return {
|
||||
ok: true,
|
||||
operation,
|
||||
interpreted: {
|
||||
id: "report.default",
|
||||
artifactId: "report",
|
||||
artifactVersion: 1,
|
||||
bindings: [],
|
||||
driftPolicy: "block",
|
||||
},
|
||||
exchange: { request: {}, response: {} },
|
||||
equivalentCli: "uv run wf deploy inspect report.default",
|
||||
durationMs: 5,
|
||||
};
|
||||
}
|
||||
if (operation === "workflow.runs.inspect") {
|
||||
return {
|
||||
ok: true,
|
||||
operation,
|
||||
interpreted: {
|
||||
runId: "run_1",
|
||||
deploymentId: "report.default",
|
||||
artifactId: "report",
|
||||
artifactVersion: 1,
|
||||
status: "completed",
|
||||
resumeReadiness: "not_applicable",
|
||||
interrupt: null,
|
||||
outcome: "ok",
|
||||
error: null,
|
||||
output: {},
|
||||
diagnostics: [],
|
||||
traceCount: 0,
|
||||
nextActions: {
|
||||
canContinue: false,
|
||||
canSaveNow: null,
|
||||
recommendedNextTool: null,
|
||||
reason: "done",
|
||||
patchExamples: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
exchange: { request: {}, response: {} },
|
||||
equivalentCli: "uv run wf run inspect run_1",
|
||||
durationMs: 5,
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
operation,
|
||||
interpreted:
|
||||
operation === "workflow.deployments.list"
|
||||
? { items: [] }
|
||||
: { items: [], total: 0, nextCursor: null },
|
||||
exchange: { request: {}, response: {} },
|
||||
equivalentCli: "",
|
||||
durationMs: 5,
|
||||
};
|
||||
});
|
||||
|
||||
const recordEvidence = vi.fn();
|
||||
const { result } = renderHook(() =>
|
||||
useLifecycleExplorer("http://127.0.0.1:8000/rpc", recordEvidence),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
result.current.selectDeployment("report.default");
|
||||
result.current.selectRun("run_1");
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
releaseValidation?.();
|
||||
await validationGate;
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.state.runDetail?.runId).toBe("run_1");
|
||||
expect(result.current.state.deploymentValidation?.status).toBe("runnable");
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores stale responses after target change", async () => {
|
||||
let callCount = 0;
|
||||
mockCallOperation.mockImplementation(async () => {
|
||||
|
||||
@@ -38,7 +38,9 @@ export const useLifecycleExplorer = (
|
||||
): LifecycleExplorerController => {
|
||||
const [state, dispatch] = useReducer(lifecycleReducer, initialLifecycleState);
|
||||
const generationRef = useRef(0);
|
||||
const inspectGenerationRef = useRef(0);
|
||||
const artifactGenerationRef = useRef(0);
|
||||
const deploymentGenerationRef = useRef(0);
|
||||
const runGenerationRef = useRef(0);
|
||||
const rawEvidenceRef = useRef<ReadonlyArray<EvidenceRecord>>([]);
|
||||
const evidenceSeqRef = useRef(0);
|
||||
|
||||
@@ -81,6 +83,19 @@ export const useLifecycleExplorer = (
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const seq = evidenceSeqRef.current++;
|
||||
const record: EvidenceRecord = {
|
||||
id: `${operation}-${seq}`,
|
||||
operation,
|
||||
label: `${operation} failed`,
|
||||
equivalentCli: "unavailable: operation failed before CLI metadata",
|
||||
request: result.exchange.request,
|
||||
response: result.exchange.response,
|
||||
durationMs: 0,
|
||||
};
|
||||
recordEvidence(record);
|
||||
rawEvidenceRef.current = [...rawEvidenceRef.current, record];
|
||||
dispatch({ type: "setRawEvidence", evidence: rawEvidenceRef.current });
|
||||
dispatch({
|
||||
type: "pushError",
|
||||
error: {
|
||||
@@ -128,14 +143,14 @@ export const useLifecycleExplorer = (
|
||||
(artifactId: string | null) => {
|
||||
dispatch({ type: "selectArtifact", artifactId });
|
||||
if (!artifactId || !target) return;
|
||||
inspectGenerationRef.current++;
|
||||
const generation = inspectGenerationRef.current;
|
||||
artifactGenerationRef.current++;
|
||||
const generation = artifactGenerationRef.current;
|
||||
const [id, version] = artifactId.split("@");
|
||||
executeOperation(
|
||||
"workflow.artifacts.inspect",
|
||||
{ artifact_id: id, version: Number(version) },
|
||||
generation,
|
||||
inspectGenerationRef,
|
||||
artifactGenerationRef,
|
||||
(interpreted) => {
|
||||
dispatch({ type: "setArtifactDetail", detail: decodeArtifactDetail(interpreted) });
|
||||
},
|
||||
@@ -148,15 +163,15 @@ export const useLifecycleExplorer = (
|
||||
(deploymentId: string | null) => {
|
||||
dispatch({ type: "selectDeployment", deploymentId });
|
||||
if (!deploymentId || !target) return;
|
||||
inspectGenerationRef.current++;
|
||||
const generation = inspectGenerationRef.current;
|
||||
deploymentGenerationRef.current++;
|
||||
const generation = deploymentGenerationRef.current;
|
||||
// Deployment selection fans out to inspect + validate. Both describe the
|
||||
// same selected deployment, so they must share one generation token.
|
||||
executeOperation(
|
||||
"workflow.deployments.inspect",
|
||||
{ deployment_id: deploymentId },
|
||||
generation,
|
||||
inspectGenerationRef,
|
||||
deploymentGenerationRef,
|
||||
(interpreted) => {
|
||||
dispatch({ type: "setDeploymentDetail", detail: decodeDeploymentDetail(interpreted) });
|
||||
},
|
||||
@@ -165,7 +180,7 @@ export const useLifecycleExplorer = (
|
||||
"workflow.deployments.validate",
|
||||
{ deployment_id: deploymentId },
|
||||
generation,
|
||||
inspectGenerationRef,
|
||||
deploymentGenerationRef,
|
||||
(interpreted) => {
|
||||
dispatch({ type: "setDeploymentValidation", validation: decodeDeploymentValidation(interpreted) });
|
||||
},
|
||||
@@ -178,15 +193,27 @@ export const useLifecycleExplorer = (
|
||||
(runId: string | null) => {
|
||||
dispatch({ type: "selectRun", runId });
|
||||
if (!runId || !target) return;
|
||||
inspectGenerationRef.current++;
|
||||
const generation = inspectGenerationRef.current;
|
||||
runGenerationRef.current++;
|
||||
const generation = runGenerationRef.current;
|
||||
executeOperation(
|
||||
"workflow.runs.inspect",
|
||||
{ run_id: runId },
|
||||
generation,
|
||||
inspectGenerationRef,
|
||||
runGenerationRef,
|
||||
(interpreted) => {
|
||||
dispatch({ type: "setRunDetail", detail: decodeRunDetail(interpreted) });
|
||||
const detail = decodeRunDetail(interpreted);
|
||||
dispatch({ type: "setRunDetail", detail });
|
||||
if (detail.traceCount > 0) {
|
||||
executeOperation(
|
||||
"workflow.runs.trace",
|
||||
{ run_id: runId, trace_range: { start: 0, limit: 50 } },
|
||||
generation,
|
||||
runGenerationRef,
|
||||
(traceInterpreted) => {
|
||||
dispatch({ type: "setTrace", trace: decodeTracePage(traceInterpreted) });
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -243,13 +270,13 @@ export const useLifecycleExplorer = (
|
||||
const loadTrace = useCallback(
|
||||
(start: number, limit: number) => {
|
||||
if (!state.selectedRunId || !target) return;
|
||||
inspectGenerationRef.current++;
|
||||
const generation = inspectGenerationRef.current;
|
||||
runGenerationRef.current++;
|
||||
const generation = runGenerationRef.current;
|
||||
executeOperation(
|
||||
"workflow.runs.trace",
|
||||
{ run_id: state.selectedRunId, trace_range: { start, limit } },
|
||||
generation,
|
||||
inspectGenerationRef,
|
||||
runGenerationRef,
|
||||
(interpreted) => {
|
||||
dispatch({ type: "setTrace", trace: decodeTracePage(interpreted) });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user