Skip to content

Commit fcff457

Browse files
committed
feat: add label and schedule filters to runs page
1 parent 032de9c commit fcff457

File tree

12 files changed

+263
-67
lines changed

12 files changed

+263
-67
lines changed

backend/.sqlx/query-8199fa82078d7fb8396f21a3f58e6169edb0b3a56cb50e03f9df4df511ff1b67.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Add down migration script here
2+
DROP INDEX IF EXISTS labeled_jobs_on_completed_jobs;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- Add up migration script here

backend/windmill-api/openapi.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5248,6 +5248,11 @@ paths:
52485248
in: query
52495249
schema:
52505250
type: boolean
5251+
- name: is_not_schedule
5252+
description: is not a scheduled job
5253+
in: query
5254+
schema:
5255+
type: boolean
52515256
responses:
52525257
"200":
52535258
description: All queued jobs
@@ -5333,6 +5338,7 @@ paths:
53335338
- $ref: "#/components/parameters/WorkspaceId"
53345339
- $ref: "#/components/parameters/OrderDesc"
53355340
- $ref: "#/components/parameters/CreatedBy"
5341+
- $ref: "#/components/parameters/Label"
53365342
- $ref: "#/components/parameters/ParentJob"
53375343
- $ref: "#/components/parameters/ScriptExactPath"
53385344
- $ref: "#/components/parameters/ScriptStartPath"
@@ -5362,6 +5368,11 @@ paths:
53625368
in: query
53635369
schema:
53645370
type: boolean
5371+
- name: is_not_schedule
5372+
description: is not a scheduled job
5373+
in: query
5374+
schema:
5375+
type: boolean
53655376
responses:
53665377
"200":
53675378
description: All completed jobs
@@ -5381,6 +5392,7 @@ paths:
53815392
parameters:
53825393
- $ref: "#/components/parameters/WorkspaceId"
53835394
- $ref: "#/components/parameters/CreatedBy"
5395+
- $ref: "#/components/parameters/Label"
53845396
- $ref: "#/components/parameters/ParentJob"
53855397
- $ref: "#/components/parameters/ScriptExactPath"
53865398
- $ref: "#/components/parameters/ScriptStartPath"
@@ -5423,6 +5435,11 @@ paths:
54235435
in: query
54245436
schema:
54255437
type: boolean
5438+
- name: is_not_schedule
5439+
description: is not a scheduled job
5440+
in: query
5441+
schema:
5442+
type: boolean
54265443
responses:
54275444
"200":
54285445
description: All jobs
@@ -8032,6 +8049,12 @@ components:
80328049
in: query
80338050
schema:
80348051
type: string
8052+
Label:
8053+
name: label
8054+
description: mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string at key 'wm_label')
8055+
in: query
8056+
schema:
8057+
type: string
80358058
ParentJob:
80368059
name: parent_job
80378060
description:

backend/windmill-api/src/db.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ impl Migrate for CustomMigrator {
136136
migration.version,
137137
migration.description
138138
);
139+
if migration.version == 20240422144808 {
140+
tracing::info!(
141+
"Special migration to add index concurrently on completed_job labels"
142+
);
143+
sqlx::query!(
144+
"CREATE INDEX CONCURRENTLY labeled_jobs_on_completed_jobs ON completed_job USING GIN ((result -> 'wm_label')) WHERE result ? 'wm_label';"
145+
).execute(&mut *self.inner).await?;
146+
}
139147
let r = self.inner.apply(migration).await;
140148
tracing::info!("Finished applying migration {}", migration.version);
141149
r

