From bef29aea50db2624a8973842477131cc22a07132 Mon Sep 17 00:00:00 2001 From: Olga Silva Date: Thu, 23 Jul 2026 17:52:35 +0100 Subject: [PATCH] [workflows-shared] Add full step output retrieval to Workflows local explorer --- .../workflows-local-explorer-step-output.md | 7 + .../__tests__/workflows/step-helpers.test.ts | 29 +++ .../src/components/workflows/CopyButton.tsx | 5 +- .../src/components/workflows/StepRow.tsx | 174 +++++++++++++- .../src/components/workflows/helpers.ts | 10 + .../workflows/$workflowName/$instanceId.tsx | 8 + .../scripts/openapi-filter-config.ts | 129 +++++++++++ .../workers/local-explorer/explorer.worker.ts | 14 ++ .../workers/local-explorer/generated/index.ts | 6 + .../local-explorer/generated/types.gen.ts | 66 ++++++ .../local-explorer/generated/zod.gen.ts | 36 +++ .../workers/local-explorer/openapi.local.json | 125 ++++++++++ .../local-explorer/resources/workflows.ts | 122 +++++++++- .../src/workers/local-explorer/route-names.ts | 1 + packages/workflows-shared/src/engine.ts | 150 ++++++++++++ .../workflows-shared/tests/engine.test.ts | 214 +++++++++++++++++- packages/workflows-shared/tests/utils.ts | 33 +++ 17 files changed, 1107 insertions(+), 22 deletions(-) create mode 100644 .changeset/workflows-local-explorer-step-output.md diff --git a/.changeset/workflows-local-explorer-step-output.md b/.changeset/workflows-local-explorer-step-output.md new file mode 100644 index 0000000000..26e707b2ca --- /dev/null +++ b/.changeset/workflows-local-explorer-step-output.md @@ -0,0 +1,7 @@ +--- +"miniflare": minor +--- + +Add full step output retrieval to the Workflows local explorer + +The Workflows instance details response truncates streamed step outputs to a short preview (or a placeholder for binary streams). Mirroring the production `/step` endpoint, the local explorer now exposes `GET /workflows/{workflow_name}/instances/{instance_id}/step`, which returns a flat `{ status, error, output }` body for a single step. The explorer UI fetches this on demand when a step whose inline preview was truncated is expanded. diff --git a/packages/local-explorer-ui/src/__tests__/workflows/step-helpers.test.ts b/packages/local-explorer-ui/src/__tests__/workflows/step-helpers.test.ts index a62630f894..25f34b5d38 100644 --- a/packages/local-explorer-ui/src/__tests__/workflows/step-helpers.test.ts +++ b/packages/local-explorer-ui/src/__tests__/workflows/step-helpers.test.ts @@ -1,4 +1,5 @@ import { describe, test } from "vitest"; +import { isTruncatedStreamPreview } from "../../components/workflows/helpers"; import { getStepDisplayName, getStepKey, @@ -106,3 +107,31 @@ describe("getRestartFromStepParam", () => { }); }); }); + +describe("isTruncatedStreamPreview", () => { + test("detects a truncated text preview", ({ expect }) => { + expect(isTruncatedStreamPreview("some output[truncated output]")).toBe( + true + ); + }); + + test("detects a binary stream placeholder", ({ expect }) => { + expect( + isTruncatedStreamPreview("[ReadableStream (binary): 2048 bytes]") + ).toBe(true); + }); + + test("detects an incomplete stream placeholder", ({ expect }) => { + expect(isTruncatedStreamPreview("[ReadableStream: 100 bytes]")).toBe(true); + }); + + test("returns false for a complete inline value", ({ expect }) => { + expect(isTruncatedStreamPreview("hello world")).toBe(false); + }); + + test("returns false for non-string values", ({ expect }) => { + expect(isTruncatedStreamPreview({ foo: "bar" })).toBe(false); + expect(isTruncatedStreamPreview(undefined)).toBe(false); + expect(isTruncatedStreamPreview(42)).toBe(false); + }); +}); diff --git a/packages/local-explorer-ui/src/components/workflows/CopyButton.tsx b/packages/local-explorer-ui/src/components/workflows/CopyButton.tsx index df859c69f5..98484ada79 100644 --- a/packages/local-explorer-ui/src/components/workflows/CopyButton.tsx +++ b/packages/local-explorer-ui/src/components/workflows/CopyButton.tsx @@ -4,9 +4,11 @@ import { useState, type JSX } from "react"; export function CopyButton({ text, label = "Copy", + disabled = false, }: { text: string; label?: string; + disabled?: boolean; }): JSX.Element { const [copied, setCopied] = useState(false); @@ -19,7 +21,8 @@ export function CopyButton({ return (