diff --git a/apps/dashboard/src/components/create-run-form.tsx b/apps/dashboard/src/components/create-run-form.tsx index b9417964..ee029174 100644 --- a/apps/dashboard/src/components/create-run-form.tsx +++ b/apps/dashboard/src/components/create-run-form.tsx @@ -45,6 +45,7 @@ export function CreateRunForm({ onCancel, onSuccess }: CreateRunFormProps) { const [workflowName, setWorkflowName] = useState(""); const [version, setVersion] = useState(""); const [input, setInput] = useState(""); + const [idempotencyKey, setIdempotencyKey] = useState(""); const [availableAt, setAvailableAt] = useState(""); const [deadlineAt, setDeadlineAt] = useState(""); const [inputError, setInputError] = useState(null); @@ -94,6 +95,7 @@ export function CreateRunForm({ onCancel, onSuccess }: CreateRunFormProps) { workflowName: workflowName.trim(), version: normalizeOptionalField(version), input: normalizedInput, + idempotencyKey: idempotencyKey || null, availableAt: availableAtIso, deadlineAt: deadlineAtIso, }, @@ -165,6 +167,29 @@ export function CreateRunForm({ onCancel, onSuccess }: CreateRunFormProps) { {inputError} + + + Idempotency Key + + { + setIdempotencyKey(event.currentTarget.value); + }} + placeholder="e.g. welcome-email:user_123" + aria-describedby="create-run-idempotency-key-description" + autoComplete="off" + disabled={isSubmitting} + /> + + Reusing a key for the same workflow name in this namespace opens + the existing run for 24 hours after creation, even with different + input or version. Leave empty to create a new run. + + +
diff --git a/apps/dashboard/src/lib/api.ts b/apps/dashboard/src/lib/api.ts index 6ad1832f..a3e36aec 100644 --- a/apps/dashboard/src/lib/api.ts +++ b/apps/dashboard/src/lib/api.ts @@ -149,6 +149,7 @@ export const createWorkflowRunServerFn = createServerFn({ method: "POST" }) workflowName: z.string().trim().min(1), version: z.string().nullable().optional(), input: z.string().nullable().optional(), + idempotencyKey: z.string().nullable().optional(), availableAt: z.string().nullable().optional(), deadlineAt: z.string().nullable().optional(), }), @@ -166,7 +167,8 @@ export const createWorkflowRunServerFn = createServerFn({ method: "POST" }) return backend.createWorkflowRun({ workflowName: data.workflowName, version, - idempotencyKey: null, + idempotencyKey: + data.idempotencyKey === "" ? null : (data.idempotencyKey ?? null), config: {}, context: null, input: parseWorkflowInput(data.input), diff --git a/apps/dashboard/src/routes/runs/$runId.tsx b/apps/dashboard/src/routes/runs/$runId.tsx index da70d64e..25124284 100644 --- a/apps/dashboard/src/routes/runs/$runId.tsx +++ b/apps/dashboard/src/routes/runs/$runId.tsx @@ -535,6 +535,17 @@ function RunOverviewPanel({ Run ID
+ {run.idempotencyKey !== null && ( +
+ + Idempotency Key + + +
+ )}
Status