diff --git a/frontend/src/lib/components/DropdownMenu.svelte b/frontend/src/lib/components/DropdownMenu.svelte index 3ce29003d5..d47e798169 100644 --- a/frontend/src/lib/components/DropdownMenu.svelte +++ b/frontend/src/lib/components/DropdownMenu.svelte @@ -38,4 +38,13 @@ {/each} + {#if items.length === 0} +
  • +
    + No actions available +
    +
  • + {/if} diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte index 4e5df358ca..af9bb13be6 100644 --- a/frontend/src/lib/components/RunsPage.svelte +++ b/frontend/src/lib/components/RunsPage.svelte @@ -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[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 > {:else}
    @@ -807,8 +798,14 @@ 0}>
    - {#if !!batchReRunOptions} - + {#if batchRerunOptionsIsOpen} + (batchRerunOptionsIsOpen = false)} + onConfirm={async (options) => { + await onReRunSelectedJobs(options) + }} + /> {:else if selectedIds.length === 1} {#if selectedIds[0] === '-'}
    There is no information available for this job
    diff --git a/frontend/src/lib/components/runs/BatchReRunOptionsPane.svelte b/frontend/src/lib/components/runs/BatchReRunOptionsPane.svelte index a6542393ad..7a3d2d6051 100644 --- a/frontend/src/lib/components/runs/BatchReRunOptionsPane.svelte +++ b/frontend/src/lib/components/runs/BatchReRunOptionsPane.svelte @@ -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 @@ {/if} - - {#if selected} -
    - - Use the job object to access data about the original job - -
    - { - 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' - }} - /> +
    + + {#if selected} +
    + + Use the job object to access data about the original job + +
    + { + 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' + }} + /> - - - {@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 - })} -
    - {#key [selected, displayedSchema]} - {#each Object.keys(displayedSchema?.properties ?? {}) as propertyName} - - { - 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' - }} - /> - - {/each} - {/key} -
    - {/if} -
    + + + {@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 + })} +
    + {#key [selected, displayedSchema]} + {#each Object.keys(displayedSchema?.properties ?? {}) as propertyName} + + { + 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' + }} + /> + + {/each} + {/key} +
    + {/if} + +
    + + +
    +
    diff --git a/frontend/src/lib/components/runs/RunsTable.svelte b/frontend/src/lib/components/runs/RunsTable.svelte index c07d6d0a26..980f94e1c9 100644 --- a/frontend/src/lib/components/runs/RunsTable.svelte +++ b/frontend/src/lib/components/runs/RunsTable.svelte @@ -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 @@ rightClickPopover?.close()} items={[ + ...(selectedIds.length === 1 + ? [ + { + label: 'Show run details', + icon: ExternalLinkIcon, + onClick: () => { + goto(`/run/${selectedIds[0]}`) + } + } + ] + : []), ...(cancellable ? [ {