fix(editor): strip {json, binary} wrapper on Load-from-execution pin
The pin loader was storing port items verbatim in the on-wire shape
[{json: ..., binary: null}]. The executor's WrapPin treats each pin-array
element as one item's whole payload, so the wrapper leaked in: scripts saw
items[0] == {"json": ..., "binary": null} and items[0]["test"] raised
KeyError instead of returning the value.
Now the loader unwraps each item to its .json payload before storing.
Updated the existing test assertion and added a multi-item regression.
This commit is contained in:
@@ -119,7 +119,40 @@ describe("PinnedDataSection", () => {
|
||||
expect(ta.value).toContain(`"loaded": true`);
|
||||
});
|
||||
const calls = (props.onChange as ReturnType<typeof vi.fn>).mock.calls;
|
||||
expect(calls.some(([v]) => JSON.stringify(v) === `[{"json":{"loaded":true}}]`)).toBe(true);
|
||||
// Wrapper {json, binary} stripped — pin stores raw payloads so scripts see
|
||||
// `items[0]["loaded"]`, not `items[0]["json"]["loaded"]`.
|
||||
expect(calls.some(([v]) => JSON.stringify(v) === `[{"loaded":true}]`)).toBe(true);
|
||||
});
|
||||
|
||||
it("Load from execution strips {json, binary} wrapper across multiple items", async () => {
|
||||
server.use(
|
||||
http.get("/api/workflows/:id/executions", () =>
|
||||
HttpResponse.json([
|
||||
{ id: "e1", workflowId: "wf1", trigger: "manual", status: "completed",
|
||||
startedAt: "2025-01-01T00:00:00Z", endedAt: null, error: null } satisfies ExecutionSummary,
|
||||
])),
|
||||
http.get("/api/executions/e1", () =>
|
||||
HttpResponse.json({
|
||||
id: "e1", workflowId: "wf1", trigger: "manual", status: "completed",
|
||||
startedAt: "", endedAt: "", error: null,
|
||||
nodes: [
|
||||
{ nodeId: "n1", status: "completed", startedAt: "", endedAt: "", error: null,
|
||||
inputs: [[
|
||||
{ json: { test: "hello" }, binary: null },
|
||||
{ json: { test: "world" }, binary: null },
|
||||
]], outputs: [] },
|
||||
],
|
||||
} satisfies ExecutionDetail)),
|
||||
);
|
||||
const { props } = renderSection({ value: null });
|
||||
await screen.findByRole("option", { name: /e1/i });
|
||||
await userEvent.selectOptions(screen.getByLabelText(/load from execution/i), "e1");
|
||||
await waitFor(() => {
|
||||
const calls = (props.onChange as ReturnType<typeof vi.fn>).mock.calls;
|
||||
expect(calls.some(([v]) =>
|
||||
JSON.stringify(v) === `[{"test":"hello"},{"test":"world"}]`
|
||||
)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("Load-from-execution for a trigger uses outputs[0] not inputs[0]", async () => {
|
||||
|
||||
@@ -77,8 +77,17 @@ export function PinnedDataSection({
|
||||
if (!row) return;
|
||||
const source = side === "inputs" ? row.inputs : row.outputs;
|
||||
const port0 = Array.isArray(source) ? source[0] : undefined;
|
||||
if (port0 === undefined) return;
|
||||
const pretty = JSON.stringify(port0, null, 2);
|
||||
if (!Array.isArray(port0)) return;
|
||||
// Strip the on-wire {json, binary} wrapper — the executor's WrapPin treats
|
||||
// each pin array element as one item's whole payload. Storing the wrapper
|
||||
// verbatim leaks `items[0] == {json:..., binary:null}` into scripts and
|
||||
// makes property access (`items[0]["test"]`) raise KeyError.
|
||||
const payloads = port0.map((it) =>
|
||||
it && typeof it === "object" && "json" in (it as object)
|
||||
? (it as { json: unknown }).json
|
||||
: it,
|
||||
);
|
||||
const pretty = JSON.stringify(payloads, null, 2);
|
||||
commit(pretty);
|
||||
setLoadEid(""); // reset the dropdown to "latest"
|
||||
}, [loadEid, loadedExec.data]);
|
||||
|
||||
Reference in New Issue
Block a user