diff --git a/backend/.sqlx/query-1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f.json b/backend/.sqlx/query-1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f.json new file mode 100644 index 0000000000..767f5cba57 --- /dev/null +++ b/backend/.sqlx/query-1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "1182fe055306d7ea435d76b74d781e066915c8397e6bbc9e408ff3dda9fec27f" +} diff --git a/backend/.sqlx/query-5cd89ab614d3cac80fb81627267ee85b191263989b0c78b2bfce77e796e96825.json b/backend/.sqlx/query-251f18ccdd89255023258a33285757801cabe4e86385e2dea10b666d1c6f2b61.json similarity index 84% rename from backend/.sqlx/query-5cd89ab614d3cac80fb81627267ee85b191263989b0c78b2bfce77e796e96825.json rename to backend/.sqlx/query-251f18ccdd89255023258a33285757801cabe4e86385e2dea10b666d1c6f2b61.json index eabf671894..d4e63d61a5 100644 --- a/backend/.sqlx/query-5cd89ab614d3cac80fb81627267ee85b191263989b0c78b2bfce77e796e96825.json +++ b/backend/.sqlx/query-251f18ccdd89255023258a33285757801cabe4e86385e2dea10b666d1c6f2b61.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND archived = false)", + "query": "select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND\n deleted = false AND archived = false)", "describe": { "columns": [ { @@ -75,5 +75,5 @@ true ] }, - "hash": "5cd89ab614d3cac80fb81627267ee85b191263989b0c78b2bfce77e796e96825" + "hash": "251f18ccdd89255023258a33285757801cabe4e86385e2dea10b666d1c6f2b61" } diff --git a/backend/.sqlx/query-65835f2e5ad38f7cc6b147dadfef6f580f15bca96d9746c9359e98ca793f8f1f.json b/backend/.sqlx/query-27ab808d7e05235180449df043ef57c6fbe7d3e261e5a52313d01782739c0e77.json similarity index 91% rename from backend/.sqlx/query-65835f2e5ad38f7cc6b147dadfef6f580f15bca96d9746c9359e98ca793f8f1f.json rename to backend/.sqlx/query-27ab808d7e05235180449df043ef57c6fbe7d3e261e5a52313d01782739c0e77.json index c52efca4c0..0c2297dcb1 100644 --- a/backend/.sqlx/query-65835f2e5ad38f7cc6b147dadfef6f580f15bca96d9746c9359e98ca793f8f1f.json +++ b/backend/.sqlx/query-27ab808d7e05235180449df043ef57c6fbe7d3e261e5a52313d01782739c0e77.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", + "query": "select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", "describe": { "columns": [ { @@ -75,5 +75,5 @@ true ] }, - "hash": "65835f2e5ad38f7cc6b147dadfef6f580f15bca96d9746c9359e98ca793f8f1f" + "hash": "27ab808d7e05235180449df043ef57c6fbe7d3e261e5a52313d01782739c0e77" } diff --git a/backend/.sqlx/query-899b48109ce20a8fbcf9c8e9339713dcdf4173564d388f1927dea06653c718d5.json b/backend/.sqlx/query-899b48109ce20a8fbcf9c8e9339713dcdf4173564d388f1927dea06653c718d5.json new file mode 100644 index 0000000000..f552871fff --- /dev/null +++ b/backend/.sqlx/query-899b48109ce20a8fbcf9c8e9339713dcdf4173564d388f1927dea06653c718d5.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE flow SET ws_error_handler_muted = $3 WHERE path = $1 AND workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "899b48109ce20a8fbcf9c8e9339713dcdf4173564d388f1927dea06653c718d5" +} diff --git a/backend/.sqlx/query-123c0608e229c29187009b7961355ddd99c4ad1f46b876dd86e372b84d806ecd.json b/backend/.sqlx/query-901c430cba96e5cab1a186207ea708f3d34a04c8ab03b385f8f2e9c662d3e724.json similarity index 91% rename from backend/.sqlx/query-123c0608e229c29187009b7961355ddd99c4ad1f46b876dd86e372b84d806ecd.json rename to backend/.sqlx/query-901c430cba96e5cab1a186207ea708f3d34a04c8ab03b385f8f2e9c662d3e724.json index 7718e05ccf..717d959538 100644 --- a/backend/.sqlx/query-123c0608e229c29187009b7961355ddd99c4ad1f46b876dd86e372b84d806ecd.json +++ b/backend/.sqlx/query-901c430cba96e5cab1a186207ea708f3d34a04c8ab03b385f8f2e9c662d3e724.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", + "query": "select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", "describe": { "columns": [ { @@ -69,5 +69,5 @@ true ] }, - "hash": "123c0608e229c29187009b7961355ddd99c4ad1f46b876dd86e372b84d806ecd" + "hash": "901c430cba96e5cab1a186207ea708f3d34a04c8ab03b385f8f2e9c662d3e724" } diff --git a/backend/.sqlx/query-97b6bbcd655001a99f0cd9b127dcddf6d45ad87308c1294e52f1ae65529271d1.json b/backend/.sqlx/query-97b6bbcd655001a99f0cd9b127dcddf6d45ad87308c1294e52f1ae65529271d1.json new file mode 100644 index 0000000000..45430a1fa7 --- /dev/null +++ b/backend/.sqlx/query-97b6bbcd655001a99f0cd9b127dcddf6d45ad87308c1294e52f1ae65529271d1.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT ws_error_handler_muted FROM script WHERE workspace_id = $1 AND hash = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "ws_error_handler_muted", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "97b6bbcd655001a99f0cd9b127dcddf6d45ad87308c1294e52f1ae65529271d1" +} diff --git a/backend/.sqlx/query-9f545c9c03d550ba0468dd7b4be39b76a7754a53f4508dce05e3f36300db483b.json b/backend/.sqlx/query-9f545c9c03d550ba0468dd7b4be39b76a7754a53f4508dce05e3f36300db483b.json new file mode 100644 index 0000000000..c174cee4bf --- /dev/null +++ b/backend/.sqlx/query-9f545c9c03d550ba0468dd7b4be39b76a7754a53f4508dce05e3f36300db483b.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT ws_error_handler_muted FROM flow WHERE workspace_id = $1 AND path = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "ws_error_handler_muted", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "9f545c9c03d550ba0468dd7b4be39b76a7754a53f4508dce05e3f36300db483b" +} diff --git a/backend/.sqlx/query-438b5b5d29b05846c2e074cad2404e797527841cd97dba80c271cbefafae65cc.json b/backend/.sqlx/query-b5646d44502b289e5d92f53f74bf0a52b8bf76775d87a1f698ed2a3c3446c86e.json similarity index 85% rename from backend/.sqlx/query-438b5b5d29b05846c2e074cad2404e797527841cd97dba80c271cbefafae65cc.json rename to backend/.sqlx/query-b5646d44502b289e5d92f53f74bf0a52b8bf76775d87a1f698ed2a3c3446c86e.json index 1166260449..00bc0a43a1 100644 --- a/backend/.sqlx/query-438b5b5d29b05846c2e074cad2404e797527841cd97dba80c271cbefafae65cc.json +++ b/backend/.sqlx/query-b5646d44502b289e5d92f53f74bf0a52b8bf76775d87a1f698ed2a3c3446c86e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)", + "query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)", "describe": { "columns": [], "parameters": { @@ -58,10 +58,11 @@ "Int4", "Int4", "Int4", + "Bool", "Bool" ] }, "nullable": [] }, - "hash": "438b5b5d29b05846c2e074cad2404e797527841cd97dba80c271cbefafae65cc" + "hash": "b5646d44502b289e5d92f53f74bf0a52b8bf76775d87a1f698ed2a3c3446c86e" } diff --git a/backend/.sqlx/query-f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515.json b/backend/.sqlx/query-f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515.json new file mode 100644 index 0000000000..13526abb60 --- /dev/null +++ b/backend/.sqlx/query-f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT error_handler FROM workspace_settings WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "error_handler", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "f8b34e09453d51d3df5be20652938a890ee353fec64ee73c312a3352da7f7515" +} diff --git a/backend/migrations/20231017172035_disable_error_handler_columns.down.sql b/backend/migrations/20231017172035_disable_error_handler_columns.down.sql new file mode 100644 index 0000000000..703b51628f --- /dev/null +++ b/backend/migrations/20231017172035_disable_error_handler_columns.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +ALTER TABLE script DROP COLUMN ws_error_handler_muted; +ALTER TABLE flow DROP COLUMN ws_error_handler_muted; \ No newline at end of file diff --git a/backend/migrations/20231017172035_disable_error_handler_columns.up.sql b/backend/migrations/20231017172035_disable_error_handler_columns.up.sql new file mode 100644 index 0000000000..26217af310 --- /dev/null +++ b/backend/migrations/20231017172035_disable_error_handler_columns.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TABLE script ADD COLUMN ws_error_handler_muted BOOLEAN NOT NULL DEFAULT false; +ALTER TABLE flow ADD COLUMN ws_error_handler_muted BOOLEAN NOT NULL DEFAULT false; \ No newline at end of file diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index e42d85bd3f..8be999dadb 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -2564,6 +2564,7 @@ async fn test_flow_lock_all(db: Pool) { open_flow: flow, path: "g/all/flow_lock_all".to_owned(), tag: None, + ws_error_handler_muted: None, }, draft_only: None, }, @@ -2917,6 +2918,7 @@ async fn run_deployed_relative_imports(db: &Pool, script_content: Stri summary: "".to_string(), tag: None, schema: std::collections::HashMap::new(), + ws_error_handler_muted: Some(false), }, ).await.unwrap(); diff --git a/backend/windmill-api/openapi-deref.yaml b/backend/windmill-api/openapi-deref.yaml index 824b865d8b..9c311142f2 100644 --- a/backend/windmill-api/openapi-deref.yaml +++ b/backend/windmill-api/openapi-deref.yaml @@ -3931,6 +3931,8 @@ paths: type: number dedicated_worker: type: boolean + ws_error_handler_muted: + type: boolean required: &ref_29 - hash - path @@ -4115,6 +4117,8 @@ paths: type: number dedicated_worker: type: boolean + ws_error_handler_muted: + type: boolean required: &ref_32 - path - summary @@ -4128,6 +4132,38 @@ paths: text/plain: schema: type: string + /w/{workspace}/scripts/toggle_workspace_error_handler/p/{path}: + post: + summary: Toggle ON and OFF the workspace error handler for a given script + operationId: toggleWorkspaceErrorHandlerForScript + tags: + - script + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_27 + requestBody: + description: Workspace error handler enabled + required: true + content: + application/json: + schema: + type: object + properties: + muted: + type: boolean + responses: + '200': + description: error handler toggled + content: + text/plain: + schema: + type: string /workers/custom_tags: get: summary: >- @@ -4955,6 +4991,8 @@ paths: type: boolean tag: type: string + ws_error_handler_muted: + type: boolean required: &ref_141 - path - edited_by @@ -4989,6 +5027,38 @@ paths: application/json: schema: allOf: *ref_41 + /w/{workspace}/flows/toggle_workspace_error_handler/{path}: + post: + summary: Toggle ON and OFF the workspace error handler for a given flow + operationId: toggleWorkspaceErrorHandlerForFlow + tags: + - flow + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_27 + requestBody: + description: Workspace error handler enabled + required: true + content: + application/json: + schema: + type: object + properties: + muted: + type: boolean + responses: + '200': + description: error handler toggled + content: + text/plain: + schema: + type: string /w/{workspace}/flows/get/draft/{path}: get: summary: get flow by path with draft @@ -5066,6 +5136,8 @@ paths: type: string tag: type: string + ws_error_handler_muted: + type: boolean required: - path - type: object diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 671d05506f..86a8ecf30f 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -2898,6 +2898,33 @@ paths: schema: type: string + /w/{workspace}/scripts/toggle_workspace_error_handler/p/{path}: + post: + summary: Toggle ON and OFF the workspace error handler for a given script + operationId: toggleWorkspaceErrorHandlerForScript + tags: + - script + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + requestBody: + description: Workspace error handler enabled + required: true + content: + application/json: + schema: + type: object + properties: + muted: + type: boolean + responses: + "200": + description: error handler toggled + content: + text/plain: + schema: + type: string + /workers/custom_tags: get: summary: get all instance custom tags (tags are used to dispatch jobs to @@ -3446,6 +3473,33 @@ paths: schema: $ref: "#/components/schemas/Flow" + /w/{workspace}/flows/toggle_workspace_error_handler/{path}: + post: + summary: Toggle ON and OFF the workspace error handler for a given flow + operationId: toggleWorkspaceErrorHandlerForFlow + tags: + - flow + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + requestBody: + description: Workspace error handler enabled + required: true + content: + application/json: + schema: + type: object + properties: + muted: + type: boolean + responses: + "200": + description: error handler toggled + content: + text/plain: + schema: + type: string + /w/{workspace}/flows/get/draft/{path}: get: summary: get flow by path with draft @@ -6390,6 +6444,8 @@ components: type: number dedicated_worker: type: boolean + ws_error_handler_muted: + type: boolean required: - hash - path @@ -6467,6 +6523,8 @@ components: type: number dedicated_worker: type: boolean + ws_error_handler_muted: + type: boolean required: - path - summary @@ -7712,6 +7770,8 @@ components: type: boolean tag: type: string + ws_error_handler_muted: + type: boolean required: - path - edited_by @@ -7728,6 +7788,8 @@ components: type: string tag: type: string + ws_error_handler_muted: + type: boolean required: - path diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index 2fc65b811a..dcbcda5541 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -53,6 +53,10 @@ pub fn workspaced_service() -> Router { .route("/get/draft/*path", get(get_flow_by_path_w_draft)) .route("/exists/*path", get(exists_flow_by_path)) .route("/list_paths", get(list_paths)) + .route( + "/toggle_workspace_error_handler/*path", + post(toggle_workspace_error_handler), + ) } pub fn global_service() -> Router { @@ -114,7 +118,8 @@ async fn list_flows( "extra_perms", "favorite.path IS NOT NULL as starred", "draft.path IS NOT NULL as has_draft", - "draft_only" + "draft_only", + "ws_error_handler_muted" ]) .left() .join("favorite") @@ -209,6 +214,56 @@ pub async fn get_hub_flow_by_id( Ok(Json(value)) } +#[derive(Deserialize)] +pub struct ToggleWorkspaceErrorHandler { + pub muted: Option, +} +async fn toggle_workspace_error_handler( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + Json(req): Json, +) -> Result { + #[cfg(not(feature = "enterprise"))] + if true { + return Err(Error::BadRequest( + "Muting the error handler for certain flow is only available in enterprise version" + .to_string(), + )); + } + + let mut tx = user_db.begin(&authed).await?; + + let error_handler_maybe: Option = sqlx::query_scalar!( + "SELECT error_handler FROM workspace_settings WHERE workspace_id = $1", + w_id + ) + .fetch_optional(&mut *tx) + .await? + .unwrap_or(None); + + return match error_handler_maybe { + Some(_) => { + sqlx::query_scalar!( + "UPDATE flow SET ws_error_handler_muted = $3 WHERE path = $1 AND workspace_id = $2", + path.to_path(), + w_id, + req.muted, + ) + .execute(&mut *tx) + .await?; + tx.commit().await?; + Ok("".to_string()) + } + None => { + tx.commit().await?; + Err(Error::ExecutionErr( + "Workspace error handler needs to be defined".to_string(), + )) + } + }; +} + async fn check_path_conflict<'c>( tx: &mut Transaction<'c, Postgres>, w_id: &str, @@ -237,6 +292,19 @@ async fn create_flow( Path(w_id): Path, Json(nf): Json, ) -> Result<(StatusCode, String)> { + #[cfg(not(feature = "enterprise"))] + if nf + .value + .get("ws_error_handler_muted") + .map(|val| val.as_bool().unwrap_or(false)) + .is_some_and(|val| val) + { + return Err(Error::BadRequest( + "Muting the error handler for certain flow is only available in enterprise version" + .to_string(), + )); + } + // cron::Schedule::from_str(&ns.schedule).map_err(|e| error::Error::BadRequest(e.to_string()))?; let authed = maybe_refresh_folders(&nf.path, &w_id, authed, &db).await; @@ -372,6 +440,19 @@ async fn update_flow( Path((w_id, flow_path)): Path<(String, StripPath)>, Json(nf): Json, ) -> Result { + #[cfg(not(feature = "enterprise"))] + if nf + .value + .get("ws_error_handler_muted") + .map(|val| val.as_bool().unwrap_or(false)) + .is_some_and(|val| val) + { + return Err(Error::BadRequest( + "Muting the error handler for certain flow is only available in enterprise version" + .to_string(), + )); + } + let flow_path = flow_path.to_path(); let authed = maybe_refresh_folders(&flow_path, &w_id, authed, &db).await; @@ -554,6 +635,7 @@ pub struct FlowWDraft { #[serde(skip_serializing_if = "Option::is_none")] pub draft_only: Option, pub tag: Option, + pub ws_error_handler_muted: Option, } async fn get_flow_by_path_w_draft( @@ -565,7 +647,7 @@ async fn get_flow_by_path_w_draft( let mut tx = user_db.begin(&authed).await?; let flow_o = sqlx::query_as::<_, FlowWDraft>( - "SELECT flow.path, flow.summary, flow,description, flow.schema, flow.value, flow.extra_perms, flow.draft_only, draft.value as draft, flow.tag FROM flow + "SELECT flow.path, flow.summary, flow,description, flow.schema, flow.value, flow.extra_perms, flow.draft_only, flow.ws_error_handler_muted, draft.value as draft, flow.tag FROM flow LEFT JOIN draft ON flow.path = draft.path AND draft.workspace_id = $2 AND draft.typ = 'flow' WHERE flow.path = $1 AND flow.workspace_id = $2", @@ -794,6 +876,7 @@ mod tests { concurrency_time_window_s: None, skip_expr: None, cache_ttl: None, + ws_error_handler_muted: None, }; let expect = serde_json::json!({ "modules": [ diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 5dd961022a..e37463255e 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -316,7 +316,7 @@ pub async fn get_path_tag_limits_cache_for_hash( Option, )> { let script = sqlx::query!( - "select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", + "select path, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", hash, w_id ) diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index d7519602e6..01e8cd1212 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -77,6 +77,7 @@ pub struct ScriptWDraft { pub cache_ttl: Option, #[serde(skip_serializing_if = "Option::is_none")] pub dedicated_worker: Option, + pub ws_error_handler_muted: Option, } pub fn global_service() -> Router { @@ -113,6 +114,10 @@ pub fn workspaced_service() -> Router { .route("/raw/h/:hash", get(raw_script_by_hash)) .route("/deployment_status/h/:hash", get(get_deployment_status)) .route("/list_paths", get(list_paths)) + .route( + "/toggle_workspace_error_handler/p/*path", + post(toggle_workspace_error_handler), + ) } #[derive(Serialize, FromRow)] @@ -168,7 +173,8 @@ async fn list_scripts( "favorite.path IS NOT NULL as starred", "tag", "draft.path IS NOT NULL as has_draft", - "draft_only" + "draft_only", + "ws_error_handler_muted" ]) .left() .join("favorite") @@ -304,6 +310,14 @@ async fn create_script( Path(w_id): Path, Json(ns): Json, ) -> Result<(StatusCode, String)> { + #[cfg(not(feature = "enterprise"))] + if ns.ws_error_handler_muted.is_some_and(|val| val) { + return Err(Error::BadRequest( + "Muting the error handler for certain script is only available in enterprise version" + .to_string(), + )); + } + let hash = ScriptHash(hash_script(&ns)); let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await; let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into(); @@ -456,8 +470,8 @@ async fn create_script( sqlx::query!( "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \ content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ - draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker) \ - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)", + draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted) \ + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)", &w_id, &hash.0, ns.path, @@ -478,7 +492,8 @@ async fn create_script( ns.concurrent_limit, ns.concurrency_time_window_s, ns.cache_ttl, - ns.dedicated_worker + ns.dedicated_worker, + ns.ws_error_handler_muted.unwrap_or(false), ) .execute(&mut tx) .await?; @@ -675,7 +690,7 @@ async fn get_script_by_path_w_draft( let mut tx = user_db.begin(&authed).await?; let script_o = sqlx::query_as::<_, ScriptWDraft>( - "SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, draft.value as draft, dedicated_worker FROM script LEFT JOIN draft ON + "SELECT hash, script.path, summary, description, content, language, kind, tag, schema, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, ws_error_handler_muted, draft.value as draft, dedicated_worker FROM script LEFT JOIN draft ON script.path = draft.path AND script.workspace_id = draft.workspace_id AND draft.typ = 'script' WHERE script.path = $1 AND script.workspace_id = $2 \ AND script.created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \ @@ -709,6 +724,56 @@ async fn list_paths( Ok(Json(scripts)) } +#[derive(Deserialize)] +pub struct ToggleWorkspaceErrorHandler { + pub muted: Option, +} +async fn toggle_workspace_error_handler( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + Json(req): Json, +) -> Result { + #[cfg(not(feature = "enterprise"))] + if true { + return Err(Error::BadRequest( + "Muting the error handler for certain script is only available in enterprise version" + .to_string(), + )); + } + + let mut tx = user_db.begin(&authed).await?; + + let error_handler_maybe: Option = sqlx::query_scalar!( + "SELECT error_handler FROM workspace_settings WHERE workspace_id = $1", + w_id + ) + .fetch_optional(&mut *tx) + .await? + .unwrap_or(None); + + match error_handler_maybe { + Some(_) => { + sqlx::query_scalar!( + "UPDATE script SET ws_error_handler_muted = $3 WHERE workspace_id = $2 AND path = $1 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2)", + path.to_path(), + w_id, + req.muted, + ) + .execute(&mut *tx) + .await?; + tx.commit().await?; + Ok("".to_string()) + } + None => { + tx.commit().await?; + Err(Error::ExecutionErr( + "Workspace error handler needs to be defined".to_string(), + )) + } + } +} + async fn get_tokened_raw_script_by_path( Extension(user_db): Extension, Path((w_id, token, path)): Path<(String, String, StripPath)>, diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index d54b1a2a8a..3b0cd1d5b3 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -37,6 +37,7 @@ pub struct Flow { #[serde(skip_serializing_if = "Option::is_none")] pub draft_only: Option, pub tag: Option, + pub ws_error_handler_muted: Option, } #[derive(Serialize)] @@ -54,6 +55,7 @@ pub struct ListableFlow { pub has_draft: bool, #[serde(skip_serializing_if = "Option::is_none")] pub draft_only: Option, + pub ws_error_handler_muted: Option, } #[derive(Deserialize)] @@ -86,6 +88,8 @@ pub struct FlowValue { pub skip_expr: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cache_ttl: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ws_error_handler_muted: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index 74b071374f..2888bf10eb 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -281,7 +281,7 @@ pub async fn script_hash_to_tag_and_limits<'c>( Option, )> { let script = sqlx::query!( - "select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", + "select tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where hash = $1 AND workspace_id = $2", script_hash.0, w_id ) diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 1601461e42..c9ff860bc8 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -222,7 +222,7 @@ pub async fn get_latest_hash_for_path<'c>( Option, )> { let r_o = sqlx::query!( - "select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where path = $1 AND workspace_id = $2 AND + "select hash, tag, concurrent_limit, concurrency_time_window_s, cache_ttl, language as \"language: ScriptLang\", dedicated_worker from script where path = $1 AND workspace_id = $2 AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND deleted = false AND archived = false)", script_path, diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 1da85d5446..5dad06b039 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -162,6 +162,7 @@ pub struct Script { #[serde(skip_serializing_if = "Option::is_none")] pub concurrency_time_window_s: Option, pub dedicated_worker: Option, + pub ws_error_handler_muted: Option, } #[derive(Serialize)] @@ -181,6 +182,7 @@ pub struct ListableScript { #[serde(skip_serializing_if = "Option::is_none")] pub draft_only: Option, pub has_deploy_errors: bool, + pub ws_error_handler_muted: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -217,6 +219,7 @@ pub struct NewScript { pub concurrency_time_window_s: Option, pub cache_ttl: Option, pub dedicated_worker: Option, + pub ws_error_handler_muted: Option, } #[derive(Deserialize)] diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index bcceb5b7b2..3db64b61d1 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -591,16 +591,40 @@ pub async fn send_error_to_workspace_handler< .ok_or_else(|| Error::InternalErr(format!("no workspace settings for id {w_id}")))?; if let Some(error_handler) = error_handler { - run_error_handler( - rsmq, - queued_job, - db, - result, - &error_handler.strip_prefix("script/").unwrap(), - error_handler_extra_args, - false, - ) - .await? + let ws_error_handler_muted: Option = match queued_job.job_kind { + JobKind::Script => { + sqlx::query_scalar!( + "SELECT ws_error_handler_muted FROM script WHERE workspace_id = $1 AND hash = $2", + queued_job.workspace_id, + queued_job.script_hash.unwrap().0, + ) + .fetch_optional(db) + .await? + } + JobKind::Flow => { + sqlx::query_scalar!( + "SELECT ws_error_handler_muted FROM flow WHERE workspace_id = $1 AND path = $2", + queued_job.workspace_id, + queued_job.script_path.as_ref().unwrap(), + ) + .fetch_optional(db) + .await? + } + _ => None, + }; + + if !ws_error_handler_muted.unwrap_or(false) { + run_error_handler( + rsmq, + queued_job, + db, + result, + &error_handler.strip_prefix("script/").unwrap(), + error_handler_extra_args, + false, + ) + .await? + } } Ok(()) @@ -2066,7 +2090,9 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection "INSERT INTO queue (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, \ - flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner, root_job, tag, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl) + flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, \ + visible_to_owner, root_job, tag, concurrent_limit, concurrency_time_window_s, timeout, \ + flow_step_id, cache_ttl) VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, now()), $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29) \ RETURNING id", workspace_id, diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index a6dfc7d355..79e4658d15 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -650,17 +650,36 @@ pub async fn update_flow_status_after_job_completion_internal< save_in_cache(db, &flow_job, cached_res_path, &nresult).await; } - add_completed_job( - db, - &flow_job, - success && !is_failure_step && !skip_error_handler, - stop_early && skip_if_stop_early, - Json(&nresult), - logs, - 0, - rsmq.clone(), - ) - .await?; + let success = success && !is_failure_step && !skip_error_handler; + if success { + add_completed_job( + db, + &flow_job, + success, + stop_early && skip_if_stop_early, + Json(&nresult), + logs, + 0, + rsmq.clone(), + ) + .await?; + } else { + add_completed_job( + db, + &flow_job, + success, + stop_early && skip_if_stop_early, + Json( + &serde_json::from_str::(nresult.get()).unwrap_or_else( + |e| json!({"error": format!("Impossible to serialize error: {e}")}), + ), + ), + logs, + 0, + rsmq.clone(), + ) + .await?; + } } true } else { @@ -2079,6 +2098,7 @@ async fn compute_next_flow_transform( concurrency_time_window_s: None, skip_expr: None, cache_ttl: None, + ws_error_handler_muted: None, }, path: inner_path, }, @@ -2136,6 +2156,7 @@ async fn compute_next_flow_transform( concurrency_time_window_s: None, skip_expr: None, cache_ttl: None, + ws_error_handler_muted: None, }, path: Some(format!("{}/forloop", flow_job.script_path())), }, @@ -2227,6 +2248,7 @@ async fn compute_next_flow_transform( concurrency_time_window_s: None, skip_expr: None, cache_ttl: None, + ws_error_handler_muted: None, }, path: Some(format!( "{}/branchone-{}", @@ -2271,6 +2293,7 @@ async fn compute_next_flow_transform( concurrency_time_window_s: None, skip_expr: None, cache_ttl: None, + ws_error_handler_muted: None, }, path: Some(format!( "{}/branchall-{}", @@ -2332,6 +2355,7 @@ async fn compute_next_flow_transform( concurrency_time_window_s: None, skip_expr: None, cache_ttl: None, + ws_error_handler_muted: None, }, path: Some(format!( "{}/branchall-{}", diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 89d200d27f..3ce04b5c34 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -113,7 +113,8 @@ value: flow.value, schema: flow.schema, tag: flow.tag, - draft_only: true + draft_only: true, + ws_error_handler_muted: flow.ws_error_handler_muted } }) } @@ -159,7 +160,8 @@ summary: flow.summary, description: flow.description ?? '', value: flow.value, - schema: flow.schema + schema: flow.schema, + ws_error_handler_muted: flow.ws_error_handler_muted } }) if (enabled) { @@ -176,7 +178,8 @@ description: flow.description ?? '', value: flow.value, schema: flow.schema, - tag: flow.tag + tag: flow.tag, + ws_error_handler_muted: flow.ws_error_handler_muted } }) const scheduleExists = await ScheduleService.existsSchedule({ diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 63398ff552..e72b9d0ad9 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -17,6 +17,7 @@ import DrawerContent from './common/drawer/DrawerContent.svelte' import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte' import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte' + import ErrorHandlerToggleButton from '$lib/components/details/ErrorHandlerToggleButton.svelte' import { Bug, CheckCircle, @@ -190,7 +191,8 @@ dedicated_worker: script.dedicated_worker, concurrent_limit: script.concurrent_limit, concurrency_time_window_s: script.concurrency_time_window_s, - cache_ttl: script.cache_ttl + cache_ttl: script.cache_ttl, + ws_error_handler_muted: script.ws_error_handler_muted } }) history.replaceState(history.state, '', `/scripts/edit/${script.path}`) @@ -233,7 +235,8 @@ envs: script.envs, concurrent_limit: script.concurrent_limit, concurrency_time_window_s: script.concurrency_time_window_s, - cache_ttl: script.cache_ttl + cache_ttl: script.cache_ttl, + ws_error_handler_muted: script.ws_error_handler_muted } }) } @@ -318,6 +321,16 @@
+ +
+ +
+
+ + {#if errorHandlerMuted} + + {/if} + {#if $$slots.badges}