diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 0ff1a67a69..9ba27103ae 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -2231,7 +2231,8 @@ paths: /w/{workspace}/resources/get_value_interpolated/{path}: get: - summary: get resource interpolated (variables and resources are fully unrolled) + summary: + get resource interpolated (variables and resources are fully unrolled) operationId: getResourceValueInterpolated tags: - resource @@ -2919,7 +2920,8 @@ paths: schema: type: string - name: first_parent_hash - description: mask to filter scripts whom first direct parent has exact hash + description: + mask to filter scripts whom first direct parent has exact hash in: query schema: type: string @@ -3114,7 +3116,8 @@ paths: /workers/custom_tags: get: - summary: get all instance custom tags (tags are used to dispatch jobs to + summary: + get all instance custom tags (tags are used to dispatch jobs to different worker groups) operationId: getCustomTags tags: @@ -3165,7 +3168,8 @@ paths: /w/{workspace}/scripts/delete/h/{hash}: post: - summary: delete script by hash (erase content but keep hash, require admin) + summary: + delete script by hash (erase content but keep hash, require admin) operationId: deleteScriptByHash tags: - script @@ -3404,14 +3408,17 @@ paths: type: string format: date-time - name: scheduled_in_secs - description: schedule the script to execute in the number of seconds starting now + description: + schedule the script to execute in the number of seconds starting now in: query schema: type: integer - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/WorkerTag" - $ref: "#/components/parameters/NewJobId" - name: invisible_to_owner - description: make the run invisible to the the script owner (default false) + description: + make the run invisible to the the script owner (default false) in: query schema: type: boolean @@ -3471,6 +3478,7 @@ paths: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/ScriptPath" - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/WorkerTag" - $ref: "#/components/parameters/NewJobId" - $ref: "#/components/parameters/IncludeHeader" - $ref: "#/components/parameters/QueueLimit" @@ -3499,6 +3507,7 @@ paths: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/ScriptPath" - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/WorkerTag" - $ref: "#/components/parameters/NewJobId" - $ref: "#/components/parameters/IncludeHeader" - $ref: "#/components/parameters/QueueLimit" @@ -4442,15 +4451,18 @@ paths: type: string format: date-time - name: scheduled_in_secs - description: schedule the script to execute in the number of seconds starting now + description: + schedule the script to execute in the number of seconds starting now in: query schema: type: integer - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/WorkerTag" - $ref: "#/components/parameters/NewJobId" - $ref: "#/components/parameters/IncludeHeader" - name: invisible_to_owner - description: make the run invisible to the the flow owner (default false) + description: + make the run invisible to the the flow owner (default false) in: query schema: type: boolean @@ -4502,15 +4514,18 @@ paths: type: string format: date-time - name: scheduled_in_secs - description: schedule the script to execute in the number of seconds starting now + description: + schedule the script to execute in the number of seconds starting now in: query schema: type: integer - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/WorkerTag" - $ref: "#/components/parameters/NewJobId" - $ref: "#/components/parameters/IncludeHeader" - name: invisible_to_owner - description: make the run invisible to the the flow owner (default false) + description: + make the run invisible to the the flow owner (default false) in: query schema: type: boolean @@ -4548,15 +4563,18 @@ paths: type: string format: date-time - name: scheduled_in_secs - description: schedule the script to execute in the number of seconds starting now + description: + schedule the script to execute in the number of seconds starting now in: query schema: type: integer - $ref: "#/components/parameters/ParentJob" + - $ref: "#/components/parameters/WorkerTag" - $ref: "#/components/parameters/NewJobId" - $ref: "#/components/parameters/IncludeHeader" - name: invisible_to_owner - description: make the run invisible to the the script owner (default false) + description: + make the run invisible to the the script owner (default false) in: query schema: type: boolean @@ -4587,7 +4605,8 @@ paths: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/IncludeHeader" - name: invisible_to_owner - description: make the run invisible to the the script owner (default false) + description: + make the run invisible to the the script owner (default false) in: query schema: type: boolean @@ -4620,7 +4639,8 @@ paths: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/IncludeHeader" - name: invisible_to_owner - description: make the run invisible to the the script owner (default false) + description: + make the run invisible to the the script owner (default false) in: query schema: type: boolean @@ -4661,6 +4681,7 @@ paths: - $ref: "#/components/parameters/StartedBefore" - $ref: "#/components/parameters/StartedAfter" - $ref: "#/components/parameters/Success" + - $ref: "#/components/parameters/ScheduledForBeforeNow" - $ref: "#/components/parameters/JobKinds" - $ref: "#/components/parameters/Suspended" - $ref: "#/components/parameters/Running" @@ -4797,6 +4818,7 @@ paths: - $ref: "#/components/parameters/StartedAfter" - $ref: "#/components/parameters/CreatedOrStartedBefore" - $ref: "#/components/parameters/Running" + - $ref: "#/components/parameters/ScheduledForBeforeNow" - $ref: "#/components/parameters/CreatedOrStartedAfter" - $ref: "#/components/parameters/JobKinds" - $ref: "#/components/parameters/ArgsFilter" @@ -4835,7 +4857,8 @@ paths: - job responses: "200": - description: the timestamp of the db that can be used to compute the drift + description: + the timestamp of the db that can be used to compute the drift content: application/json: schema: @@ -5099,7 +5122,8 @@ paths: /w/{workspace}/jobs/resume_urls/{id}/{resume_id}: get: - summary: get resume urls given a job_id, resume_id and a nonce to resume a flow + summary: + get resume urls given a job_id, resume_id and a nonce to resume a flow operationId: getResumeUrls tags: - job @@ -5650,7 +5674,8 @@ paths: - $ref: "#/components/parameters/WorkspaceId" - name: only_member_of in: query - description: only list the groups the user is member of (default false) + description: + only list the groups the user is member of (default false) schema: type: boolean responses: @@ -5838,7 +5863,8 @@ paths: - $ref: "#/components/parameters/WorkspaceId" - name: only_member_of in: query - description: only list the folders the user is member of (default false) + description: + only list the folders the user is member of (default false) schema: type: boolean responses: @@ -6667,8 +6693,8 @@ paths: /w/{workspace}/job_helpers/v2/boto3_connection_settings: post: summary: - Converts an S3 resource to the set of arguments necessary to connect - to an S3 bucket using boto3 in Python + Converts an S3 resource to the set of arguments necessary to connect to + an S3 bucket using boto3 in Python operationId: boto3ConnectionSettingsV2 tags: - helpers @@ -6922,7 +6948,8 @@ components: type: integer PerPage: name: per_page - description: number of items to return for a given page (default 30, max 100) + description: + number of items to return for a given page (default 30, max 100) in: query schema: type: integer @@ -6947,6 +6974,12 @@ components: schema: type: string format: uuid + WorkerTag: + name: tag + description: Override the tag to use + in: query + schema: + type: string NewJobId: name: job_id description: @@ -7042,6 +7075,13 @@ components: in: query schema: type: boolean + ScheduledForBeforeNow: + name: scheduled_for_before_now + description: + filter on jobs scheduled_for before now (hence waitinf for a worker) + in: query + schema: + type: boolean Suspended: name: suspended description: filter on suspended jobs @@ -7056,7 +7096,8 @@ components: type: boolean ArgsFilter: name: args - description: filter on jobs containing those args as a json subset (@> in postgres) + description: + filter on jobs containing those args as a json subset (@> in postgres) in: query schema: type: string @@ -7068,7 +7109,8 @@ components: type: string ResultFilter: name: result - description: filter on jobs containing those result as a json subset (@> in postgres) + description: + filter on jobs containing those result as a json subset (@> in postgres) in: query schema: type: string diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 436f457a9d..6a78e25ca1 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -519,6 +519,7 @@ pub struct RunJobQuery { queue_limit: Option, payload: Option, job_id: Option, + tag: Option, } impl RunJobQuery { @@ -558,6 +559,7 @@ pub struct ListQueueQuery { // filter by matching a subset of the args using base64 encoded json subset pub args: Option, pub tag: Option, + pub scheduled_for_before_now: Option, } fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> SqlBuilder { @@ -620,7 +622,7 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq if *s { sqlb.and_where_gt("suspend", 0); } else { - sqlb.and_where_eq("suspend", 0); + sqlb.and_where_is_null("suspend_until"); } } @@ -635,6 +637,10 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq sqlb.and_where("args @> ?".bind(&args.replace("'", "''"))); } + if lq.scheduled_for_before_now.is_some_and(|x| x) { + sqlb.and_where_le("scheduled_for", "now()"); + } + sqlb } @@ -704,7 +710,7 @@ async fn cancel_all( require_admin(authed.is_admin, &authed.username)?; let mut jobs = sqlx::query!( - "UPDATE queue SET canceled = true, canceled_by = $2, scheduled_for = now(), suspend = 0 WHERE workspace_id = $1 AND schedule_path IS NULL RETURNING id, running", + "UPDATE queue SET canceled = true, canceled_by = $2, scheduled_for = now(), suspend = 0 WHERE scheduled_for < now() AND workspace_id = $1 AND schedule_path IS NULL RETURNING id, running", w_id, authed.username ) @@ -863,6 +869,7 @@ async fn list_jobs( args: lq.args, tag: lq.tag, schedule_path: lq.schedule_path, + scheduled_for_before_now: lq.scheduled_for_before_now, }, &[ "'QueuedJob' as typ", @@ -1743,6 +1750,8 @@ pub async fn run_flow_by_path( .map(|x| (x.tag, x.dedicated_worker)) .unwrap_or_else(|| (None, None)); + let tag = run_query.tag.clone().or(tag); + check_tag_available_for_workspace(&w_id, &tag).await?; let scheduled_for = run_query.get_scheduled_for(&db).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); @@ -1870,6 +1879,7 @@ pub async fn run_job_by_path( script_path_to_payload(script_path, &db, &w_id).await?; let scheduled_for = run_query.get_scheduled_for(&db).await?; + let tag = run_query.tag.clone().or(tag); check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); @@ -2142,6 +2152,8 @@ pub async fn run_wait_result_job_by_path_get( let (job_payload, tag, delete_after_use, timeout) = script_path_to_payload(script_path, &db, &w_id).await?; + + let tag = run_query.tag.clone().or(tag); check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); @@ -2260,6 +2272,7 @@ async fn run_wait_result_script_by_path_internal( let (job_payload, tag, delete_after_use, timeout) = script_path_to_payload(script_path, &db, &w_id).await?; + let tag = run_query.tag.clone().or(tag); check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); @@ -2325,6 +2338,7 @@ pub async fn run_wait_result_script_by_hash( ) = get_path_tag_limits_cache_for_hash(&db, &w_id, hash).await?; check_scopes(&authed, || format!("run:script/{path}"))?; + let tag = run_query.tag.clone().or(tag); check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); @@ -2415,6 +2429,7 @@ async fn run_wait_result_flow_by_path_internal( .map(|x| (x.tag, x.dedicated_worker, x.early_return)) .unwrap_or_else(|| (None, None, None)); + let tag = run_query.tag.clone().or(tag); check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); @@ -2466,7 +2481,8 @@ async fn run_preview_job( )); } let scheduled_for = run_query.get_scheduled_for(&db).await?; - check_tag_available_for_workspace(&w_id, &preview.tag).await?; + let tag = run_query.tag.clone().or(preview.tag.clone()); + check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); let (uuid, tx) = push( @@ -2500,7 +2516,7 @@ async fn run_preview_job( false, None, true, - preview.tag, + tag, None, None, None, @@ -2685,7 +2701,8 @@ async fn run_preview_flow_job( )); } let scheduled_for = run_query.get_scheduled_for(&db).await?; - check_tag_available_for_workspace(&w_id, &raw_flow.tag).await?; + let tag = run_query.tag.clone().or(raw_flow.tag.clone()); + check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); let (uuid, tx) = push( @@ -2710,7 +2727,7 @@ async fn run_preview_flow_job( false, None, true, - raw_flow.tag, + tag, None, None, None, @@ -2750,6 +2767,7 @@ pub async fn run_job_by_hash( check_scopes(&authed, || format!("run:script/{path}"))?; let scheduled_for = run_query.get_scheduled_for(&db).await?; + let tag = run_query.tag.clone().or(tag); check_tag_available_for_workspace(&w_id, &tag).await?; let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into(), rsmq); @@ -2954,6 +2972,7 @@ pub struct ListCompletedQuery { // filter by matching a subset of the result using base64 encoded json subset pub result: Option, pub tag: Option, + pub scheduled_for_before_now: Option, } async fn list_completed_jobs( diff --git a/frontend/src/lib/components/RunForm.svelte b/frontend/src/lib/components/RunForm.svelte index 631c39e3fe..b46c94658e 100644 --- a/frontend/src/lib/components/RunForm.svelte +++ b/frontend/src/lib/components/RunForm.svelte @@ -1,29 +1,20 @@ + +
+
+ {#if SCRIPT_VIEW_SHOW_SCHEDULE_RUN_LATER} +
+ + +
+
+
+ +
+
+ {/if} +
+ {#if !$userStore?.operator} + {#if $workerTags && $workerTags?.length > 0} +
+ +
+ {/if} + {/if} + + {#if runnable?.path?.startsWith(`u/${$userStore?.username}`) != true && (runnable?.path?.split('/')?.length ?? 0) > 2} +
+ + By default, runs are visible to the owner(s) of the script or flow being triggered +
+ {/if} +
diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 63a4d6d5b0..4ba4e7a70c 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -578,7 +578,7 @@ -
+
{#if $flowStore.tag == undefined}