fix(editor): auto-save before Run so in-memory edits actually execute
The Run button in NodeEditorDialog and the Test-run button in the editor
toolbar both POST /api/workflows/{id}/executions, which reads the graph
from the DB. Any un-saved edit (Python script tweak, config change, a
freshly-dragged node) was silently ignored.
Both entry points now await the workflow save before starting the
execution. New Vitest pins the save-then-post ordering in the dialog.
This commit is contained in:
@@ -49,6 +49,7 @@ function renderDialog(overrides: Partial<ComponentProps<typeof NodeEditorDialog>
|
||||
onPatch: vi.fn(),
|
||||
onDelete: vi.fn(),
|
||||
onClose: vi.fn(),
|
||||
onSave: vi.fn().mockResolvedValue(undefined),
|
||||
...overrides,
|
||||
};
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
@@ -76,6 +77,7 @@ function renderControlledDialog(overrides: Partial<ComponentProps<typeof NodeEdi
|
||||
onPatch={(patch) => setN((prev) => ({ ...prev, data: { ...prev.data, ...patch } }))}
|
||||
onDelete={overrides.onDelete ?? (() => {})}
|
||||
onClose={overrides.onClose ?? (() => {})}
|
||||
onSave={overrides.onSave ?? (() => Promise.resolve())}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -134,6 +136,28 @@ describe("NodeEditorDialog", () => {
|
||||
expect(screen.getByRole("button", { name: /^run/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("Run button awaits onSave before POSTing the execution", async () => {
|
||||
const events: string[] = [];
|
||||
const onSave = vi.fn().mockImplementation(async () => {
|
||||
events.push("save");
|
||||
});
|
||||
server.use(
|
||||
http.post("/api/workflows/:id/executions", () => {
|
||||
events.push("post");
|
||||
return HttpResponse.json({ executionId: "e-new" }, { status: 202 });
|
||||
}),
|
||||
http.get("/api/executions/e-new", () =>
|
||||
HttpResponse.json({
|
||||
id: "e-new", workflowId: "wf1", trigger: "manual", status: "completed",
|
||||
startedAt: "", endedAt: "", error: null, nodes: [],
|
||||
} satisfies ExecutionDetail)),
|
||||
);
|
||||
renderDialog({ onSave });
|
||||
await userEvent.click(screen.getByRole("button", { name: /^run/i }));
|
||||
await waitFor(() => expect(events).toEqual(["save", "post"]));
|
||||
expect(onSave).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("Run button POSTs an execution and shows the fresh row on success", async () => {
|
||||
server.use(
|
||||
http.post("/api/workflows/:id/executions", () =>
|
||||
|
||||
@@ -22,11 +22,14 @@ interface Props {
|
||||
onPatch: (patch: Partial<VcNodeData>) => void;
|
||||
onDelete: () => void;
|
||||
onClose: () => void;
|
||||
// Persist the current in-memory graph. The dialog awaits this before starting a Run
|
||||
// so unsaved edits (Python script tweaks, config changes) actually take effect.
|
||||
onSave: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function NodeEditorDialog({
|
||||
node, nodeTypes, connections, workflowId, workflowActive,
|
||||
triggerNodeIds, onPatch, onDelete, onClose,
|
||||
triggerNodeIds, onPatch, onDelete, onClose, onSave,
|
||||
}: Props) {
|
||||
const nt = nodeTypes.find((t) => t.id === node.data.typeId);
|
||||
const focusedFieldRef = useRef<ExpressionFieldHandle | null>(null);
|
||||
@@ -91,9 +94,12 @@ export function NodeEditorDialog({
|
||||
const invalidate = useInvalidate();
|
||||
const qc = useQueryClient();
|
||||
const runMutation = useApiMutation(
|
||||
() => {
|
||||
async () => {
|
||||
const startFrom = triggerNodeIds[0];
|
||||
if (!startFrom) throw new Error("no trigger");
|
||||
// Persist in-memory graph edits first — the executor reads from the DB,
|
||||
// so an un-saved script tweak would otherwise be ignored on Run.
|
||||
await onSave();
|
||||
return apiFetch<{ executionId: string }>(
|
||||
`/api/workflows/${workflowId}/executions`,
|
||||
{ method: "POST", body: { startFromNodeId: startFrom, triggerData: {} } },
|
||||
|
||||
@@ -103,9 +103,11 @@ export default function EditorPage() {
|
||||
);
|
||||
|
||||
const testRun = useApiMutation(
|
||||
() => {
|
||||
async () => {
|
||||
const startFrom = selectedId || triggerNodeIds[0];
|
||||
if (!startFrom) throw new Error("Add a trigger node or select a node to start from.");
|
||||
// Persist in-memory graph edits first — the executor reads from the DB.
|
||||
await save.mutateAsync();
|
||||
return apiFetch<{ executionId: string }>(
|
||||
`/api/workflows/${id}/executions`,
|
||||
{ method: "POST", body: { startFromNodeId: startFrom, triggerData: {} } },
|
||||
@@ -223,6 +225,7 @@ export default function EditorPage() {
|
||||
onPatch={(patch) => patchNode(editingNode.id, patch)}
|
||||
onDelete={() => deleteNode(editingNode.id)}
|
||||
onClose={() => setEditingId(null)}
|
||||
onSave={() => save.mutateAsync()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user