backend/windmill-api/src/jobs.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ fn generate_get_job_query(no_logs: bool, table: &str) -> String {
585585
result,
586586
deleted,
587587
is_skipped,
588+
result->>'wm_label' as label,
588589
CASE WHEN result is null or pg_column_size(result) < 2000000 THEN result ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as result"
589590
} else {
590591
"scheduled_for,
@@ -779,6 +780,8 @@ pub struct ListableCompletedJob {
779780
pub tag: String,
780781
#[serde(skip_serializing_if = "Option::is_none")]
781782
pub priority: Option<i16>,
783+
#[serde(skip_serializing_if = "Option::is_none")]
784+
pub label: Option<String>,
782785
}
783786

784787
#[derive(Deserialize, Clone)]
@@ -839,6 +842,7 @@ pub struct ListQueueQuery {
839842
pub all_workspaces: Option<bool>,
840843
pub is_flow_step: Option<bool>,
841844
pub has_null_parent: Option<bool>,
845+
pub is_not_schedule: Option<bool>,
842846
}
843847

844848
fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> SqlBuilder {
@@ -943,6 +947,10 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
943947
sqlb.and_where_le("scheduled_for", "now()");
944948
}
945949

950+
if lq.is_not_schedule.unwrap_or(false) {
951+
sqlb.and_where("schedule_path IS null");
952+
}
953+
946954
sqlb
947955
}
948956

@@ -1171,13 +1179,14 @@ async fn list_jobs(
11711179
"null as concurrent_limit",
11721180
"null as concurrency_time_window_s",
11731181
"priority",
1182+
"result->>'wm_label' as label",
11741183
],
11751184
))
11761185
} else {
11771186
None
11781187
};
11791188

