Batch reruns actions + nits

This commit is contained in:
Diego Imbert
2026-02-09 10:52:59 +01:00
parent 5e2cf436f1
commit de95f4f64d
4 changed files with 147 additions and 111 deletions
@@ -38,4 +38,13 @@
</button>
</li>
{/each}
{#if items.length === 0}
<li class="w-full">
<div
class="px-3 h-9 text-xs font-normal w-full text-left flex items-center gap-2.5 text-hint"
>
No actions available
</div>
</li>
{/if}
</ul>
+15 -18
View File
@@ -47,6 +47,7 @@
let { initialPath }: Props = $props()
let batchRerunOptionsIsOpen = $state(false)
let filters = useSearchParams(runsFiltersSchema)
// Initialize path filter from route param if provided and not already set via query params
@@ -68,8 +69,6 @@
let loadingSelectedIds = $state(false)
let selectedWorkspace: string | undefined = $state(undefined)
let batchReRunOptions: BatchReRunOptions | undefined = $state(undefined)
let jobKinds: string | undefined = $derived(computeJobKinds(filters.job_kinds))
let paths: string[] = $state([])
let usernames: string[] = $state([])
@@ -159,7 +158,6 @@
filters.max_ts = null
selectedIds = []
filters.schedule_path = null
batchReRunOptions = undefined
selectedWorkspace = undefined
jobsLoader?.loadJobs(true)
}
@@ -319,7 +317,7 @@
}
}
async function reRunJobs(jobIdsToReRun: string[]) {
async function reRunJobs(jobIdsToReRun: string[], batchReRunOptions: BatchReRunOptions) {
if (!$workspaceStore) return
if (askingForConfirmation) {
@@ -328,8 +326,8 @@
const body: Parameters<typeof JobService.batchReRunJobs>[0]['requestBody'] = {
job_ids: jobIdsToReRun,
script_options_by_path: batchReRunOptions?.script ?? {},
flow_options_by_path: batchReRunOptions?.flow ?? {}
script_options_by_path: batchReRunOptions.script ?? {},
flow_options_by_path: batchReRunOptions.flow ?? {}
}
// workaround because EventSource does not support POST requests
@@ -387,7 +385,6 @@
})
selectedIds = []
batchReRunOptions = undefined
jobsLoader?.loadJobs(true, true)
}
@@ -405,14 +402,14 @@
})
}
async function onReRunSelectedJobs() {
async function onReRunSelectedJobs(batchReRunOptions: BatchReRunOptions) {
const jobIdsToReRun = selectedIds
askingForConfirmation = {
title: `Confirm re-running the selected jobs`,
confirmBtnText: `Re-run ${jobIdsToReRun.length} jobs`,
type: 'reload',
onConfirm: async () => {
await reRunJobs(jobIdsToReRun)
await reRunJobs(jobIdsToReRun, batchReRunOptions)
}
}
}
@@ -754,13 +751,7 @@
on:filterByWorker={filterByWorker}
bind:this={runsTable}
perPage={filters.per_page}
bind:batchRerunOptionsIsOpen={
() => !!batchReRunOptions,
(opened) => {
if (!opened) batchReRunOptions = undefined
else batchReRunOptions = { flow: {}, script: {} }
}
}
bind:batchRerunOptionsIsOpen
></RunsTable>
{:else}
<div class="gap-1 flex flex-col">
@@ -807,8 +798,14 @@
</Pane>
<AnimatedPane size={40} minSize={15} class="flex flex-col" opened={selectedIds.length > 0}>
<div class="mt-14 overflow-y-auto pr-4 ml-2 relative flex-1">
{#if !!batchReRunOptions}
<BatchReRunOptionsPane {selectedIds} bind:options={batchReRunOptions} />
{#if batchRerunOptionsIsOpen}
<BatchReRunOptionsPane
{selectedIds}
onCancel={() => (batchRerunOptionsIsOpen = false)}
onConfirm={async (options) => {
await onReRunSelectedJobs(options)
}}
/>
{:else if selectedIds.length === 1}
{#if selectedIds[0] === '-'}
<div class="p-4">There is no information available for this job</div>
@@ -27,7 +27,7 @@
mergeSchemasForBatchReruns
} from '$lib/components/jobs/batchReruns'
import Toggle from '../Toggle.svelte'
import { TriangleAlert } from 'lucide-svelte'
import { RefreshCwIcon, TriangleAlert } from 'lucide-svelte'
import { readFieldsRecursively } from '$lib/utils'
import Button from '../common/button/Button.svelte'
import ResizeTransitionWrapper from '../common/ResizeTransitionWrapper.svelte'
@@ -35,13 +35,16 @@
let {
selectedIds,
options = $bindable()
onCancel,
onConfirm
}: {
selectedIds: string[]
options: BatchReRunOptions
onCancel: () => void
onConfirm: (options: BatchReRunOptions) => void
} = $props()
let selected: JobGroup | undefined = $state()
let options: BatchReRunOptions = $state({ flow: {}, script: {} })
watch(
() => jobGroups.current,
() => {
@@ -188,96 +191,108 @@
</Pane>
{/if}
<Pane size={68} class="relative">
<PanelSection
title="Inputs"
class="overflow-y-scroll absolute inset-0"
id="batch-rerun-options-args"
>
{#if selected}
<div class="text-sm w-full pb-2">
<Alert type="info" title="Available expressions :">
Use the <code>job</code> object to access data about the original job
</Alert>
</div>
<Toggle
checked={selectedUsesLatestSchema}
disabled={selected?.kind === 'flow'}
on:change={(e) => {
if (!selected) return
;(options[selected.kind][selected.script_path] ??= {}).use_latest_version =
e.detail as boolean
}}
size="sm"
options={{
right: 'Always use latest version',
rightTooltip:
selected.kind === 'flow'
? 'Flow jobs will always run on the latest version of the flow'
: 'Run all jobs with the latest version of the script even if they originally ran an older version'
}}
/>
<div class="flex flex-col absolute inset-0">
<PanelSection
title="Inputs"
class="overflow-y-scroll flex-1"
id="batch-rerun-options-args"
>
{#if selected}
<div class="text-sm w-full pb-2">
<Alert type="info" title="Available expressions :">
Use the <code>job</code> object to access data about the original job
</Alert>
</div>
<Toggle
checked={selectedUsesLatestSchema}
disabled={selected?.kind === 'flow'}
on:change={(e) => {
if (!selected) return
;(options[selected.kind][selected.script_path] ??= {}).use_latest_version =
e.detail as boolean
}}
size="sm"
options={{
right: 'Always use latest version',
rightTooltip:
selected.kind === 'flow'
? 'Flow jobs will always run on the latest version of the flow'
: 'Run all jobs with the latest version of the script even if they originally ran an older version'
}}
/>
<!-- Even if we use the latest schema, we want the editor -->
<!-- to only lint the original jobs' values -->
{@const displayedSchema = selectedUsesLatestSchema
? (selected.latest_schema as Schema | undefined)
: mergeSchemasForBatchReruns(selected.schemas.map((s) => (s.schema as Schema) ?? {}))}
{@const extraLib = buildExtraLibForBatchReruns({
schemas: selected.schemas,
script_path: selected.script_path
})}
<div class="w-full h-full">
{#key [selected, displayedSchema]}
{#each Object.keys(displayedSchema?.properties ?? {}) as propertyName}
<ResizeTransitionWrapper vertical innerClass="w-full">
<InputTransformForm
class="items-start mb-6"
arg={options[selected.kind][selected.script_path]?.input_transforms?.[
propertyName
] ?? {
type: 'javascript',
expr: batchReRunDefaultPropertyExpr(propertyName, selected.schemas)
}}
on:change={(e) => {
if (!selected) return
const newArg = e.detail.arg as InputTransform
;((options[selected.kind][selected.script_path] ??= {}).input_transforms ??=
{})[propertyName] = newArg
}}
argName={propertyName}
schema={displayedSchema ?? {}}
{extraLib}
previousModuleId={undefined}
pickableProperties={{
hasResume: false,
previousId: undefined,
priorIds: {},
flow_input: {}
}}
hideHelpButton
{...propertyAlwaysExists(propertyName, selected)
? {}
: {
headerTooltip:
'This property does not exist on all versions of the script. You can handle different cases in the code below',
HeaderTooltipIcon: TriangleAlert,
headerTooltipIconClass: 'text-orange-500'
}}
{...propertyAlwaysHasSameType(propertyName, selected)
? {}
: {
headerTooltip:
'This property does not always have the same type depending on the version of the script. You can handle different cases in the code below',
HeaderTooltipIcon: TriangleAlert,
headerTooltipIconClass: 'text-orange-500'
}}
/>
</ResizeTransitionWrapper>
{/each}
{/key}
</div>
{/if}
</PanelSection>
<!-- Even if we use the latest schema, we want the editor -->
<!-- to only lint the original jobs' values -->
{@const displayedSchema = selectedUsesLatestSchema
? (selected.latest_schema as Schema | undefined)
: mergeSchemasForBatchReruns(
selected.schemas.map((s) => (s.schema as Schema) ?? {})
)}
{@const extraLib = buildExtraLibForBatchReruns({
schemas: selected.schemas,
script_path: selected.script_path
})}
<div class="w-full h-full">
{#key [selected, displayedSchema]}
{#each Object.keys(displayedSchema?.properties ?? {}) as propertyName}
<ResizeTransitionWrapper vertical innerClass="w-full">
<InputTransformForm
class="items-start mb-6"
arg={options[selected.kind][selected.script_path]?.input_transforms?.[
propertyName
] ?? {
type: 'javascript',
expr: batchReRunDefaultPropertyExpr(propertyName, selected.schemas)
}}
on:change={(e) => {
if (!selected) return
const newArg = e.detail.arg as InputTransform
;((options[selected.kind][selected.script_path] ??=
{}).input_transforms ??= {})[propertyName] = newArg
}}
argName={propertyName}
schema={displayedSchema ?? {}}
{extraLib}
previousModuleId={undefined}
pickableProperties={{
hasResume: false,
previousId: undefined,
priorIds: {},
flow_input: {}
}}
hideHelpButton
{...propertyAlwaysExists(propertyName, selected)
? {}
: {
headerTooltip:
'This property does not exist on all versions of the script. You can handle different cases in the code below',
HeaderTooltipIcon: TriangleAlert,
headerTooltipIconClass: 'text-orange-500'
}}
{...propertyAlwaysHasSameType(propertyName, selected)
? {}
: {
headerTooltip:
'This property does not always have the same type depending on the version of the script. You can handle different cases in the code below',
HeaderTooltipIcon: TriangleAlert,
headerTooltipIconClass: 'text-orange-500'
}}
/>
</ResizeTransitionWrapper>
{/each}
{/key}
</div>
{/if}
</PanelSection>
<div class="flex justify-end gap-2 w-full pt-2 pb-2 pr-4">
<Button variant="subtle" onClick={onCancel}>Cancel</Button>
<Button
variant="accent"
onClick={() => onConfirm(options)}
endIcon={{ icon: RefreshCwIcon }}>Run again</Button
>
</div>
</div>
</Pane>
</Splitpanes>
</div>
@@ -4,7 +4,7 @@
import VirtualList from '@tutorlatin/svelte-tiny-virtual-list'
import { createEventDispatcher } from 'svelte'
import Tooltip from '../Tooltip.svelte'
import { AlertTriangle, CircleXIcon, RefreshCwIcon } from 'lucide-svelte'
import { AlertTriangle, CircleXIcon, ExternalLinkIcon, RefreshCwIcon } from 'lucide-svelte'
import Popover from '../Popover.svelte'
import { workspaceStore } from '$lib/stores'
import './runs-grid.css'
@@ -13,6 +13,7 @@
import RightClickPopover from '../RightClickPopover.svelte'
import DropdownMenu from '../DropdownMenu.svelte'
import { isJobCancelable, isJobReRunnable } from '$lib/utils'
import { goto } from '$lib/navigation'
interface Props {
//import InfiniteLoading from 'svelte-infinite-loading'
@@ -47,6 +48,7 @@
const keysPressed = useKeyPressed(['Shift', 'Control', 'Meta', 'A'], {
onKeyDown(key, e) {
if (key === 'A' && (keysPressed.Control || keysPressed.Meta)) {
if (batchRerunOptionsIsOpen) return
e.preventDefault()
e.stopPropagation()
selectedIds = flatJobs
@@ -364,6 +366,7 @@
on:select={() => {
const jobId = jobOrDate.job.id
if (keysPressed.Control || keysPressed.Meta) {
if (batchRerunOptionsIsOpen && !isJobReRunnable(jobOrDate.job)) return
if (selectedIds.includes(jobOrDate.job.id)) {
selectedIds = selectedIds.filter((id) => id != jobId)
} else {
@@ -371,6 +374,7 @@
selectedIds = selectedIds
}
} else if (keysPressed.Shift && selectedIds.length > 0) {
if (batchRerunOptionsIsOpen && !isJobReRunnable(jobOrDate.job)) return
const lastSelectedId = selectedIds[selectedIds.length - 1]
const lastSelectedIndex = flatJobs?.findIndex(
(jobOrDate) =>
@@ -452,6 +456,17 @@
<DropdownMenu
closeCallback={() => rightClickPopover?.close()}
items={[
...(selectedIds.length === 1
? [
{
label: 'Show run details',
icon: ExternalLinkIcon,
onClick: () => {
goto(`/run/${selectedIds[0]}`)
}
}
]
: []),
...(cancellable
? [
{