diff --git a/backend/.sqlx/query-f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45.json b/backend/.sqlx/query-00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f.json similarity index 69% rename from backend/.sqlx/query-f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45.json rename to backend/.sqlx/query-00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f.json index 3adc1f8cd5..a3ec5fbabe 100644 --- a/backend/.sqlx/query-f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45.json +++ b/backend/.sqlx/query-00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval \n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ", + "query": "\n DELETE\n FROM parallel_monitor_lock\n WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval\n RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q\n WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL\n ) AS workspace_id\n ", "describe": { "columns": [ { @@ -36,5 +36,5 @@ null ] }, - "hash": "f822df86bf511fe0d3044b69525a6ff2524167877929886adca9b0fa3d3fee45" + "hash": "00c4a602aa6a50f2f922851ce63b5216e915c7649698687a00d47da55c70349f" } diff --git a/backend/.sqlx/query-ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1.json b/backend/.sqlx/query-3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8.json similarity index 68% rename from backend/.sqlx/query-ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1.json rename to backend/.sqlx/query-3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8.json index 6c92e12f6a..321831d724 100644 --- a/backend/.sqlx/query-ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1.json +++ b/backend/.sqlx/query-3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval \n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker", + "query": "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval\n AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker", "describe": { "columns": [ { @@ -22,5 +22,5 @@ null ] }, - "hash": "ebd25047d14bec8457465ba6ed9b6b15b8f2303157b4c6e80ac9e3d84a57d8b1" + "hash": "3e261911cc4c5289da49865f54350613f9e651540a279bd7d75e5e7d79f676a8" } diff --git a/backend/.sqlx/query-ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json similarity index 50% rename from backend/.sqlx/query-ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927.json rename to backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index c2dfed73a2..713ccb9dd3 100644 --- a/backend/.sqlx/query-ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) \n SELECT worker_ids.worker FROM worker_ids \n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker \n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval", + "query": "WITH worker_ids AS (SELECT unnest($1::text[]) as worker)\n SELECT worker_ids.worker FROM worker_ids\n LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker\n WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval", "describe": { "columns": [ { @@ -18,5 +18,5 @@ null ] }, - "hash": "ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927" + "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" } diff --git a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json index 54e94cfb8f..99269c9851 100644 --- a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json +++ b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json @@ -18,8 +18,8 @@ "Left": [] }, "nullable": [ - false, - true + true, + false ] }, "hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76" diff --git a/backend/.sqlx/query-abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5.json b/backend/.sqlx/query-daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134.json similarity index 76% rename from backend/.sqlx/query-abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5.json rename to backend/.sqlx/query-daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134.json index 55039055c8..2e2a9ba027 100644 --- a/backend/.sqlx/query-abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5.json +++ b/backend/.sqlx/query-daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE \n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update", + "query": "WITH to_update AS (\n SELECT q.id, q.workspace_id, r.ping, COALESCE(zjc.counter, 0) as counter\n FROM v2_job_queue q\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_runtime r ON r.id = j.id\n LEFT JOIN zombie_job_counter zjc ON zjc.job_id = q.id\n WHERE ping < now() - ($1 || ' seconds')::interval\n AND running = true\n AND kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow')\n AND same_worker = false\n AND (zjc.counter IS NULL OR zjc.counter <= $2)\n FOR UPDATE of q SKIP LOCKED\n ),\n zombie_jobs AS (\n UPDATE v2_job_queue q\n SET running = false, started_at = null\n FROM to_update tu\n WHERE q.id = tu.id AND (tu.counter IS NULL OR tu.counter < $2)\n RETURNING q.id, q.workspace_id, ping, tu.counter\n ),\n update_ping AS (\n UPDATE v2_job_runtime r\n SET ping = null\n FROM zombie_jobs zj\n WHERE r.id = zj.id\n ),\n increment_counter AS (\n INSERT INTO zombie_job_counter (job_id, counter)\n SELECT id, 1 FROM to_update WHERE counter < $2\n ON CONFLICT (job_id) DO UPDATE\n SET counter = zombie_job_counter.counter + 1\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id AS \"id!\", workspace_id AS \"workspace_id!\", ping, counter + 1 AS counter FROM to_update", "describe": { "columns": [ { @@ -37,5 +37,5 @@ null ] }, - "hash": "abfc6b530565f125bd0b8ac99cd67fd22f14f8fce89e59890ba21e87fe323af5" + "hash": "daf9674838fb3e3653a356c7434c719616a614d77e726433737e5f5d9bd60134" } diff --git a/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json b/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json index 649ab85650..de2e819af2 100644 --- a/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json +++ b/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json @@ -41,11 +41,11 @@ ] }, "nullable": [ - false, - false, - false, - false, - false, + true, + true, + true, + true, + true, true ] }, diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 8dae3f8c5e..e8bb9be822 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -135,7 +135,7 @@ pub async fn initial_load( } } - + if let Err(e) = load_metrics_enabled(conn).await { tracing::error!("Error loading expose metrics: {e:#}"); } @@ -176,7 +176,7 @@ pub async fn initial_load( } } } - + if let Err(e) = reload_hub_base_url_setting(conn, server_mode).await { tracing::error!("Error reloading hub base url: {:?}", e) @@ -612,7 +612,7 @@ async fn send_log_file_to_object_store( let (ok_lines, err_lines) = read_log_counters(ts_str); if let Some(db) = conn.as_sql() { - if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)", + if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)", hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT) .execute(db) .await { @@ -1652,7 +1652,7 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker increment_counter AS ( INSERT INTO zombie_job_counter (job_id, counter) SELECT id, 1 FROM to_update WHERE counter < $2 - ON CONFLICT (job_id) DO UPDATE + ON CONFLICT (job_id) DO UPDATE SET counter = zombie_job_counter.counter + 1 ), update_concurrency AS ( @@ -1744,7 +1744,7 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker let same_worker_timeout_jobs = { let long_same_worker_jobs = sqlx::query!( - "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval + "SELECT worker, array_agg(v2_job_queue.id) as ids FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id LEFT JOIN v2_job_runtime ON v2_job_queue.id = v2_job_runtime.id WHERE v2_job_queue.created_at < now() - ('60 seconds')::interval AND running = true AND ping IS NULL AND same_worker = true AND worker IS NOT NULL GROUP BY worker", ) .fetch_all(db) @@ -1758,9 +1758,9 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker .collect::>(); let long_dead_workers: std::collections::HashSet = sqlx::query_scalar!( - "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) - SELECT worker_ids.worker FROM worker_ids - LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker + "WITH worker_ids AS (SELECT unnest($1::text[]) as worker) + SELECT worker_ids.worker FROM worker_ids + LEFT JOIN worker_ping ON worker_ids.worker = worker_ping.worker WHERE worker_ping.worker IS NULL OR worker_ping.ping_at < now() - ('60 seconds')::interval", &worker_ids[..] ) @@ -1804,7 +1804,7 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker let non_restartable_jobs = if *RESTART_ZOMBIE_JOBS { vec![] } else { - sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval + sqlx::query_as::<_, QueuedJob>("SELECT * FROM v2_as_queue WHERE last_ping < now() - ($1 || ' seconds')::interval AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false") .bind(ZOMBIE_JOB_TIMEOUT.as_str()) .fetch_all(db) @@ -2001,7 +2001,7 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> { } ); report_critical_error(reason.clone(), db.clone(), Some(&flow.workspace_id), None).await; - cancel_zombie_flow_job(db, flow.id, &flow.workspace_id, + cancel_zombie_flow_job(db, flow.id, &flow.workspace_id, format!(r#"{reason} This would happen if a worker was interrupted, killed or crashed while doing a state transition at the end of a job which is always an unexpected behavior that should never happen. Please check your worker logs for more details and feel free to report it to the Windmill team on our Discord or support@windmill.dev (response for non EE customers will be best effort) with as much context as possible, ideally: @@ -2018,7 +2018,7 @@ Please check your worker logs for more details and feel free to report it to the r#" DELETE FROM parallel_monitor_lock - WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval + WHERE last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval RETURNING parent_flow_id, job_id, last_ping, (SELECT workspace_id FROM v2_job_queue q WHERE q.id = parent_flow_id AND q.running = true AND q.canceled_by IS NULL ) AS workspace_id @@ -2126,7 +2126,7 @@ pub async fn reload_critical_error_channels_setting(conn: &DB) -> error::Result< v } else { tracing::error!( - "Could not parse critical_error_emails setting as an array of channels, found: {:#?}", + "Could not parse critical_error_channels setting as an array of channels, found: {:#?}", &q ); vec![] diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 0dae5e223a..772430b468 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -9757,7 +9757,7 @@ paths: type: array items: type: string - + /w/{workspace}/gcp_triggers/subscriptions/list/{path}: post: summary: list all subscription of a give topic from google cloud service @@ -14132,7 +14132,7 @@ components: csharp, nu, java - # for related places search: ADD_NEW_LANG + # for related places search: ADD_NEW_LANG ] Preview: @@ -15083,7 +15083,7 @@ components: - delivery_type - subscription_mode - + SubscriptionMode: type: string enum: @@ -15147,7 +15147,7 @@ components: enum: - oidc - credentials - + SqsTrigger: allOf: - $ref: "#/components/schemas/TriggerExtraProperty" @@ -16704,4 +16704,29 @@ components: presigned: type: string required: - - s3 \ No newline at end of file + - s3 + + TeamsChannel: + type: object + required: + - team_id + - team_name + - channel_id + - channel_name + properties: + team_id: + type: string + description: Microsoft Teams team ID + minLength: 1 + team_name: + type: string + description: Microsoft Teams team name + minLength: 1 + channel_id: + type: string + description: Microsoft Teams channel ID + minLength: 1 + channel_name: + type: string + description: Microsoft Teams channel name + minLength: 1 \ No newline at end of file diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index 32e48d4f2f..78e28f02bb 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -2,7 +2,7 @@ import { scimSamlSetting, settings, settingsKeys, type SettingStorage } from './instanceSettings' import { Button, Tab, TabContent, Tabs } from '$lib/components/common' import { SettingService, SettingsService } from '$lib/gen' - import type { TeamInfo } from '$lib/gen/types.gen' + import type { TeamInfo, TeamsChannel } from '$lib/gen/types.gen' import { sendUserToast } from '$lib/toast' import { deepEqual } from 'fast-equals' @@ -132,6 +132,14 @@ setupSnowflakeUrls() } + // Remove empty or invalid teams_channel entries + $values.critical_error_channels = $values.critical_error_channels.filter((entry) => { + if (entry && typeof entry == 'object' && 'teams_channel' in entry) { + return isValidTeamsChannel(entry.teams_channel) + } + return true + }) + let shouldReloadPage = false if ($values) { const allSettings = [...Object.values(settings), scimSamlSetting].flatMap((x) => @@ -214,6 +222,21 @@ await SettingService.sendStats() sendUserToast('Usage sent') } + + function isValidTeamsChannel(value: any): value is TeamsChannel { + return ( + typeof value === 'object' && + value !== null && + typeof value.team_id === 'string' && + value.team_id.trim() !== '' && + typeof value.team_name === 'string' && + value.team_name.trim() !== '' && + typeof value.channel_id === 'string' && + value.channel_id.trim() !== '' && + typeof value.channel_name === 'string' && + value.channel_name.trim() !== '' + ) + }