From 21f37f0583cd80450d84272931edfbfcb3a52c22 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 5 Apr 2025 14:10:53 +0000 Subject: [PATCH] feat: add wildcards filter for worker/label/tags --- backend/windmill-api/openapi.yaml | 11 +++++ .../windmill-api/src/concurrency_groups.rs | 1 + backend/windmill-api/src/jobs.rs | 46 +++++++++++++++---- .../src/lib/components/runs/JobLoader.svelte | 23 ++++++---- .../src/lib/components/runs/RunsFilter.svelte | 30 +++++++++++- .../(logged)/runs/[...path]/+page.svelte | 38 ++++++++++----- 6 files changed, 119 insertions(+), 30 deletions(-) diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 2275d8be2a..bef33a9b66 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -6786,6 +6786,7 @@ paths: - $ref: "#/components/parameters/Running" - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" @@ -6914,6 +6915,7 @@ paths: - $ref: "#/components/parameters/Running" - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" @@ -6992,6 +6994,7 @@ paths: - $ref: "#/components/parameters/JobKinds" - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" @@ -7055,6 +7058,7 @@ paths: - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" - name: is_skipped @@ -11888,6 +11892,7 @@ paths: - $ref: "#/components/parameters/ArgsFilter" - $ref: "#/components/parameters/Tag" - $ref: "#/components/parameters/ResultFilter" + - $ref: "#/components/parameters/AllowWildcards" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" - name: is_skipped @@ -12393,6 +12398,12 @@ components: in: query schema: type: boolean + AllowWildcards: + name: allow_wildcards + description: allow wildcards (*) in the filter of label, tag, worker + in: query + schema: + type: boolean ArgsFilter: name: args description: filter on jobs containing those args as a json subset (@> in postgres) diff --git a/backend/windmill-api/src/concurrency_groups.rs b/backend/windmill-api/src/concurrency_groups.rs index e59821c3e5..4135c3729c 100644 --- a/backend/windmill-api/src/concurrency_groups.rs +++ b/backend/windmill-api/src/concurrency_groups.rs @@ -215,6 +215,7 @@ async fn get_concurrent_intervals( is_flow_step: _, all_workspaces: _, concurrency_key: Some(_), + allow_wildcards: None, } => true, _ => false, }; diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index d2f09cfdc9..9d35f7bc0f 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -1374,6 +1374,7 @@ pub struct ListQueueQuery { pub has_null_parent: Option, pub is_not_schedule: Option, pub concurrency_key: Option, + pub allow_wildcards: Option, } impl From for ListQueueQuery { @@ -1404,6 +1405,7 @@ impl From for ListQueueQuery { has_null_parent: lcq.has_null_parent, is_not_schedule: lcq.is_not_schedule, concurrency_key: lcq.concurrency_key, + allow_wildcards: lcq.allow_wildcards, } } } @@ -1427,7 +1429,11 @@ pub fn filter_list_queue_query( } if let Some(w) = &lq.worker { - sqlb.and_where_eq("v2_job_queue.worker", "?".bind(w)); + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job_queue.worker", w.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job_queue.worker", "?".bind(w)); + } } if let Some(ps) = &lq.script_path_start { @@ -1447,8 +1453,13 @@ pub fn filter_list_queue_query( sqlb.and_where_eq("created_by", "?".bind(cb)); } if let Some(t) = &lq.tag { - sqlb.and_where_eq("v2_job.tag", "?".bind(t)); + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job.tag", t.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job.tag", "?".bind(t)); + } } + if let Some(r) = &lq.running { sqlb.and_where_eq("running", &r); } @@ -5446,14 +5457,27 @@ pub fn filter_list_completed_query( } if let Some(label) = &lq.label { - let mut wh = format!("result->'wm_labels' ? "); - wh.push_str(&format!("'{}'", &label.replace("'", "''"))); - sqlb.and_where("result ? 'wm_labels'"); - sqlb.and_where(&wh); + if lq.allow_wildcards.unwrap_or(false) { + let wh = format!( + "EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') label WHERE label LIKE '{}')", + &label.replace("*", "%").replace("'", "''") + ); + sqlb.and_where("result ? 'wm_labels'"); + sqlb.and_where(&wh); + } else { + let mut wh = format!("result->'wm_labels' ? "); + wh.push_str(&format!("'{}'", &label.replace("'", "''"))); + sqlb.and_where("result ? 'wm_labels'"); + sqlb.and_where(&wh); + } } if let Some(worker) = &lq.worker { - sqlb.and_where_eq("v2_job_completed.worker", "?".bind(worker)); + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job_completed.worker", worker.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job_completed.worker", "?".bind(worker)); + } } if w_id != "admins" || !lq.all_workspaces.is_some_and(|x| x) { @@ -5475,8 +5499,13 @@ pub fn filter_list_completed_query( sqlb.and_where_eq("runnable_id", "?".bind(h)); } if let Some(t) = &lq.tag { - sqlb.and_where_eq("v2_job.tag", "?".bind(t)); + if lq.allow_wildcards.unwrap_or(false) { + sqlb.and_where_like_left("v2_job.tag", t.replace("*", "%")); + } else { + sqlb.and_where_eq("v2_job.tag", "?".bind(t)); + } } + if let Some(cb) = &lq.created_by { sqlb.and_where_eq("created_by", "?".bind(cb)); } @@ -5618,6 +5647,7 @@ pub struct ListCompletedQuery { pub is_not_schedule: Option, pub concurrency_key: Option, pub worker: Option, + pub allow_wildcards: Option, } async fn list_completed_jobs( diff --git a/frontend/src/lib/components/runs/JobLoader.svelte b/frontend/src/lib/components/runs/JobLoader.svelte index e924151f53..6d939e42fe 100644 --- a/frontend/src/lib/components/runs/JobLoader.svelte +++ b/frontend/src/lib/components/runs/JobLoader.svelte @@ -53,7 +53,7 @@ | undefined export let lookback: number = 0 export let perPage: number | undefined = undefined - + export let allowWildcards: boolean = false let intervalId: NodeJS.Timeout | undefined let sync = true @@ -70,6 +70,7 @@ lookback && user && folder && + allowWildcards && schedulePath != undefined && showFutureJobs != undefined && showSchedules != undefined && @@ -168,8 +169,8 @@ success == 'running' || success == 'suspended' ? true : success == 'waiting' - ? false - : undefined, + ? false + : undefined, isSkipped: isSkipped ? undefined : false, // isFlowStep: jobKindsCat != 'all' ? false : undefined, hasNullParent: jobKindsCat != 'all' ? true : undefined, @@ -191,7 +192,8 @@ ? resultFilter : undefined, allWorkspaces: allWorkspaces ? true : undefined, - perPage + perPage, + allowWildcards }) } catch (e) { sendUserToast('There was an issue loading jobs, see browser console for more details', true) @@ -239,7 +241,8 @@ ? resultFilter : undefined, allWorkspaces: allWorkspaces ? true : undefined, - perPage + perPage, + allowWildcards }) } catch (e) { sendUserToast('There was an issue loading jobs, see browser console for more details', true) @@ -306,8 +309,8 @@ x.started_at ? new Date(x.started_at) > minDate : x.created_at - ? new Date(x.created_at) > minDate - : false + ? new Date(x.created_at) > minDate + : false ) externalJobs = computeExternalJobs( newExternalJobs.filter((x) => x.started_at && new Date(x.started_at) > minDate) @@ -440,8 +443,8 @@ x.started_at ? new Date(x.started_at) > minDate : x.created_at - ? new Date(x.created_at) > minDate - : false + ? new Date(x.created_at) > minDate + : false ) } else { return jobs @@ -482,7 +485,7 @@ tag: '-', job_kind: 'script', duration_ms: x.duration_ms - } as Job) + }) as Job ) } diff --git a/frontend/src/lib/components/runs/RunsFilter.svelte b/frontend/src/lib/components/runs/RunsFilter.svelte index 1325e85904..65860ccc53 100644 --- a/frontend/src/lib/components/runs/RunsFilter.svelte +++ b/frontend/src/lib/components/runs/RunsFilter.svelte @@ -46,7 +46,7 @@ export let folder: string | null = null export let mobile: boolean = false export let schedulePath: string | undefined - + export let allowWildcards: boolean = false // Autocomplete data export let paths: string[] = [] export let usernames: string[] = [] @@ -312,6 +312,13 @@ }, 1000) }} /> +
+ +
{/key} {:else if filterBy === 'concurrencyKey'} @@ -388,6 +395,13 @@ }, 1000) }} /> +
+ +
{/key} {:else if filterBy === 'schedulePath'} @@ -456,6 +470,13 @@ }, 1000) }} /> +
+ +
{/key} {/if} @@ -826,6 +847,13 @@ {/key} {/if} + {#if filterBy === 'tag' || filterBy === 'label' || filterBy === 'worker'} + + {/if}