feat: add wildcards filter for worker/label/tags

This commit is contained in:
Ruben Fiszel
2025-04-05 14:10:53 +00:00
parent eecf0e9f13
commit 21f37f0583
6 changed files with 119 additions and 30 deletions
+11
View File
@@ -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)
@@ -215,6 +215,7 @@ async fn get_concurrent_intervals(
is_flow_step: _,
all_workspaces: _,
concurrency_key: Some(_),
allow_wildcards: None,
} => true,
_ => false,
};
+38 -8
View File
@@ -1374,6 +1374,7 @@ pub struct ListQueueQuery {
pub has_null_parent: Option<bool>,
pub is_not_schedule: Option<bool>,
pub concurrency_key: Option<String>,
pub allow_wildcards: Option<bool>,
}
impl From<ListCompletedQuery> for ListQueueQuery {
@@ -1404,6 +1405,7 @@ impl From<ListCompletedQuery> 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<bool>,
pub concurrency_key: Option<String>,
pub worker: Option<String>,
pub allow_wildcards: Option<bool>,
}
async fn list_completed_jobs(
@@ -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
)
}
@@ -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)
}}
/>
<div class="absolute top-10">
<Toggle
bind:checked={allowWildcards}
size="xs"
options={{ right: 'allow wildcards (*)' }}
></Toggle>
</div>
</div>
{/key}
{:else if filterBy === 'concurrencyKey'}
@@ -388,6 +395,13 @@
}, 1000)
}}
/>
<div class="absolute top-10">
<Toggle
bind:checked={allowWildcards}
size="xs"
options={{ right: 'allow wildcards (*)' }}
></Toggle>
</div>
</div>
{/key}
{:else if filterBy === 'schedulePath'}
@@ -456,6 +470,13 @@
}, 1000)
}}
/>
<div class="absolute top-10">
<Toggle
bind:checked={allowWildcards}
size="xs"
options={{ right: 'allow wildcards (*)' }}
></Toggle>
</div>
</div>
{/key}
{/if}
@@ -826,6 +847,13 @@
{/key}
{/if}
{#if filterBy === 'tag' || filterBy === 'label' || filterBy === 'worker'}
<Toggle
bind:checked={allowWildcards}
size="xs"
options={{ right: 'allow wildcards (*)' }}
></Toggle>
{/if}
<Label label="Kind">
<ToggleButtonGroup bind:selected={jobKindsCat} let:item>
<ToggleButton value="all" label="All" {item} />
@@ -51,6 +51,7 @@
let user: string | null = $page.url.searchParams.get('user')
let folder: string | null = $page.url.searchParams.get('folder')
let label: string | null = $page.url.searchParams.get('label')
let allowWildcards: boolean = $page.url.searchParams.get('allow_wildcards') == 'true'
let concurrencyKey: string | null = $page.url.searchParams.get('concurrency_key')
let tag: string | null = $page.url.searchParams.get('tag')
// Rest of filters handled by RunsFilter
@@ -69,14 +70,14 @@
$page.url.searchParams.get('show_schedules') != undefined
? $page.url.searchParams.get('show_schedules') == 'true'
: localStorage.getItem('show_schedules_in_run') == 'false'
? false
: true
? false
: true
let showFutureJobs: boolean =
$page.url.searchParams.get('show_future_jobs') != undefined
? $page.url.searchParams.get('show_future_jobs') == 'true'
: localStorage.getItem('show_future_jobs') == 'false'
? false
: true
? false
: true
let argFilter: any = $page.url.searchParams.get('arg')
? JSON.parse(decodeURIComponent($page.url.searchParams.get('arg') ?? '{}'))
@@ -101,6 +102,7 @@
concurrencyKey = $page.url.searchParams.get('concurrency_key')
tag = $page.url.searchParams.get('tag')
worker = $page.url.searchParams.get('worker')
allowWildcards = $page.url.searchParams.get('allow_wildcards') == 'true'
// Rest of filters handled by RunsFilter
success = ($page.url.searchParams.get('success') ?? undefined) as
| 'running'
@@ -116,14 +118,14 @@
$page.url.searchParams.get('show_schedules') != undefined
? $page.url.searchParams.get('show_schedules') == 'true'
: localStorage.getItem('show_schedules_in_run') == 'false'
? false
: true
? false
: true
showFutureJobs =
$page.url.searchParams.get('show_future_jobs') != undefined
? $page.url.searchParams.get('show_future_jobs') == 'true'
: localStorage.getItem('show_future_jobs') == 'false'
? false
: true
? false
: true
argFilter = $page.url.searchParams.get('arg')
? JSON.parse(decodeURIComponent($page.url.searchParams.get('arg') ?? '{}'))
@@ -198,6 +200,7 @@
graph ||
maxTs ||
allWorkspaces ||
allowWildcards ||
$workspaceStore) &&
setQuery(false)
@@ -307,6 +310,12 @@
searchParams.delete('label')
}
if (allowWildcards) {
searchParams.set('allow_wildcards', allowWildcards.toString())
} else {
searchParams.delete('allow_wildcards')
}
if (graph != 'RunChart') {
searchParams.set('graph', graph)
} else {
@@ -411,6 +420,7 @@
tag = null
schedulePath = undefined
worker = null
allowWildcards = false
}
function filterByConcurrencyKey(e: CustomEvent<string>) {
@@ -433,6 +443,7 @@
tag = e.detail
schedulePath = undefined
worker = null
allowWildcards = false
}
function filterBySchedule(e: CustomEvent<string>) {
@@ -455,6 +466,7 @@
tag = null
schedulePath = undefined
worker = e.detail
allowWildcards = false
}
let calendarChangeTimeout: NodeJS.Timeout | undefined = undefined
@@ -499,8 +511,8 @@
success == 'running' || success == 'suspended'
? true
: success == 'waiting'
? false
: undefined,
? false
: undefined,
isSkipped: isSkipped ? undefined : false,
// isFlowStep: jobKindsCat != 'all' ? false : undefined,
hasNullParent:
@@ -519,7 +531,8 @@
resultFilter && resultFilter != '{}' && resultFilter != '' && resultError == ''
? resultFilter
: undefined,
allWorkspaces: allWorkspaces ? true : undefined
allWorkspaces: allWorkspaces ? true : undefined,
allowWildcards: allowWildcards ? true : undefined
}
selectedFiltersString = JSON.stringify(selectedFilters, null, 4)
@@ -577,6 +590,7 @@
</script>
<JobLoader
{allowWildcards}
{allWorkspaces}
bind:jobs
{user}
@@ -705,6 +719,7 @@
</div>
</div>
<RunsFilter
bind:allowWildcards
bind:isSkipped
bind:user
bind:folder
@@ -1092,6 +1107,7 @@
</Tooltip>
</div>
<RunsFilter
bind:allowWildcards
bind:isSkipped
{paths}
{usernames}