From 6fac8963909fb40726d85f5e897398d2a691edb0 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:51:15 +0100 Subject: [PATCH 01/25] fix: send alternatives on timeout toast (#6920) * Fix broken batch reruns InputTransformForm * send alternatives on timeout toast * Toast when runs page query is slow + throttle toasts spamming * configurable perPage in runs page * store perPage in query params * subtle border * nit fix * reduce to 25 actions on runs page * Fix annoying scrollbar due to AIChatLayout * nit animated pane * don't kill AI Chat Manager on pane close * button shrink 0 * nist * Cancelable Promise Utils * migrate to CancelablePromiseUtils * CancelablePromise onTimeout + update usage * CancelablePromise onTimeout + update usage * Loading spinner + fix per page bug in audit logs * Fix .cancel() not behaving as expected * fix nits * audit logs nits * auditlog filter fix selects * fix wrong number of jobs when switching perPage * default 1000 * Fix breaking merge conflict * Fix missing computeCompletedJobs * change audit logs default perPage to 100 --- frontend/package-lock.json | 8 +- frontend/package.json | 2 +- frontend/src/lib/cancelable-promise-utils.ts | 84 + frontend/src/lib/components/RunChart.svelte | 17 +- frontend/src/lib/components/RunsPage.svelte | 1365 +++++++++++++++++ .../auditLogs/AuditLogsFilters.svelte | 188 ++- .../auditLogs/AuditLogsTable.svelte | 23 +- .../auditLogs/AuditLogsTimeline.svelte | 2 +- .../src/lib/components/runs/JobsLoader.svelte | 294 ++-- .../components/runs/ManuelDatePicker.svelte | 20 +- .../src/lib/components/runs/RunsTable.svelte | 16 +- .../src/lib/components/select/Select.svelte | 5 +- .../components/select/SelectDropdown.svelte | 5 +- .../src/lib/components/select/utils.svelte.ts | 3 +- .../(root)/(logged)/audit_logs/+page.svelte | 5 +- .../(logged)/runs/[...path]/+page.svelte | 1326 +--------------- 16 files changed, 1769 insertions(+), 1594 deletions(-) create mode 100644 frontend/src/lib/cancelable-promise-utils.ts create mode 100644 frontend/src/lib/components/RunsPage.svelte diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d651c99a6f..ee00a7129b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,7 @@ "@redocly/json-to-json-schema": "^0.0.1", "@scalar/openapi-parser": "^0.15.0", "@tanstack/svelte-table": "npm:tanstack-table-8-svelte-5@^0.1", - "@tutorlatin/svelte-tiny-virtual-list": "^3.0.2", + "@tutorlatin/svelte-tiny-virtual-list": "^3.0.16", "@windmill-labs/svelte-dnd-action": "^0.9.44", "@xterm/addon-fit": "^0.10.0", "@xyflow/svelte": "^1.0.0", @@ -3020,9 +3020,9 @@ } }, "node_modules/@tutorlatin/svelte-tiny-virtual-list": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@tutorlatin/svelte-tiny-virtual-list/-/svelte-tiny-virtual-list-3.0.15.tgz", - "integrity": "sha512-ew61aZNXGf0b5X+UjbOAhiNwzI21vijhB/mtBs8bpNOVYQ50TG6Qx00t+fR5C72eGnmdzguewZ2WP6QPNOTQJg==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@tutorlatin/svelte-tiny-virtual-list/-/svelte-tiny-virtual-list-3.0.16.tgz", + "integrity": "sha512-JQSmhRDAFZbq2rTlzn+kFXJayi5VPLxeGjD01ZVyV2ti7PlQE/ov6rQFR1c8s7Y3B1OiTcv3oEWGi3ib69V8eQ==", "license": "MIT", "engines": { "node": ">=20.17.0" diff --git a/frontend/package.json b/frontend/package.json index dfb0fda59a..df05bf5de7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -89,7 +89,7 @@ "@redocly/json-to-json-schema": "^0.0.1", "@scalar/openapi-parser": "^0.15.0", "@tanstack/svelte-table": "npm:tanstack-table-8-svelte-5@^0.1", - "@tutorlatin/svelte-tiny-virtual-list": "^3.0.2", + "@tutorlatin/svelte-tiny-virtual-list": "^3.0.16", "@windmill-labs/svelte-dnd-action": "^0.9.44", "@xterm/addon-fit": "^0.10.0", "@xyflow/svelte": "^1.0.0", diff --git a/frontend/src/lib/cancelable-promise-utils.ts b/frontend/src/lib/cancelable-promise-utils.ts new file mode 100644 index 0000000000..17e8be5a93 --- /dev/null +++ b/frontend/src/lib/cancelable-promise-utils.ts @@ -0,0 +1,84 @@ +import { CancelablePromise } from './gen' + +export namespace CancelablePromiseUtils { + export function then( + promise: CancelablePromise, + f: (value: T) => CancelablePromise + ): CancelablePromise { + let promiseToBeCanceled: CancelablePromise = promise + let p = new CancelablePromise((resolve, reject) => { + promise + .then((value1) => { + let promise2 = f(value1) + promiseToBeCanceled = promise2 + promise2.then((value2) => resolve(value2)).catch((err) => reject(err)) + }) + .catch((err) => reject(err)) + }) + p.cancel = () => promiseToBeCanceled.cancel() + return p + } + + export function pure(value: T): CancelablePromise { + return new CancelablePromise((resolve) => resolve(value)) + } + + export function err(error: any): CancelablePromise { + return new CancelablePromise((_, reject) => reject(error)) + } + + export function map( + promise: CancelablePromise, + f: (value: T) => U + ): CancelablePromise { + return then(promise, (value) => pure(f(value))) + } + + export function pipe( + promise: CancelablePromise, + f: (value: T) => void + ): CancelablePromise { + promise.then((value) => { + f(value) + }) + return promise + } + + export function catchErr( + promise: CancelablePromise, + f: (error: any) => CancelablePromise + ): CancelablePromise { + let promiseToBeCanceled: CancelablePromise = promise + let p = new CancelablePromise((resolve, reject) => { + promise + .then((value) => resolve(value)) + .catch((err) => { + let promise2 = f(err) + promiseToBeCanceled = promise2 + return promise2.then((value2) => resolve(value2)).catch((err2) => reject(err2)) + }) + .catch((err) => reject(err)) + }) + p.cancel = () => promiseToBeCanceled.cancel() + return p + } + + export function finallyDo(promise: CancelablePromise, f: () => void): CancelablePromise { + promise = map(promise, (value) => (f(), value)) + promise = catchErr(promise, (e) => (f(), err(e))) + return promise + } + + // Calls onTimeout if the promise does not settle within timeoutMs milliseconds + export function onTimeout( + promise: CancelablePromise, + timeoutMs: number, + onTimeout: () => void + ): CancelablePromise { + let timeoutId: number | undefined = setTimeout(onTimeout, timeoutMs) + promise = finallyDo(promise, () => { + if (timeoutId !== undefined) clearTimeout(timeoutId) + }) + return promise + } +} diff --git a/frontend/src/lib/components/RunChart.svelte b/frontend/src/lib/components/RunChart.svelte index d40cddc808..78873a3537 100644 --- a/frontend/src/lib/components/RunChart.svelte +++ b/frontend/src/lib/components/RunChart.svelte @@ -28,6 +28,7 @@ selectedIds?: string[] canSelect?: boolean lastFetchWentToEnd?: boolean + totalRowsFetched: number onPointClicked: (ids: string[]) => void onLoadExtra: () => void onZoom: (zoom: { min: Date; max: Date }) => void @@ -41,6 +42,7 @@ selectedIds = $bindable([]), canSelect = true, lastFetchWentToEnd = false, + totalRowsFetched, onPointClicked, onLoadExtra, onZoom @@ -301,13 +303,14 @@
{#if !lastFetchWentToEnd} -
+
+ +
{/if}
diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte new file mode 100644 index 0000000000..091dcc2992 --- /dev/null +++ b/frontend/src/lib/components/RunsPage.svelte @@ -0,0 +1,1365 @@ + + + + + + + { + const func = askingForConfirmation?.onConfirm + await func?.(forceCancelInPopup) + askingForConfirmation = undefined + }} + type={askingForConfirmation?.type} + loading={askingForConfirmation?.loading} + on:canceled={() => { + askingForConfirmation = undefined + }} +> + {#if askingForConfirmation?.preContent} +
{askingForConfirmation.preContent}
+ + {#if forceCancelInPopup} +
+

+ Force cancel is enabled. This is dangerous, only do this if you have no alternatives. + Instead of being gracefully cancelled, all jobs will be immediately sent to the completed + job table regardless of them being processed or not or part of running flows. You may end + up in an inconsistent state. +

+
+ {/if} + {/if} +
+ + + + {#if selectedIds.length === 1} + {#if selectedIds[0] === '-'} +
There is no information available for this job
+ {:else} + + {/if} + {/if} +
+
+ + { + reset() + loadFromQuery() + }} +/> + +{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find((_) => _.id === $workspaceStore)?.operator_settings?.runs} + +{:else} +
+ +
+
+
+

+ Runs +

+ + + All past and schedule executions of scripts and flows, including previews. You only see + your own runs or runs of groups you belong to unless you are an admin. + +
+ + + { + jobsFilter('waiting') + }} + onJobsSuspended={() => { + jobsFilter('suspended') + }} + small={innerWidth < smallScreenWidth} + /> +
+ +
+ +
+ + {#if minTs || maxTs} + + {/if} + { + minTs = new Date(detail).toISOString() + calendarChangeTimeout && clearTimeout(calendarChangeTimeout) + calendarChangeTimeout = setTimeout(() => { + jobsLoader?.loadJobs(minTs, maxTs, true) + }, 1000) + }} + on:clear={async () => { + minTs = undefined + calendarChangeTimeout && clearTimeout(calendarChangeTimeout) + calendarChangeTimeout = setTimeout(() => { + jobsLoader?.loadJobs(minTs, maxTs, true) + }, 1000) + }} + /> + + + + {#if maxTs || minTs} + + {/if} + { + maxTs = new Date(detail).toISOString() + calendarChangeTimeout && clearTimeout(calendarChangeTimeout) + calendarChangeTimeout = setTimeout(() => { + jobsLoader?.loadJobs(minTs, maxTs, true) + }, 1000) + }} + on:clear={async () => { + maxTs = undefined + calendarChangeTimeout && clearTimeout(calendarChangeTimeout) + calendarChangeTimeout = setTimeout(() => { + jobsLoader?.loadJobs(minTs, maxTs, true) + }, 1000) + }} + /> + + + {#if minTs || maxTs} + + + + {/if} +
+ + +
+ { + if (e.detail == 'running' && maxTs != undefined) { + maxTs = undefined + } + }} + {usernames} + {folders} + {paths} + mobile={innerWidth < verySmallScreenWidth} + small={innerWidth < smallScreenWidth} + calendarSmall={!minTs && !maxTs} + /> +
+
+
+ + +
+
+
+ { + graph = detail + graphIsRunsChart = graph === 'RunChart' + }} + > + {#snippet children({ item })} + + + {/snippet} + + + {#if !graphIsRunsChart} + setLookback(0), + id: '0' + }, + { + displayName: '1 day', + action: () => setLookback(1), + id: '1' + }, + { + displayName: '3 days', + action: () => setLookback(3), + id: '3' + }, + { + displayName: '7 days', + action: () => setLookback(7), + id: '7' + } + ]} + selected={lookback.toString()} + selectedDisplayName={`${lookback} days lookback`} + > + {#snippet extraLabel()} + + {#snippet text()} + How far behind the min datetime to start considering jobs for the concurrency + graph. Change this value to include jobs started before the set time window for + the computation of the graph + {/snippet} + + {/snippet} + + {/if} +
+
+ {#if graph === 'RunChart'} + { + minTs = zoom.min.toISOString() + maxTs = zoom.max.toISOString() + manualDatePicker?.resetChoice() + jobsLoader?.loadJobs(minTs, maxTs, true) + }} + onPointClicked={(ids) => { + runsTable?.scrollToRun(ids) + }} + /> + {:else if graph === 'ConcurrencyChart'} + { + minTs = zoom.min.toISOString() + maxTs = zoom.max.toISOString() + jobsLoader?.loadJobs(minTs, maxTs, true) + }} + /> + {/if} +
+ +
+ + +
+ +
+
+ {#if selectionMode && selectableJobCount} +
+
+ +
+ +
+ {/if} + + +
+ +
+
+ { + localStorage.setItem( + 'show_schedules_in_run', + showSchedules ? 'true' : 'false' + ) + }} + options={tableTopBarWidth < 800 || selectionMode + ? {} + : { right: 'Cron schedules' }} + /> + + + +
+ +
+ { + localStorage.setItem('show_future_jobs', showFutureJobs ? 'true' : 'false') + }} + id="planned-later" + options={tableTopBarWidth < 800 || selectionMode + ? {} + : { right: 'Planned later' }} + /> + + + +
+
+ { + lastFetchWentToEnd = false + jobsLoader?.loadJobs(minTs, maxTs, true) + }} + bind:minTs + bind:maxTs + bind:selectedManualDate + {loading} + bind:this={manualDatePicker} + numberOfLastJobsToFetch={perPage} + /> + { + localStorage.setItem('auto_refresh_in_runs', autoRefresh ? 'true' : 'false') + }} + options={{ right: 'Auto-refresh' }} + textClass="whitespace-nowrap" + /> +
+
+
+ + +
+ {#if jobs} + + {:else} +
+ {#each new Array(8) as _} + + {/each} +
+ {/if} +
+
+ Per page: + - {#if usernames} - {#if $userStore?.is_admin || $userStore?.is_super_admin} - - {/if} - {#each usernames as e} - {#if e == username || $userStore?.is_admin || $userStore?.is_super_admin} - - {:else} - - {/if} - {/each} - {/if} - + (resources.value?.push(r), (resource = r))} createText="Press enter to use this value" bind:value={resource} @@ -423,6 +401,7 @@ Operation - - {#each ['Create', 'Update', 'Delete', 'Execute'] as e} - - {/each} - + - {/if} - { - minTs = new Date(detail).toISOString() - calendarChangeTimeout && clearTimeout(calendarChangeTimeout) - calendarChangeTimeout = setTimeout(() => { - jobsLoader?.loadJobs(minTs, maxTs, true) - }, 1000) - }} - on:clear={async () => { - minTs = undefined - calendarChangeTimeout && clearTimeout(calendarChangeTimeout) - calendarChangeTimeout = setTimeout(() => { - jobsLoader?.loadJobs(minTs, maxTs, true) - }, 1000) - }} - /> - - - - {#if maxTs || minTs} - - {/if} - { - maxTs = new Date(detail).toISOString() - calendarChangeTimeout && clearTimeout(calendarChangeTimeout) - calendarChangeTimeout = setTimeout(() => { - jobsLoader?.loadJobs(minTs, maxTs, true) - }, 1000) - }} - on:clear={async () => { - maxTs = undefined - calendarChangeTimeout && clearTimeout(calendarChangeTimeout) - calendarChangeTimeout = setTimeout(() => { - jobsLoader?.loadJobs(minTs, maxTs, true) - }, 1000) - }} - /> - - - {#if minTs || maxTs} - - - - {/if} -
- - -
- { - if (e.detail == 'running' && maxTs != undefined) { - maxTs = undefined - } - }} - {usernames} - {folders} - {paths} - mobile={innerWidth < verySmallScreenWidth} - small={innerWidth < smallScreenWidth} - calendarSmall={!minTs && !maxTs} - /> -
-
-
- - -
-
-
- { - graph = detail - graphIsRunsChart = graph === 'RunChart' - }} - > - {#snippet children({ item })} - - - {/snippet} - - - {#if !graphIsRunsChart} - setLookback(0), - id: '0' - }, - { - displayName: '1 day', - action: () => setLookback(1), - id: '1' - }, - { - displayName: '3 days', - action: () => setLookback(3), - id: '3' - }, - { - displayName: '7 days', - action: () => setLookback(7), - id: '7' - } - ]} - selected={lookback.toString()} - selectedDisplayName={`${lookback} days lookback`} - > - {#snippet extraLabel()} - - {#snippet text()} - How far behind the min datetime to start considering jobs for the concurrency - graph. Change this value to include jobs started before the set time window for - the computation of the graph - {/snippet} - - {/snippet} - - {/if} -
-
- {#if graph === 'RunChart'} - { - minTs = zoom.min.toISOString() - maxTs = zoom.max.toISOString() - manualDatePicker?.resetChoice() - jobsLoader?.loadJobs(minTs, maxTs, true) - }} - onPointClicked={(ids) => { - runsTable?.scrollToRun(ids) - }} - /> - {:else if graph === 'ConcurrencyChart'} - { - minTs = zoom.min.toISOString() - maxTs = zoom.max.toISOString() - jobsLoader?.loadJobs(minTs, maxTs, true) - }} - /> - {/if} -
- -
- - -
- -
-
- {#if selectionMode && selectableJobCount} -
-
- -
- -
- {/if} - - -
- -
-
- { - localStorage.setItem( - 'show_schedules_in_run', - showSchedules ? 'true' : 'false' - ) - }} - options={tableTopBarWidth < 800 || selectionMode - ? {} - : { right: 'Cron schedules' }} - /> - - - -
- -
- { - localStorage.setItem('show_future_jobs', showFutureJobs ? 'true' : 'false') - }} - id="planned-later" - options={tableTopBarWidth < 800 || selectionMode - ? {} - : { right: 'Planned later' }} - /> - - - -
-
- { - lastFetchWentToEnd = false - jobsLoader?.loadJobs(minTs, maxTs, true) - }} - bind:minTs - bind:maxTs - bind:selectedManualDate - {loading} - bind:this={manualDatePicker} - /> - { - localStorage.setItem('auto_refresh_in_runs', autoRefresh ? 'true' : 'false') - }} - options={{ right: 'Auto-refresh' }} - textClass="whitespace-nowrap" - /> -
-
-
- - -
- {#if jobs} - - {:else} -
- {#each new Array(8) as _} - - {/each} -
- {/if} -
-
-
- 0}> - {#if selectionMode === 're-run'} - - {:else if selectedIds.length === 1} - {#if selectedIds[0] === '-'} -
There is no information available for this job
- {:else} - - {/if} - {:else if selectedIds.length > 1} -
There are {selectedIds.length} jobs selected. Choose 1 to see detailed information
- {/if} -
-
-
-
-{/if} +{#key perPage} + +{/key} From 0639623af6a16c0ad937746c0d28cf7c14e11719 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:50:04 +0100 Subject: [PATCH 02/25] Trigger kind filter (#7123) * Pass JobTriggerKind to push() to store it in v2_job * fix compiltion * frontend job trigger kind filter * SQL trigger_kind filtering * fix animated button * ee repo ref * Fix "Show schedule" + trigger kind = schedule * duplicate email Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- backend/ee-repo-ref.txt | 2 +- backend/tests/common/mod.rs | 1 + backend/tests/relative_imports.rs | 27 ++++++--- backend/windmill-api/openapi.yaml | 23 +++++++ backend/windmill-api/src/apps.rs | 3 + .../windmill-api/src/concurrency_groups.rs | 1 + backend/windmill-api/src/flows.rs | 2 + backend/windmill-api/src/jobs.rs | 50 ++++++++++++++-- backend/windmill-api/src/scripts.rs | 1 + .../windmill-api/src/triggers/http/handler.rs | 3 + backend/windmill-api/src/triggers/listener.rs | 1 + .../src/triggers/trigger_helpers.rs | 21 ++++++- .../src/triggers/websocket/listener.rs | 3 + .../src/triggers/websocket/mod.rs | 10 ++-- backend/windmill-queue/src/jobs.rs | 17 ++++-- backend/windmill-queue/src/schedule.rs | 1 + backend/windmill-worker/src/ai/tools.rs | 1 + backend/windmill-worker/src/worker_flow.rs | 1 + .../windmill-worker/src/worker_lockfiles.rs | 1 + frontend/src/lib/components/RunsPage.svelte | 60 +++++++++++++------ .../src/lib/components/runs/JobsLoader.svelte | 6 ++ .../src/lib/components/runs/RunsFilter.svelte | 28 +++++++-- .../components/triggers/CaptureSection.svelte | 7 +-- frontend/src/lib/components/triggers/utils.ts | 33 ++++++++++ 24 files changed, 250 insertions(+), 53 deletions(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 0da534da09..f4c0774520 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -7cdb62d968feef22046b12a1217b711af457cced \ No newline at end of file +4302ebbb8ded39e04ebcf7aaac71847c9bebe19c \ No newline at end of file diff --git a/backend/tests/common/mod.rs b/backend/tests/common/mod.rs index 40a9980a96..129fd86d63 100644 --- a/backend/tests/common/mod.rs +++ b/backend/tests/common/mod.rs @@ -208,6 +208,7 @@ impl RunJob { false, None, debounce_job_id_o, + None, ) .await .expect("push has to succeed"); diff --git a/backend/tests/relative_imports.rs b/backend/tests/relative_imports.rs index 2de18732b4..052f5a3743 100644 --- a/backend/tests/relative_imports.rs +++ b/backend/tests/relative_imports.rs @@ -421,14 +421,16 @@ def main(): path: "f/rel/root_flow_renamed".into(), summary: "".into(), description: None, - value: to_raw_value(&serde_json::from_str::( - &serde_json::to_string(flow.value()) - .unwrap() - .replace("nstep1", "Foxes") - .replace("nstep2_2", "like") - .replace("nstep_4_1", "Emeralds"), - ) - .unwrap()), + value: to_raw_value( + &serde_json::from_str::( + &serde_json::to_string(flow.value()) + .unwrap() + .replace("nstep1", "Foxes") + .replace("nstep2_2", "like") + .replace("nstep_4_1", "Emeralds"), + ) + .unwrap(), + ), schema: None, draft_only: None, tag: None, @@ -437,7 +439,7 @@ def main(): deployment_message: None, visible_to_runner_only: None, on_behalf_of_email: None, - ws_error_handler_muted: None + ws_error_handler_muted: None, }) .send() .await @@ -862,6 +864,7 @@ def main(): false, None, None, + None, ) .await .unwrap(); @@ -1021,6 +1024,7 @@ def main(): false, None, debounce_job_id_o, + None, ) .await .unwrap(); @@ -1198,6 +1202,7 @@ def main(): false, None, debounce_job_id_o, + None, ) .await .unwrap(); @@ -1705,6 +1710,7 @@ WHERE false, None, None, + None, ) .await .unwrap(); @@ -1844,6 +1850,7 @@ WHERE false, None, None, + None, ) .await .unwrap(); @@ -2274,6 +2281,7 @@ WHERE false, None, None, + None, ) .await .unwrap(); @@ -2399,6 +2407,7 @@ WHERE // false, // None, // None, + // None, // ) // .await // .unwrap(); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 36d4d30208..4171eaa178 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -8451,6 +8451,7 @@ paths: - $ref: "#/components/parameters/ResultFilter" - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/PerPage" + - $ref: "#/components/parameters/JobTriggerKind" - name: is_skipped description: is the job skipped in: query @@ -14424,6 +14425,7 @@ paths: - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" + - $ref: "#/components/parameters/JobTriggerKind" - name: is_skipped description: is the job skipped in: query @@ -14862,6 +14864,27 @@ components: in: query schema: type: integer + JobTriggerKind: + name: trigger_kind + description: trigger kind (schedule, http, websocket...) + in: query + schema: + type: string + enum: + - webhook + - default_email + - email + - schedule + - http + - websocket + - postgres + - kafka + - nats + - mqtt + - sqs + - gcp + - poll + - cli OrderDesc: name: order_desc description: order by desc order (default true) diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index b669f5e9a9..c7b1bd18bc 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -1241,6 +1241,7 @@ async fn create_app_internal<'a>( false, None, None, + None, ) .await?; tracing::info!("Pushed app dependency job {}", dependency_job_uuid); @@ -1630,6 +1631,7 @@ async fn update_app_internal<'a>( false, None, None, + None, ) .await?; tracing::info!("Pushed app dependency job {}", dependency_job_uuid); @@ -1949,6 +1951,7 @@ async fn execute_component( false, end_user_email, None, + None, ) .await?; tx.commit().await?; diff --git a/backend/windmill-api/src/concurrency_groups.rs b/backend/windmill-api/src/concurrency_groups.rs index bfd0b8182f..4a96a16e0b 100644 --- a/backend/windmill-api/src/concurrency_groups.rs +++ b/backend/windmill-api/src/concurrency_groups.rs @@ -222,6 +222,7 @@ async fn get_concurrent_intervals( all_workspaces: _, concurrency_key: Some(_), allow_wildcards: None, + trigger_kind: _, } => true, _ => false, }; diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index 9952ca1ddd..0b4bdbd0fb 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -562,6 +562,7 @@ async fn create_flow( false, None, None, + None, ) .await?; @@ -1025,6 +1026,7 @@ async fn update_flow( false, None, None, + None, ) .await?; diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index d695a6091d..57581d5446 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -34,7 +34,7 @@ use windmill_common::flow_conversations::add_message_to_conversation_tx; use windmill_common::flow_status::{JobResult, RestartedFrom}; use windmill_common::jobs::{ check_tag_available_for_workspace_internal, format_completed_job_result, format_result, - DynamicInput, ENTRYPOINT_OVERRIDE, + DynamicInput, JobTriggerKind, ENTRYPOINT_OVERRIDE, }; use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat}; use windmill_common::utils::{RunnableKind, WarnAfterExt}; @@ -1781,6 +1781,7 @@ pub struct ListQueueQuery { pub is_not_schedule: Option, pub concurrency_key: Option, pub allow_wildcards: Option, + pub trigger_kind: Option, } impl From for ListQueueQuery { @@ -1812,6 +1813,7 @@ impl From for ListQueueQuery { is_not_schedule: lcq.is_not_schedule, concurrency_key: lcq.concurrency_key, allow_wildcards: lcq.allow_wildcards, + trigger_kind: lcq.trigger_kind, } } } @@ -1935,6 +1937,10 @@ pub fn filter_list_queue_query( sqlb.and_where("trigger_kind IS DISTINCT FROM 'schedule'"); } + if let Some(tk) = &lq.trigger_kind { + sqlb.and_where_eq("trigger_kind", "?".bind(&format!("{}", tk))); + } + sqlb } @@ -3851,6 +3857,7 @@ async fn batch_rerun_handle_job( StripPath(job.script_path.clone()), RunJobQuery { skip_preprocessor: Some(true), ..Default::default() }, PushArgsOwned { extra: None, args }, + None, ) .await; if let Ok((uuid, _)) = result { @@ -3867,6 +3874,7 @@ async fn batch_rerun_handle_job( StripPath(job.script_path.clone()), RunJobQuery { skip_preprocessor: Some(true), ..Default::default() }, PushArgsOwned { extra: None, args }, + None, ) .await } else { @@ -3987,7 +3995,7 @@ pub async fn run_flow_by_path( .await?; let (uuid, _) = - run_flow_by_path_inner(authed, db, user_db, w_id, flow_path, run_query, args).await?; + run_flow_by_path_inner(authed, db, user_db, w_id, flow_path, run_query, args, None).await?; Ok((StatusCode::CREATED, uuid.to_string())) } @@ -4000,6 +4008,7 @@ pub async fn run_flow_by_path_inner( flow_path: StripPath, run_query: RunJobQuery, args: PushArgsOwned, + trigger_kind: Option, ) -> error::Result<(Uuid, Option)> { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -4078,6 +4087,7 @@ pub async fn run_flow_by_path_inner( false, None, None, + trigger_kind, ) .await?; @@ -4195,6 +4205,7 @@ pub async fn restart_flow( false, None, None, + None, ) .await?; tx.commit().await?; @@ -4219,8 +4230,17 @@ pub async fn run_script_by_path( ) .await?; - let (uuid, _) = - run_script_by_path_inner(authed, db, user_db, w_id, script_path, run_query, args).await?; + let (uuid, _) = run_script_by_path_inner( + authed, + db, + user_db, + w_id, + script_path, + run_query, + args, + None, + ) + .await?; Ok((StatusCode::CREATED, uuid.to_string())) } @@ -4233,6 +4253,7 @@ pub async fn run_script_by_path_inner( script_path: StripPath, run_query: RunJobQuery, args: PushArgsOwned, + trigger_kind: Option, ) -> error::Result<(Uuid, Option)> { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -4304,6 +4325,7 @@ pub async fn run_script_by_path_inner( false, None, None, + trigger_kind, ) .await?; tx.commit().await?; @@ -4461,6 +4483,7 @@ pub async fn run_workflow_as_code( false, None, None, + None, ) .await?; @@ -5005,6 +5028,7 @@ pub async fn run_wait_result_job_by_path_get( false, None, None, + None, ) .await?; tx.commit().await?; @@ -5159,6 +5183,7 @@ pub async fn run_wait_result_script_by_path_internal( false, None, None, + None, ) .await?; tx.commit().await?; @@ -5281,6 +5306,7 @@ pub async fn run_wait_result_script_by_hash( false, None, None, + None, ) .await?; tx.commit().await?; @@ -5439,6 +5465,7 @@ pub async fn stream_job( StripPath(script_path), run_query, args, + None, ) .await? .0 @@ -5465,6 +5492,7 @@ pub async fn stream_job( StripPath(flow_path), run_query, args, + None, ) .await? .0 @@ -5594,6 +5622,7 @@ pub async fn run_wait_result_flow_by_path_internal( false, None, None, + None, ) .await?; @@ -5688,6 +5717,7 @@ async fn run_preview_script( false, None, None, + None, ) .await?; tx.commit().await?; @@ -5808,6 +5838,7 @@ async fn run_bundle_preview_script( false, None, None, + None, ) .await?; job_id = Some(uuid); @@ -5947,6 +5978,7 @@ async fn run_dependencies_job( false, None, None, + None, ) .await?; tx.commit().await?; @@ -6016,6 +6048,7 @@ async fn run_flow_dependencies_job( false, None, None, + None, ) .await?; tx.commit().await?; @@ -6369,6 +6402,7 @@ async fn run_preview_flow_job( false, None, None, + None, ) .await?; @@ -6461,6 +6495,7 @@ async fn run_dynamic_select( StripPath(path), run_query.clone(), push_args.clone(), + None, ) .await?; @@ -6565,6 +6600,7 @@ async fn run_dynamic_select( false, None, None, + None, ) .await?; tx.commit().await?; @@ -6698,6 +6734,7 @@ pub async fn run_job_by_hash_inner( false, None, None, + None, ) .await?; tx.commit().await?; @@ -7666,6 +7703,10 @@ pub fn filter_list_completed_query( sqlb.and_where("trigger_kind IS DISTINCT FROM 'schedule'"); } + if let Some(tk) = &lq.trigger_kind { + sqlb.and_where_eq("trigger_kind", "?".bind(&format!("{}", tk))); + } + sqlb } @@ -7742,6 +7783,7 @@ pub struct ListCompletedQuery { pub concurrency_key: Option, pub worker: Option, pub allow_wildcards: Option, + pub trigger_kind: Option, } async fn list_completed_jobs( diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index a639c58f6e..82c8864fe3 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -1022,6 +1022,7 @@ async fn create_script_internal<'c>( false, None, None, + None, ) .await?; Ok((hash, new_tx, None)) diff --git a/backend/windmill-api/src/triggers/http/handler.rs b/backend/windmill-api/src/triggers/http/handler.rs index 5d8898f121..fd051e05be 100644 --- a/backend/windmill-api/src/triggers/http/handler.rs +++ b/backend/windmill-api/src/triggers/http/handler.rs @@ -1050,6 +1050,7 @@ async fn route_job( trigger.error_handler_args.as_ref(), format!("http_trigger/{}", trigger.path), None, + Some(windmill_common::jobs::JobTriggerKind::Http), ) .await .map_err(|e| e.into_response())?; @@ -1108,6 +1109,7 @@ async fn route_job( trigger.error_handler_args.as_ref(), format!("http_trigger/{}", trigger.path), None, + Some(windmill_common::jobs::JobTriggerKind::Http), ) .await .map_err(|e| e.into_response()), @@ -1123,6 +1125,7 @@ async fn route_job( trigger.error_handler_path.as_deref(), trigger.error_handler_args.as_ref(), format!("http_trigger/{}", trigger.path), + Some(windmill_common::jobs::JobTriggerKind::Http), ) .await .map_err(|e| e.into_response()), diff --git a/backend/windmill-api/src/triggers/listener.rs b/backend/windmill-api/src/triggers/listener.rs index 431c4313f9..af9d0e32ce 100644 --- a/backend/windmill-api/src/triggers/listener.rs +++ b/backend/windmill-api/src/triggers/listener.rs @@ -515,6 +515,7 @@ pub trait Listener: TriggerCrud + TriggerJobArgs { error_handler_args, format!("{}_trigger/{}", Self::TRIGGER_KIND, listening_trigger.path), None, + Some(Self::JOB_TRIGGER_KIND), ) .await?; diff --git a/backend/windmill-api/src/triggers/trigger_helpers.rs b/backend/windmill-api/src/triggers/trigger_helpers.rs index 7deaf6dc1d..d72a88e773 100644 --- a/backend/windmill-api/src/triggers/trigger_helpers.rs +++ b/backend/windmill-api/src/triggers/trigger_helpers.rs @@ -12,7 +12,10 @@ use windmill_common::{ error::Result, flows::{FlowModuleValue, Retry}, get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, - jobs::{get_has_preprocessor_from_content_and_lang, script_path_to_payload, JobPayload}, + jobs::{ + get_has_preprocessor_from_content_and_lang, script_path_to_payload, JobPayload, + JobTriggerKind, + }, scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang}, triggers::{ HubOrWorkspaceId, RunnableFormat, RunnableFormatVersion, TriggerKind, @@ -494,6 +497,7 @@ pub async fn trigger_runnable_inner( error_handler_args: Option<&sqlx::types::Json>>, trigger_path: String, job_id: Option, + trigger_kind: Option, ) -> Result<(Uuid, Option, Option)> { let error_handler_args = error_handler_args.map(|args| { let args = args @@ -516,6 +520,7 @@ pub async fn trigger_runnable_inner( path, run_query, args, + trigger_kind, ) .await?; (uuid, None, early_return) @@ -532,6 +537,7 @@ pub async fn trigger_runnable_inner( error_handler_args.as_ref(), trigger_path, job_id, + trigger_kind, ) .await?; (uuid, delete_after_use, None) @@ -554,6 +560,7 @@ pub async fn trigger_runnable( error_handler_args: Option<&sqlx::types::Json>>, trigger_path: String, job_id: Option, + trigger_kind: Option, ) -> Result { let (uuid, _, _) = trigger_runnable_inner( db, @@ -568,6 +575,7 @@ pub async fn trigger_runnable( error_handler_args, trigger_path, job_id, + trigger_kind, ) .await?; Ok((StatusCode::CREATED, uuid.to_string()).into_response()) @@ -586,6 +594,7 @@ pub async fn trigger_runnable_and_wait_for_result( error_handler_path: Option<&str>, error_handler_args: Option<&sqlx::types::Json>>, trigger_path: String, + trigger_kind: Option, ) -> Result { let username = authed.username.clone(); let (uuid, delete_after_use, early_return) = trigger_runnable_inner( @@ -601,6 +610,7 @@ pub async fn trigger_runnable_and_wait_for_result( error_handler_args, trigger_path, None, + trigger_kind, ) .await?; let (result, success) = @@ -627,6 +637,7 @@ pub async fn trigger_runnable_and_wait_for_raw_result( error_handler_path: Option<&str>, error_handler_args: Option<&sqlx::types::Json>>, trigger_path: String, + trigger_kind: Option, ) -> Result<(Box, bool)> { let username = authed.username.clone(); let (uuid, delete_after_use, early_return) = trigger_runnable_inner( @@ -642,6 +653,7 @@ pub async fn trigger_runnable_and_wait_for_raw_result( error_handler_args, trigger_path, None, + trigger_kind, ) .await?; @@ -675,6 +687,7 @@ pub async fn trigger_runnable_and_wait_for_raw_result_with_error_ctx( error_handler_path: Option<&str>, error_handler_args: Option<&sqlx::types::Json>>, trigger_path: String, + trigger_kind: Option, ) -> Result> { let (result, success) = trigger_runnable_and_wait_for_raw_result( db, @@ -688,6 +701,7 @@ pub async fn trigger_runnable_and_wait_for_raw_result_with_error_ctx( error_handler_path, error_handler_args, trigger_path, + trigger_kind, ) .await?; @@ -714,6 +728,7 @@ async fn trigger_script_internal( error_handler_args: Option<&sqlx::types::Json>>>, trigger_path: String, job_id: Option, + trigger_kind: Option, ) -> Result<(Uuid, Option)> { if retry.is_none() && error_handler_path.is_none() { let run_query = RunJobQuery { job_id, ..Default::default() }; @@ -726,6 +741,7 @@ async fn trigger_script_internal( path, run_query, args, + trigger_kind, ) .await } else { @@ -741,6 +757,7 @@ async fn trigger_script_internal( error_handler_args, trigger_path, job_id, + trigger_kind, ) .await } @@ -758,6 +775,7 @@ async fn trigger_script_with_retry_and_error_handler( error_handler_args: Option<&sqlx::types::Json>>>, trigger_path: String, job_id: Option, + trigger_kind: Option, ) -> Result<(Uuid, Option)> { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -870,6 +888,7 @@ async fn trigger_script_with_retry_and_error_handler( false, None, None, + trigger_kind, ) .await?; tx.commit().await?; diff --git a/backend/windmill-api/src/triggers/websocket/listener.rs b/backend/windmill-api/src/triggers/websocket/listener.rs index bfecaf479d..a89532566a 100644 --- a/backend/windmill-api/src/triggers/websocket/listener.rs +++ b/backend/windmill-api/src/triggers/websocket/listener.rs @@ -94,6 +94,7 @@ impl ListeningTrigger { None, None, "".to_string(), // doesn't matter as no retry/error handler + Some(windmill_common::jobs::JobTriggerKind::Websocket), ) .await .map(|r| r.get().to_owned())?; @@ -392,6 +393,7 @@ impl Listener for WebsocketTrigger { error_handler_path.as_deref(), error_handler_args.as_ref(), format!("websocket_trigger/{}", trigger_path), + Some(windmill_common::jobs::JobTriggerKind::Websocket), ) => { if let Ok((result, success)) = result { if !success && !can_return_error_result { @@ -428,6 +430,7 @@ impl Listener for WebsocketTrigger { error_handler_args, format!("websocket_trigger/{}", listening_trigger.path), None, + Some(windmill_common::jobs::JobTriggerKind::Websocket), ) .await?; } diff --git a/backend/windmill-api/src/triggers/websocket/mod.rs b/backend/windmill-api/src/triggers/websocket/mod.rs index 7a4d98bcc1..e19c958423 100644 --- a/backend/windmill-api/src/triggers/websocket/mod.rs +++ b/backend/windmill-api/src/triggers/websocket/mod.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use crate::{ db::ApiAuthed, - triggers::trigger_helpers::{trigger_runnable_and_wait_for_raw_result_with_error_ctx, TriggerJobArgs}, + triggers::trigger_helpers::{ + trigger_runnable_and_wait_for_raw_result_with_error_ctx, TriggerJobArgs, + }, }; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; @@ -64,9 +66,8 @@ pub fn value_to_args_hashmap( args: Option<&Box>, ) -> Result>> { let args = if let Some(args) = args { - let args_map: Option> = - serde_json::from_str(args.get()) - .map_err(|e| Error::BadRequest(format!("invalid json: {}", e)))?; + let args_map: Option> = serde_json::from_str(args.get()) + .map_err(|e| Error::BadRequest(format!("invalid json: {}", e)))?; args_map .unwrap_or_else(HashMap::new) @@ -112,6 +113,7 @@ pub async fn get_url_from_runnable_value( None, None, "".to_string(), // doesn't matter as no retry/error handler + Some(windmill_common::jobs::JobTriggerKind::Websocket), ) .await?; diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 9f02f4be26..7783751944 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -470,6 +470,7 @@ pub async fn push_init_job<'c>( false, None, None, + None, ) .await?; inner_tx.commit().await?; @@ -528,6 +529,7 @@ pub async fn push_periodic_bash_job<'c>( false, None, None, + None, ) .await?; inner_tx.commit().await?; @@ -1373,6 +1375,7 @@ async fn restart_job_if_perpetual_inner( false, None, None, + None, ) .await?; tx.commit().await?; @@ -1923,6 +1926,7 @@ pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>( false, None, None, + None, ) .await?; tx.commit().await?; @@ -3882,6 +3886,7 @@ pub async fn push<'c, 'd>( // If we know there is already a debounce job, we can use this for debouncing. // NOTE: Only works with dependency jobs triggered by relative imports debounce_job_id_o: Option, + trigger_kind: Option, ) -> Result<(Uuid, Transaction<'c, Postgres>), Error> { #[cfg(feature = "cloud")] if *CLOUD_HOSTED { @@ -5277,11 +5282,13 @@ pub async fn push<'c, 'd>( // tracing::error!("Could not insert job_perms for job {job_id}: {err:#}"); // } - let trigger_kind = if schedule_path.is_some() { - Some(JobTriggerKind::Schedule) - } else { - None - }; + let trigger_kind = trigger_kind.or_else(|| { + if schedule_path.is_some() { + Some(JobTriggerKind::Schedule) + } else { + None + } + }); let root_job = if root_job.is_some() && (root_job == flow_innermost_root_job.or(parent_job).or(Some(job_id))) diff --git a/backend/windmill-queue/src/schedule.rs b/backend/windmill-queue/src/schedule.rs index aed22e3f28..b9bfb0fe58 100644 --- a/backend/windmill-queue/src/schedule.rs +++ b/backend/windmill-queue/src/schedule.rs @@ -503,6 +503,7 @@ pub async fn push_scheduled_job<'c>( false, None, None, + Some(windmill_common::jobs::JobTriggerKind::Schedule), ) .warn_after_seconds_with_sql(1, "push in push_scheduled_job".to_string()) .await?; diff --git a/backend/windmill-worker/src/ai/tools.rs b/backend/windmill-worker/src/ai/tools.rs index 74f46c2bf4..478eca5e54 100644 --- a/backend/windmill-worker/src/ai/tools.rs +++ b/backend/windmill-worker/src/ai/tools.rs @@ -471,6 +471,7 @@ async fn execute_windmill_tool( true, None, None, + None, ) .await?; diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index f550bcd09d..f0bc4cfc1e 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -3303,6 +3303,7 @@ async fn push_next_flow_job( false, None, None, + None, ) .warn_after_seconds(2) .await?; diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 44b68948a7..0b840884f5 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -670,6 +670,7 @@ pub async fn trigger_dependents_to_recompute_dependencies( false, None, debounce_job_id_o, + None, ) .await?; diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte index 091dcc2992..86bcac405d 100644 --- a/frontend/src/lib/components/RunsPage.svelte +++ b/frontend/src/lib/components/RunsPage.svelte @@ -54,6 +54,7 @@ import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte' import Select from '$lib/components/select/Select.svelte' import AnimatedPane from '$lib/components/splitPanes/AnimatedPane.svelte' + import type { JobTriggerType } from '$lib/components/triggers/utils' let { perPage = $bindable() }: { perPage: number } = $props() @@ -114,6 +115,9 @@ ? JSON.parse(decodeURIComponent(page.url.searchParams.get('result') ?? '{}')) : undefined ) + let jobTriggerKind: JobTriggerType | undefined = $state( + (page.url.searchParams.get('job_trigger_kind') as JobTriggerType) ?? undefined + ) // Handled on the main page let minTs = $state(page.url.searchParams.get('min_ts') ?? undefined) @@ -163,6 +167,7 @@ resultFilter = page.url.searchParams.get('result') ? JSON.parse(decodeURIComponent(page.url.searchParams.get('result') ?? '{}')) : undefined + jobTriggerKind = (page.url.searchParams.get('job_trigger_kind') as JobTriggerType) ?? undefined // Handled on the main page minTs = page.url.searchParams.get('min_ts') ?? undefined @@ -287,6 +292,13 @@ } else { searchParams.delete('result') } + + if (jobTriggerKind) { + searchParams.set('job_trigger_kind', jobTriggerKind) + } else { + searchParams.delete('job_trigger_kind') + } + if (schedulePath) { searchParams.set('schedule_path', schedulePath) } else { @@ -557,11 +569,18 @@ resultFilter && resultFilter != '{}' && resultFilter != '' && resultError == '' ? resultFilter : undefined, + jobTriggerKind, allWorkspaces: allWorkspaces ? true : undefined, allowWildcards: allowWildcards ? true : undefined } } + $effect(() => { + if (jobTriggerKind === 'schedule' && !showSchedules) { + showSchedules = true + } + }) + async function cancelJobs(uuidsToCancel: string[], forceCancel: boolean = false) { const uuids = await JobService.cancelSelection({ workspace: $workspaceStore ?? '', @@ -751,6 +770,7 @@ showFutureJobs, argFilter, resultFilter, + jobTriggerKind, schedulePath, jobKindsCat, concurrencyKey, @@ -837,6 +857,7 @@ {showSkipped} {argFilter} {resultFilter} + {jobTriggerKind} {showSchedules} {showFutureJobs} {schedulePath} @@ -1053,6 +1074,7 @@ bind:success bind:argFilter bind:resultFilter + bind:jobTriggerKind bind:argError bind:resultError bind:jobKindsCat @@ -1221,24 +1243,26 @@
-
- { - localStorage.setItem( - 'show_schedules_in_run', - showSchedules ? 'true' : 'false' - ) - }} - options={tableTopBarWidth < 800 || selectionMode - ? {} - : { right: 'Cron schedules' }} - /> - - - -
+ {#if !jobTriggerKind} +
+ { + localStorage.setItem( + 'show_schedules_in_run', + showSchedules ? 'true' : 'false' + ) + }} + options={tableTopBarWidth < 800 || selectionMode + ? {} + : { right: 'Schedules' }} + /> + + + +
+ {/if}
onParamChanges()) diff --git a/frontend/src/lib/components/runs/RunsFilter.svelte b/frontend/src/lib/components/runs/RunsFilter.svelte index 24b85b3cfc..0df73f1b42 100644 --- a/frontend/src/lib/components/runs/RunsFilter.svelte +++ b/frontend/src/lib/components/runs/RunsFilter.svelte @@ -2,7 +2,6 @@ import { Button } from '../common' import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte' import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte' - import Tooltip from '../Tooltip.svelte' import { CircleAlert, CircleCheck, Hourglass, ListFilterPlus, CirclePlay, X } from 'lucide-svelte' import JsonEditor from '../JsonEditor.svelte' import Toggle from '../Toggle.svelte' @@ -18,6 +17,7 @@ import DropdownSelect from '../DropdownSelect.svelte' import TooltipV2 from '$lib/components/meltComponents/Tooltip.svelte' import TextInput from '../text_input/TextInput.svelte' + import { jobTriggerTypes, triggerDisplayNamesMap, type JobTriggerType } from '../triggers/utils' interface Props { // Filters @@ -31,6 +31,7 @@ argFilter: string argError: string resultFilter: string + jobTriggerKind: JobTriggerType | undefined resultError: string jobKindsCat: string user?: string | null @@ -67,6 +68,7 @@ argFilter = $bindable(), argError = $bindable(), resultFilter = $bindable(), + jobTriggerKind = $bindable(), resultError = $bindable(), jobKindsCat = $bindable(), user = $bindable(null), @@ -949,26 +951,40 @@ {/if} + +