Files
windmill/frontend/src/lib/components/scripts/PerpetualRunsDeployModal.svelte
T
hugocasaandClaude Opus 5 cf5c49c3dc feat: restart perpetual runs on the version a deploy makes runnable (#11200)
* feat: opt-in move of perpetual runs to a newly deployed script version

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: keep the perpetual-run opt-in across relocks and check the new version's tag

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: pin the tag check on a perpetual version switch

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: count perpetual runs past the first queue page in the deploy prompt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: restart perpetual runs on the version a deploy makes runnable

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: claim a perpetual run and queue its replacement in one transaction

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: honour a cancel that lands after the worker last read its queue row

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor: leave the lost-cancel fix to its own PR and match the scale down to 0 wording

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: run a preprocessor the deployed version adds over the arguments carried over

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* style: shorten the wording of the modal's argument warning

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor: never preprocess a restarted perpetual run, as every other restart does

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: preprocess for a replacement whose run had not been through one

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: push a perpetual replacement without the deployed debounce settings

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: name the runs the deploy button restarts

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* perf: skip the perpetual restart lookups on a deploy that is not perpetual

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: move perpetual runs before anything that can fail after the deploy commits

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: report cancellation on the queue listing so the deploy prompt can skip it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: pass the tag workspace to the availability check after the merge

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* style: say which arguments are defined differently and which values are kept

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: resolve a dynamic tag before checking it for a restarted run

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: pin that a deployed dynamic tag is checked as it resolves

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-29 18:03:43 +02:00

117 lines
3.4 KiB
Svelte

<script lang="ts">
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
import { Alert, Button } from '$lib/components/common'
import type { PerpetualRunsAtPath } from './perpetualRuns'
interface Props {
/** Open while set. */
runs: PerpetualRunsAtPath | undefined
/** Stops the runs without deploying, so the deploy that follows starts nothing in their place. */
onStop: () => Promise<boolean>
onConfirmed: () => void
onCanceled: () => void
}
let { runs, onStop, onConfirmed, onCanceled }: Props = $props()
let stopping = $state(false)
let stopped = $state(false)
const single = $derived(runs?.count === 1)
const subject = $derived(single ? 'it' : 'them')
const runsText = $derived(
single ? '1 run of this script is' : `${runs?.count} runs of this script are`
)
function reset() {
stopping = false
stopped = false
}
</script>
<ConfirmationModal
open={!!runs}
title="Perpetual runs on an earlier version"
confirmationText={stopped
? 'Deploy'
: runs?.count === undefined
? 'Deploy and restart runs'
: `Deploy and restart ${runs.count} ${single ? 'run' : 'runs'}`}
type="reload"
loading={stopping}
onConfirmed={() => {
reset()
onConfirmed()
}}
onCanceled={() => {
reset()
onCanceled()
}}
>
{#if runs}
<div class="flex flex-col gap-3">
{#if stopped}
<p>
{single ? 'The run was' : 'The runs were'} scaled down. Deploying starts nothing in {single
? 'its'
: 'their'} place.
</p>
{:else}
{#if runs.count === undefined}
<p>
Runs of this script could not be listed. Any queued or running on an earlier version
stop and start again on this version, with the values they have now.
</p>
{:else}
<p>
{runsText} queued or running on an earlier version. Deploying stops {subject} and starts
{subject}
again on this version, with the values {single ? 'it has' : 'they have'} now.
</p>
{/if}
{#if runs.count === undefined || runs.mismatchedArgs.length > 0}
<Alert
type="warning"
size="xs"
title={runs.mismatchedArgs.length > 0
? 'Arguments no longer match'
: 'Their arguments could not be checked'}
>
<div class="flex flex-col items-start gap-2">
<p>
{#if runs.mismatchedArgs.length > 0}
This version defines
{runs.mismatchedArgs.length === 1 ? 'this argument' : 'these arguments'} differently:
{#each runs.mismatchedArgs as arg, i (arg)}
<code>{arg}</code>{i < runs.mismatchedArgs.length - 1 ? ', ' : '.'}
{/each}
{:else}
The versions the runs are on could not be read, so arguments this version defines
differently would still be carried over.
{/if}
{single ? 'The restarted run keeps' : 'Restarted runs keep'} the values {single
? 'it has'
: 'they have'} now. To run this version with values that fit it, scale down to 0 here
and start a run yourself once deployed.
</p>
<Button
variant="default"
unifiedSize="xs"
btnClasses="bg-surface"
disabled={stopping}
onclick={async () => {
stopping = true
stopped = await onStop()
stopping = false
}}
>
{stopping ? 'Scaling down to 0…' : 'Scale down to 0'}
</Button>
</div>
</Alert>
{/if}
{/if}
</div>
{/if}
</ConfirmationModal>