1180-
let sql = if lq.success.is_none() {
1189+
let sql = if lq.success.is_none() && lq.label.is_none() {
11811190
let sqlq = list_queue_jobs_query(
11821191
&w_id,
11831192
&ListQueueQuery {
@@ -1203,6 +1212,7 @@ async fn list_jobs(
12031212
all_workspaces: lq.all_workspaces,
12041213
is_flow_step: lq.is_flow_step,
12051214
has_null_parent: lq.has_null_parent,
1215+
is_not_schedule: lq.is_not_schedule,
12061216
},
12071217
&[
12081218
"'QueuedJob' as typ",
@@ -1236,6 +1246,7 @@ async fn list_jobs(
12361246
"concurrent_limit",
12371247
"concurrency_time_window_s",
12381248
"priority",
1249+
"null as label",
12391250
],
12401251
);
12411252

@@ -1968,6 +1979,7 @@ struct UnifiedJob {
19681979
concurrent_limit: Option<i32>,
19691980
concurrency_time_window_s: Option<i32>,
19701981
priority: Option<i16>,
1982+
label: Option<String>,
19711983
}
19721984

19731985
impl<'a> From<UnifiedJob> for Job {
@@ -2005,6 +2017,7 @@ impl<'a> From<UnifiedJob> for Job {
20052017
mem_peak: uj.mem_peak,
20062018
tag: uj.tag,
20072019
priority: uj.priority,
2020+
label: uj.label,
20082021
}),
20092022
"QueuedJob" => Job::QueuedJob(QueuedJob {
20102023
workspace_id: uj.workspace_id,
@@ -3732,6 +3745,14 @@ fn list_completed_jobs_query(
37323745
sqlb.and_where("result @> ?".bind(&result.replace("'", "''")));
37333746
}
37343747

3748+
if let Some(label) = &lq.label {
3749+
sqlb.and_where("result->>'wm_label' = ?".bind(label));
3750+
}
3751+
3752+
if lq.is_not_schedule.unwrap_or(false) {
3753+
sqlb.and_where("schedule_path IS null");
3754+
}
3755+
37353756
sqlb
37363757
}
37373758
#[derive(Deserialize, Clone)]
@@ -3763,6 +3784,8 @@ pub struct ListCompletedQuery {
37633784
pub scheduled_for_before_now: Option<bool>,
37643785
pub all_workspaces: Option<bool>,
37653786
pub has_null_parent: Option<bool>,
3787+
pub label: Option<String>,
3788+
pub is_not_schedule: Option<bool>,
37663789
}
37673790

37683791
async fn list_completed_jobs(
@@ -3810,6 +3833,7 @@ async fn list_completed_jobs(
38103833
"mem_peak",
38113834
"tag",
38123835
"priority",
3836+
"result->>'wm_label' as label",
38133837
"'CompletedJob' as type",
38143838
],
38153839
)

backend/windmill-common/src/jobs.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ pub struct CompletedJob {
243243
pub tag: String,
244244
#[serde(skip_serializing_if = "Option::is_none")]
245245
pub priority: Option<i16>,
246+
#[serde(skip_serializing_if = "Option::is_none")]
247+
pub label: Option<String>,
246248
}
247249

248250
impl CompletedJob {

frontend/src/lib/components/runs/JobLoader.svelte

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
import { workspaceStore } from '$lib/stores'
77
88
import { tweened, type Tweened } from 'svelte/motion'
9-
import { forLater } from '$lib/forLater'
109
1110
export let jobs: Job[] | undefined
1211
export let user: string | null
12+
export let label: string | null
1313
export let folder: string | null
1414
export let path: string | null
1515
export let success: 'success' | 'failure' | 'running' | undefined = undefined
1616
export let isSkipped: boolean = false
17-
export let hideSchedules: boolean = false
17+
export let showSchedules: boolean = false
1818
export let argFilter: string | undefined
1919
export let resultFilter: string | undefined = undefined
2020
export let schedulePath: string | undefined = undefined
@@ -39,12 +39,13 @@
3939
$: jobKinds = computeJobKinds(jobKindsCat)
4040
$: ($workspaceStore && loadJobsIntern(true)) ||
4141
(path &&
42+
label &&
4243
success &&
4344
isSkipped != undefined &&
4445
jobKinds &&
4546
user &&
4647
folder &&
47-
hideSchedules != undefined &&
48+
showSchedules != undefined &&
4849
allWorkspaces != undefined &&
4950
argFilter != undefined &&
5051
resultFilter != undefined)
@@ -107,6 +108,8 @@
107108
running: success == 'running' ? true : undefined,
108109
isSkipped: isSkipped ? undefined : false,
109110
isFlowStep: jobKindsCat != 'all' ? false : undefined,
111+
label: label === null || label === '' ? undefined : label,
112+
isNotSchedule: showSchedules == false ? true : undefined,
110113
args:
111114
argFilter && argFilter != '{}' && argFilter != '' && argError == '' ? argFilter : undefined,
112115
result:
@@ -141,12 +144,6 @@
141144
try {
142145
jobs = await fetchJobs(maxTs, minTs)
143146
computeCompletedJobs()
144-
145-
if (hideSchedules && !schedulePath) {
146-
jobs = jobs.filter(
147-
(job) => !(job && 'running' in job && job.scheduled_for && forLater(job.scheduled_for))
148-
)
149-
}
150147
} catch (err) {
151148
sendUserToast(`There was a problem fetching jobs: ${err}`, true)
152149
console.error(JSON.stringify(err))
@@ -215,13 +212,6 @@
215212
.forEach((x) => (jobs![jobs?.findIndex((y) => y.id == x.id)!] = x))
216213
jobs = jobs
217214
computeCompletedJobs()
218-
219-
if (hideSchedules && !schedulePath) {
220-
jobs = jobs.filter(
221-
(job) =>
222-
!(job && 'running' in job && job.scheduled_for && forLater(job.scheduled_for))
223-
)
224-
}
225215
}
226216
227217
loading = false

frontend/src/lib/components/runs/RunRow.svelte

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
export let job: Job
2929
export let selectedId: string | undefined = undefined
3030
export let containerWidth: number = 0
31+
export let containsLabel: boolean = false
3132
3233
let scheduleEditor: ScheduleEditor
3334
</script>
@@ -170,6 +171,24 @@
170171
{/if}
171172
{/if}
172173
</div>
174+
{#if containsLabel}
175+
<div class="w-3/12 flex justify-start">
176+
{#if job && job?.['label']}
177+
<div class="flex flex-row items-center gap-1">
178+
<div class="text-xs">{job?.['label']}</div>
179+
<Button
180+
size="xs2"
181+
color="light"
182+
on:click={() => {
183+
dispatch('filterByLabel', job?.['label'])
184+
}}
185+
>
186+
<ListFilter size={10} />
187+
</Button>
188+
</div>
189+
{/if}
190+
</div>
191+
{/if}
173192
<div class="w-3/12 flex justify-start">
174193
{#if job && job.schedule_path}
175194
<div class="flex flex-row items-center gap-1">

0 commit comments

Comments
 (0)