mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 16:05:42 +00:00
feat: support multiple labels for jobs (wm_label -> wm_labels)
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DROP INDEX CONCURRENTLY IF EXISTS labeled_jobs_on_jobs",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6f12be65a4fe3eb39292164363f557de9cef7017dcfbcd40370b849a288c52e3"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "CREATE INDEX CONCURRENTLY labeled_jobs_on_completed_jobs ON completed_job USING GIN ((result -> 'wm_label')) WHERE result ? 'wm_label';",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8199fa82078d7fb8396f21a3f58e6169edb0b3a56cb50e03f9df4df511ff1b67"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "CREATE INDEX CONCURRENTLY labeled_jobs_on_jobs ON completed_job USING GIN ((result -> 'wm_labels')) WHERE result ? 'wm_label';",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "89e72f997e27b9298bd923e7f2b546c5ff061bd9ed63798024306456ca148aec"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1 @@
|
||||
-- Add up migration script here
|
||||
@@ -1026,7 +1026,7 @@ async fn listen_for_uuid_on(
|
||||
|
||||
async fn completed_job(uuid: Uuid, db: &Pool<Postgres>) -> CompletedJob {
|
||||
|
||||
sqlx::query_as::<_, CompletedJob>("SELECT *, result->>'wm_label' as label FROM completed_job WHERE id = $1").bind(uuid)
|
||||
sqlx::query_as::<_, CompletedJob>("SELECT *, result->'wm_labels' as label FROM completed_job WHERE id = $1").bind(uuid)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.unwrap()
|
||||
|
||||
@@ -8051,7 +8051,7 @@ components:
|
||||
type: string
|
||||
Label:
|
||||
name: label
|
||||
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')
|
||||
description: mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels')
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -8780,6 +8780,10 @@ components:
|
||||
type: string
|
||||
priority:
|
||||
type: integer
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- created_by
|
||||
|
||||
@@ -136,12 +136,13 @@ impl Migrate for CustomMigrator {
|
||||
migration.version,
|
||||
migration.description
|
||||
);
|
||||
if migration.version == 20240422144808 {
|
||||
tracing::info!(
|
||||
"Special migration to add index concurrently on completed_job labels"
|
||||
);
|
||||
if migration.version == 20240424083501 {
|
||||
tracing::info!("Special migration to add index concurrently on job labels 2");
|
||||
sqlx::query!(
|
||||
"CREATE INDEX CONCURRENTLY labeled_jobs_on_completed_jobs ON completed_job USING GIN ((result -> 'wm_label')) WHERE result ? 'wm_label';"
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS labeled_jobs_on_jobs"
|
||||
).execute(&mut *self.inner).await?;
|
||||
sqlx::query!(
|
||||
"CREATE INDEX CONCURRENTLY labeled_jobs_on_jobs ON completed_job USING GIN ((result -> 'wm_labels')) WHERE result ? 'wm_label';"
|
||||
).execute(&mut *self.inner).await?;
|
||||
}
|
||||
let r = self.inner.apply(migration).await;
|
||||
|
||||
@@ -585,7 +585,7 @@ fn generate_get_job_query(no_logs: bool, table: &str) -> String {
|
||||
result,
|
||||
deleted,
|
||||
is_skipped,
|
||||
result->>'wm_label' as label,
|
||||
result->'wm_labels' as labels,
|
||||
CASE WHEN result is null or pg_column_size(result) < 2000000 THEN result ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as result"
|
||||
} else {
|
||||
"scheduled_for,
|
||||
@@ -781,7 +781,7 @@ pub struct ListableCompletedJob {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label: Option<String>,
|
||||
pub labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
@@ -1179,7 +1179,7 @@ async fn list_jobs(
|
||||
"null as concurrent_limit",
|
||||
"null as concurrency_time_window_s",
|
||||
"priority",
|
||||
"result->>'wm_label' as label",
|
||||
"result->'wm_labels' as labels",
|
||||
],
|
||||
))
|
||||
} else {
|
||||
@@ -1246,7 +1246,7 @@ async fn list_jobs(
|
||||
"concurrent_limit",
|
||||
"concurrency_time_window_s",
|
||||
"priority",
|
||||
"null as label",
|
||||
"null as labels",
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1979,7 +1979,7 @@ struct UnifiedJob {
|
||||
concurrent_limit: Option<i32>,
|
||||
concurrency_time_window_s: Option<i32>,
|
||||
priority: Option<i16>,
|
||||
label: Option<String>,
|
||||
labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl<'a> From<UnifiedJob> for Job {
|
||||
@@ -2017,7 +2017,7 @@ impl<'a> From<UnifiedJob> for Job {
|
||||
mem_peak: uj.mem_peak,
|
||||
tag: uj.tag,
|
||||
priority: uj.priority,
|
||||
label: uj.label,
|
||||
labels: uj.labels,
|
||||
}),
|
||||
"QueuedJob" => Job::QueuedJob(QueuedJob {
|
||||
workspace_id: uj.workspace_id,
|
||||
@@ -2298,7 +2298,7 @@ pub async fn restart_flow(
|
||||
check_license_key_valid().await?;
|
||||
|
||||
let completed_job = sqlx::query_as::<_, CompletedJob>(
|
||||
"SELECT *, result->>'wm_label' as label from completed_job WHERE id = $1 and workspace_id = $2",
|
||||
"SELECT *, result->'wm_labels' as labels from completed_job WHERE id = $1 and workspace_id = $2",
|
||||
)
|
||||
.bind(job_id)
|
||||
.bind(&w_id)
|
||||
@@ -3746,8 +3746,10 @@ fn list_completed_jobs_query(
|
||||
}
|
||||
|
||||
if let Some(label) = &lq.label {
|
||||
sqlb.and_where("result->>'wm_label' = ?".bind(label));
|
||||
sqlb.and_where("result ? 'wm_label'");
|
||||
let mut wh = format!("result->'wm_labels' ? ");
|
||||
wh.push_str(&format!("'{}'", &label.replace("'", "''")));
|
||||
sqlb.and_where(&wh);
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
}
|
||||
|
||||
if lq.is_not_schedule.unwrap_or(false) {
|
||||
@@ -3834,7 +3836,7 @@ async fn list_completed_jobs(
|
||||
"mem_peak",
|
||||
"tag",
|
||||
"priority",
|
||||
"result->>'wm_label' as label",
|
||||
"result->'wm_labels' as labels",
|
||||
"'CompletedJob' as type",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -244,7 +244,7 @@ pub struct CompletedJob {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label: Option<String>,
|
||||
pub labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl CompletedJob {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
export let selectedId: string | undefined = undefined
|
||||
export let containerWidth: number = 0
|
||||
export let containsLabel: boolean = false
|
||||
export let activeLabel: string | null
|
||||
|
||||
let scheduleEditor: ScheduleEditor
|
||||
</script>
|
||||
@@ -108,7 +109,10 @@
|
||||
<div class="whitespace-nowrap text-xs font-semibold truncate">
|
||||
{#if job.script_path}
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<a href="/run/{job.id}?workspace={job.workspace_id}" class="truncate w-30 dark:text-blue-400">
|
||||
<a
|
||||
href="/run/{job.id}?workspace={job.workspace_id}"
|
||||
class="truncate w-30 dark:text-blue-400"
|
||||
>
|
||||
{job.script_path}
|
||||
</a>
|
||||
<Button
|
||||
@@ -173,18 +177,24 @@
|
||||
</div>
|
||||
{#if containsLabel}
|
||||
<div class="w-3/12 flex justify-start">
|
||||
{#if job && job?.['label']}
|
||||
{#if job && job?.['labels']}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<div class="text-xs">{job?.['label']}</div>
|
||||
<Button
|
||||
size="xs2"
|
||||
color="light"
|
||||
on:click={() => {
|
||||
dispatch('filterByLabel', job?.['label'])
|
||||
}}
|
||||
>
|
||||
<ListFilter size={10} />
|
||||
</Button>
|
||||
{#if Array.isArray(job?.['labels'])}
|
||||
{#each job?.['labels'] as label}
|
||||
<Button
|
||||
variant="border"
|
||||
size="xs2"
|
||||
btnClasses={activeLabel == label ? 'bg-blue-50 dark:bg-blue-900/50' : undefined}
|
||||
color="light"
|
||||
on:click={() => {
|
||||
dispatch('filterByLabel', label)
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
<ListFilter size={10} />
|
||||
</Button>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
|
||||
<span class="text-xs absolute -top-4"
|
||||
>Label <Tooltip
|
||||
>Labels are values in the result of completed jobs at key 'wm_label' to easily
|
||||
>Labels are string values in the array at the result field 'wm_labels' to easily
|
||||
filter them</Tooltip
|
||||
></span
|
||||
>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
export let jobs: Job[] | undefined = undefined
|
||||
export let selectedId: string | undefined = undefined
|
||||
export let selectedWorkspace: string | undefined = undefined
|
||||
export let activeLabel: string | null = null
|
||||
// const loadMoreQuantity: number = 100
|
||||
|
||||
function getTime(job: Job): string | undefined {
|
||||
@@ -22,7 +23,7 @@
|
||||
|
||||
let newContainsLabel = false
|
||||
for (const job of jobs) {
|
||||
if (job?.['label'] != undefined) {
|
||||
if (job?.['labels'] != undefined) {
|
||||
newContainsLabel = true
|
||||
}
|
||||
const field: string | undefined = getTime(job)
|
||||
@@ -182,6 +183,7 @@
|
||||
selectedId = jobOrDate.job.id
|
||||
dispatch('select')
|
||||
}}
|
||||
{activeLabel}
|
||||
on:filterByLabel
|
||||
on:filterByPath
|
||||
on:filterByUser
|
||||
|
||||
@@ -465,6 +465,7 @@
|
||||
{#if jobs}
|
||||
<RunsTable
|
||||
{jobs}
|
||||
activeLabel={label}
|
||||
bind:selectedId
|
||||
bind:selectedWorkspace
|
||||
on:filterByPath={filterByPath}
|
||||
@@ -629,6 +630,7 @@
|
||||
</div>
|
||||
<div class="grow">
|
||||
<RunsTable
|
||||
activeLabel={label}
|
||||
{jobs}
|
||||
bind:selectedId
|
||||
bind:selectedWorkspace
|
||||
|
||||
Reference in New Issue
Block a user