mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-20 00:03:21 +00:00
Batch reruns actions + nits
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
? [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user