feat: add workflow console lifecycle explorer
This commit is contained in:
@@ -92,17 +92,28 @@ describe("App", () => {
|
||||
it("ignores stale source inventory responses after reconnect", async () => {
|
||||
const firstSources = deferred<RpcResponse>();
|
||||
const secondSources = deferred<RpcResponse>();
|
||||
const lifecycleOk: RpcResponse = {
|
||||
ok: true,
|
||||
operation: "workflow.artifacts.list",
|
||||
label: "List artifacts",
|
||||
interpreted: { items: [], total: 0, nextCursor: null },
|
||||
exchange: { request: {}, response: {} },
|
||||
equivalentCli: "uv run wf artifact list",
|
||||
durationMs: 5,
|
||||
};
|
||||
let latestSourcesDeferred = firstSources;
|
||||
mockedConnectToServer
|
||||
.mockResolvedValueOnce(successfulConnection("http://first.example/rpc"))
|
||||
.mockResolvedValueOnce(successfulConnection("http://second.example/rpc"));
|
||||
mockedCallOperation
|
||||
.mockReturnValueOnce(firstSources.promise)
|
||||
.mockReturnValueOnce(secondSources.promise);
|
||||
mockedCallOperation.mockImplementation((op: string) => {
|
||||
if (op === "workflow.sources.list") return latestSourcesDeferred.promise;
|
||||
return Promise.resolve(lifecycleOk);
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
|
||||
await screen.findByTestId("sources-loading");
|
||||
|
||||
latestSourcesDeferred = secondSources;
|
||||
await userEvent.click(screen.getByRole("button", { name: "Reconnect" }));
|
||||
secondSources.resolve(successfulSources("local.second"));
|
||||
firstSources.resolve(successfulSources("local.first"));
|
||||
@@ -114,4 +125,26 @@ describe("App", () => {
|
||||
expect(screen.queryByTestId("source-id-local.first")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("mounts lifecycle explorer after connect", async () => {
|
||||
mockedConnectToServer.mockResolvedValue(
|
||||
successfulConnection("http://127.0.0.1:8765/rpc"),
|
||||
);
|
||||
mockedCallOperation.mockResolvedValue({
|
||||
ok: true,
|
||||
operation: "workflow.sources.list",
|
||||
label: "List sources",
|
||||
interpreted: { sources: [], total: 0, nextCursor: null },
|
||||
exchange: { request: {}, response: {} },
|
||||
equivalentCli: "uv run wf source list",
|
||||
durationMs: 5,
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("lifecycle-explorer")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,12 +2,14 @@ import { useReducer, useEffect, useCallback, useRef } from "react";
|
||||
import {
|
||||
connectionReducer,
|
||||
initialState,
|
||||
type EvidenceRecord,
|
||||
type SourceRecord,
|
||||
} from "./state.js";
|
||||
import { connectToServer, callOperation } from "../connection/api.js";
|
||||
import { ConnectionHeader } from "../components/ConnectionHeader.js";
|
||||
import { SourceInventory } from "../components/SourceInventory.js";
|
||||
import { ProtocolEvidence } from "../components/ProtocolEvidence.js";
|
||||
import { LifecycleExplorer } from "../lifecycle/LifecycleExplorer.js";
|
||||
import { useLifecycleExplorer } from "../lifecycle/useLifecycleExplorer.js";
|
||||
|
||||
const parseSources = (
|
||||
data: unknown,
|
||||
@@ -44,6 +46,15 @@ export const App = () => {
|
||||
const connectGeneration = useRef(0);
|
||||
const sourcesGeneration = useRef(0);
|
||||
|
||||
const connectedTarget = state.phase === "connected" ? state.connectedTarget : null;
|
||||
|
||||
const recordEvidence = useCallback(
|
||||
(record: EvidenceRecord) => dispatch({ type: "evidence_recorded", record }),
|
||||
[],
|
||||
);
|
||||
|
||||
const lifecycleController = useLifecycleExplorer(connectedTarget, recordEvidence);
|
||||
|
||||
const loadSources = useCallback(
|
||||
async (target: string) => {
|
||||
const generation = ++sourcesGeneration.current;
|
||||
@@ -154,7 +165,9 @@ export const App = () => {
|
||||
loading={state.sourcesLoading}
|
||||
error={state.sourceError}
|
||||
/>
|
||||
<ProtocolEvidence evidence={state.evidence} />
|
||||
<section aria-label="Lifecycle Explorer" data-testid="lifecycle-explorer">
|
||||
<LifecycleExplorer controller={lifecycleController} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user