Flow delete offers to delete its runs, history included

DELETE /runs?flow= removes every Run row of the flow but left its flow_run
rows, so a deleted flow kept appearing in the history. history=true now
drops those too, and the flow delete dialogs (editor and bulk) send it when
"Also delete its runs" is ticked.
This commit is contained in:
2026-10-15 10:52:33 +02:00
parent 7ea53ccf17
commit 6fa44bacf3
11 changed files with 124 additions and 18 deletions
@@ -277,6 +277,7 @@ export function ConfirmDelete({
pending,
description,
onConfirm,
children,
}: {
open: boolean
onOpenChange: (open: boolean) => void
@@ -286,6 +287,8 @@ export function ConfirmDelete({
/** What is actually lost, when the git-backed answer below is not it. */
description?: string
onConfirm: () => void
/** An option the delete takes, drawn between the question and the buttons. */
children?: ReactNode
}) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -307,6 +310,7 @@ export function ConfirmDelete({
)}
</DialogDescription>
</DialogHeader>
{children}
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Keep {names.length === 1 ? "it" : "them"}
+9 -2
View File
@@ -31,6 +31,7 @@ import {
FlowsService,
type MessageSpec,
type NodeDef_Input,
RunsService,
} from "@/client"
import { DiscardDraftDialog } from "@/components/Common/Dock"
import { useTheme } from "@/components/theme-provider"
@@ -790,7 +791,13 @@ function FlowEditorInner({
})
const deleteMutation = useMutation({
mutationFn: () => FlowsService.deleteFlow({ name: flowName }),
mutationFn: async (withRuns: boolean) => {
await FlowsService.deleteFlow({ name: flowName })
// After the flow, which is refused while a run is live, so a refusal
// leaves both standing.
if (withRuns)
await RunsService.deleteRuns({ flow: flowName, history: true })
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: flowKeys.all })
setFlowPanelOpen(false)
@@ -1327,7 +1334,7 @@ function FlowEditorInner({
nodeCount={definitions.length}
// Confirmed, so it is a finished edit rather than a run of keystrokes.
onChange={(next) => commitDoc({ ...next, nodes: definitions }, true)}
onDelete={() => deleteMutation.mutate()}
onDelete={(withRuns) => deleteMutation.mutate(withRuns)}
enabled={detail.enabled ?? true}
toggling={enableMutation.isPending}
onToggleEnabled={(next) => enableMutation.mutate(next)}
+42 -4
View File
@@ -1,7 +1,8 @@
import { useState } from "react"
import { useId, useState } from "react"
import type { FlowDef_Input } from "@/client"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
Dialog,
DialogContent,
@@ -34,7 +35,7 @@ export function FlowPanel({
definition: FlowDef_Input
nodeCount: number
onChange: (next: FlowDef_Input) => void
onDelete: () => void
onDelete: (withRuns: boolean) => void
hasDraft: boolean
enabled: boolean
toggling: boolean
@@ -42,6 +43,7 @@ export function FlowPanel({
onClose: () => void
}) {
const [confirmOpen, setConfirmOpen] = useState(false)
const [withRuns, setWithRuns] = useState(false)
return (
<>
@@ -64,7 +66,10 @@ export function FlowPanel({
variant="ghost"
size="sm"
className="text-destructive hover:text-destructive"
onClick={() => setConfirmOpen(true)}
onClick={() => {
setWithRuns(false)
setConfirmOpen(true)
}}
data-testid="delete-flow"
>
Delete flow
@@ -142,6 +147,7 @@ export function FlowPanel({
stays in the flow store's git repository.
</DialogDescription>
</DialogHeader>
<AlsoDeleteRuns checked={withRuns} onCheckedChange={setWithRuns} />
<DialogFooter>
<Button variant="outline" onClick={() => setConfirmOpen(false)}>
Keep it
@@ -150,7 +156,7 @@ export function FlowPanel({
variant="destructive"
onClick={() => {
setConfirmOpen(false)
onDelete()
onDelete(withRuns)
}}
data-testid="confirm-delete-flow"
>
@@ -162,3 +168,35 @@ export function FlowPanel({
</>
)
}
/**
* Whether deleting a flow takes its runs along. Off by default: a run is a
* record of what ran, and keeping one keeps the artifacts it recorded.
*/
export function AlsoDeleteRuns({
checked,
onCheckedChange,
plural = false,
}: {
checked: boolean
onCheckedChange: (checked: boolean) => void
plural?: boolean
}) {
const id = useId()
return (
<div className="grid gap-1">
<label htmlFor={id} className="flex items-center gap-2 text-sm">
<Checkbox
id={id}
checked={checked}
onCheckedChange={(next) => onCheckedChange(next === true)}
data-testid="delete-flow-runs"
/>
Also delete {plural ? "their" : "its"} runs
</label>
<p className="text-sm text-muted-foreground">
Otherwise the runs stay, and keep their artifacts on disk.
</p>
</div>
)
}