Files
windmill/frontend/src/lib/components/FlowRestartButton.svelte
T
Ruben FiszelandClaude Opus 4.5 203f6785c4 fix: isolate SvelteKit-specific imports for library usage
Split SvelteKit-specific code into separate files to allow
windmill-components to be used as a library in non-SvelteKit
contexts (e.g., windmill-react-sdk):

- Split logout.ts into logout.ts and logoutKit.ts
- Split svelte5Utils.svelte.ts into svelte5Utils.svelte.ts and
  svelte5UtilsKit.svelte.ts (for runed/kit useSearchParams)
- Fix triggers/utils.ts type-only import resolution
- Update FlowRestartButton to use callback instead of direct navigation
- Update all route files to import from logoutKit

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 00:25:32 +00:00

222 lines
6.3 KiB
Svelte

<script lang="ts">
import { Badge, Button } from './common'
import Popover from './meltComponents/Popover.svelte'
import { Play, RefreshCw } from 'lucide-svelte'
import { FlowService, JobService, type FlowVersion } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptyString, sendUserToast } from '$lib/utils'
interface Props {
jobId: string
selectedJobStep: string
selectedJobStepType: 'single' | 'forloop' | 'branchall'
restartBranchNames?: [number, string][]
flowPath?: string
disabled?: boolean
enterpriseOnly?: boolean
variant?: 'default' | 'accent'
unifiedSize?: 'xs' | 'sm' | 'md' | 'lg'
/** Called when flow is restarted. If not provided, will navigate to the new run using goto (requires SvelteKit) */
onRestart?: (stepId: string, branchOrIterationN: number, flowVersion?: number) => void
/** Called when flow restart completes with the new job ID. Used for navigation in non-SvelteKit contexts */
onRestartComplete?: (newJobId: string) => void
}
let {
jobId,
selectedJobStep,
selectedJobStepType,
restartBranchNames = [],
flowPath = undefined,
disabled = false,
enterpriseOnly = false,
variant = 'default',
unifiedSize = 'md',
onRestart,
onRestartComplete
}: Props = $props()
let branchOrIterationN = $state(0)
let selectedVersionMode: 'run' | 'custom' = $state('run')
let customFlowVersion: number | undefined = $state(undefined)
let flowVersions: Array<FlowVersion> = $state([])
let loadingVersions: boolean = $state(false)
async function restartFlow(stepId: string, branchOrIterationN: number, flowVersion?: number) {
let run = await JobService.restartFlowAtStep({
workspace: $workspaceStore!,
id: jobId,
requestBody: {
step_id: stepId,
branch_or_iteration_n: branchOrIterationN,
flow_version: flowVersion
}
})
onRestartComplete?.(run)
}
async function loadFlowVersions() {
if (!flowPath || loadingVersions) return
loadingVersions = true
try {
flowVersions = await FlowService.getFlowHistory({
workspace: $workspaceStore!,
path: flowPath
})
if (flowVersions.length > 0 && customFlowVersion === undefined) {
customFlowVersion = flowVersions[0].id
}
} catch (e) {
sendUserToast('Failed to load flow versions', true)
} finally {
loadingVersions = false
}
}
function getFlowVersionForRestart(): number | undefined {
if (selectedVersionMode === 'run') {
return undefined // use run version
} else if (selectedVersionMode === 'custom') {
return customFlowVersion
}
return undefined
}
function handleRestart() {
if (onRestart) {
onRestart(selectedJobStep, branchOrIterationN, getFlowVersionForRestart())
} else {
const flowVersion = getFlowVersionForRestart()
restartFlow(selectedJobStep, branchOrIterationN, flowVersion)
}
}
</script>
{#snippet flowVersionSelector()}
<label>
<div class="pb-1 text-xs font-semibold text-emphasis">Flow version</div>
<div class="flex flex-col gap-2">
<select
bind:value={selectedVersionMode}
class="grow"
onchange={() => {
if (selectedVersionMode === 'custom' && flowVersions.length === 0) {
loadFlowVersions()
}
}}
>
<option value="run">Run version</option>
<option value="custom">Specific version</option>
</select>
{#if selectedVersionMode === 'custom'}
{#if loadingVersions}
<div class="text-xs text-secondary">Loading versions...</div>
{:else if flowVersions.length > 0}
<select bind:value={customFlowVersion} class="grow text-xs">
{#each flowVersions as version}
<option value={version.id}>
{#if emptyString(version.deployment_msg)}Version {version.id}{:else}{version.deployment_msg}{/if}
- {new Date(version.created_at).toLocaleString()}
</option>
{/each}
</select>
{:else}
<div class="text-xs text-tertiary">No versions available</div>
{/if}
{/if}
</div>
</label>
{/snippet}
{#snippet singleRestartButton()}
<Button
title={`Re-start this flow from step ${selectedJobStep} (included).${enterpriseOnly ? ' This is a feature only available in enterprise edition.' : ''}`}
{variant}
{unifiedSize}
{disabled}
startIcon={{ icon: Play }}
nonCaptureEvent={!!flowPath}
onClick={() => {
if (!flowPath) {
handleRestart()
}
}}
>
Re-start from
<Badge baseClass="ml-1" color="indigo">
{selectedJobStep}
</Badge>
{#if enterpriseOnly && disabled}
(EE)
{/if}
</Button>
{/snippet}
{#if selectedJobStepType === 'single'}
{#if !flowPath}
{@render singleRestartButton()}
{:else}
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}
disablePopup={!flowPath}
>
{#snippet trigger()}
{@render singleRestartButton()}
{/snippet}
{#snippet content()}
<div class="flex flex-col gap-4 text-primary p-4 min-w-64">
{@render flowVersionSelector()}
<Button variant="accent" onClick={handleRestart}>Restart</Button>
</div>
{/snippet}
</Popover>
{/if}
{:else}
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
{#snippet trigger()}
<Button
title={`Re-start this flow from step ${selectedJobStep} (included).${enterpriseOnly ? ' This is a feature only available in enterprise edition.' : ''}`}
{variant}
{unifiedSize}
{disabled}
startIcon={{ icon: RefreshCw }}
nonCaptureEvent={true}
>
Re-start from
<Badge baseClass="ml-1" color="indigo">
{selectedJobStep}
</Badge>
{#if enterpriseOnly && disabled}
(EE)
{/if}
</Button>
{/snippet}
{#snippet content()}
<div class="flex flex-col gap-4 text-primary p-4 min-w-64">
<label>
<div class="pb-1 text-xs font-semibold text-emphasis"
>{selectedJobStepType == 'forloop' ? 'From iteration #' : 'From branch'}</div
>
<div class="flex w-full gap-2">
{#if selectedJobStepType === 'forloop'}
<input type="number" min="0" bind:value={branchOrIterationN} class="!w-32 grow" />
{:else}
<select bind:value={branchOrIterationN} class="!w-32 grow">
{#each restartBranchNames as [branchIdx, branchName]}
<option value={branchIdx}>{branchName}</option>
{/each}
</select>
{/if}
</div>
</label>
{#if flowPath}
{@render flowVersionSelector()}
{/if}
<Button variant="accent" onClick={handleRestart}>Restart</Button>
</div>
{/snippet}
</Popover>
{/if}