fix cloned workflow steps keeping the source step's uuid

`CopyStepAction` deleted the cloned step's `id` so a fresh one would be assigned, but never did the same for `uuid`, so a cloned step kept its source's uuid verbatim and saving after cloning failed with "Duplicate step UUID '...' in request." The Options API `onClone` used to strip `uuid` at the call site, but that got dropped during the Composition API migration; moving the fix into `CopyStepAction` itself means any future caller of `copyStep` gets correct behavior automatically.

Added a regression test in `Index.test.ts` that adds a step with a fixed uuid to the step store, emits `onClone` from the `WorkflowGraph` stub, and asserts the resulting clone has a different id, uuid, and label than the source step.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ahmed Awan
2026-08-03 14:51:23 -05:00
parent 8356901b96
commit e275cd5f79
2 changed files with 34 additions and 0 deletions
@@ -363,6 +363,9 @@ export class CopyStepAction extends UndoRedoAction {
this.stepLabel = `${step.id + 1}: ${step.label ?? step.name}`;
this.step = cloneStepWithUniqueLabel(step, labelSet);
delete this.step.id;
// A cloned step must get its own uuid; keeping the source step's uuid
// produces a "Duplicate step UUID" error when saving the workflow.
delete this.step.uuid;
}
get name() {
@@ -14,6 +14,7 @@ import { getWorkflowFull } from "@/components/Workflow/workflows.services";
import { getAppRoot } from "@/onload/loadConfig";
import { useDatatypesMapperStore } from "@/stores/datatypesMapperStore";
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
import { getVersions, saveWorkflow } from "./modules/services";
import { getStateUpgradeMessages } from "./modules/utilities";
@@ -168,6 +169,36 @@ describe("Index", () => {
expect(stateStore.hasChanges).toBeFalsy();
});
it("assigns a fresh id and uuid when cloning a step", async () => {
await flushPromises();
const stepStore = useWorkflowStepStore("workflow_id");
const sourceStep = stepStore.addStep({
type: "tool",
label: "source label",
name: "source step",
content_id: "cat1",
tool_id: "cat1",
tool_state: {},
input_connections: {},
inputs: [],
outputs: [],
position: { left: 0, top: 0 },
uuid: "11111111-1111-1111-1111-111111111111",
});
wrapper.find(SELECTORS.WORKFLOW_GRAPH).vm.$emit("onClone", String(sourceStep.id));
await nextTick();
const clonedStepId = Object.keys(stepStore.steps).find((stepId) => stepId !== String(sourceStep.id))!;
const clonedStep = stepStore.steps[clonedStepId]!;
// The clone must not keep the source step's uuid, otherwise saving
// the workflow fails with "Duplicate step UUID ... in request."
expect(clonedStep.uuid).not.toBe(sourceStep.uuid);
expect(clonedStep.label).not.toBe(sourceStep.label);
});
it("routes to download URL and respects Galaxy prefix", async () => {
Object.defineProperty(window, "location", {
value: { href: "original" },