mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-30 08:16:49 +00:00
feat: add workspace setting to disable error handler for u/ scripts/flows (#7634)
* feat: add workspace setting to disable error handler for u/ scripts/flows Add a new workspace setting `error_handler_muted_on_user_path` that allows disabling the error handler for jobs related to scripts/flows that start with the "u/" prefix (user scripts/flows). Changes: - Add migration to add `error_handler_muted_on_user_path` column to workspace_settings - Update WorkspaceSettings struct and get_settings query - Update EditErrorHandler struct and edit_error_handler API endpoint - Update error handler cache to include the new setting - Add skip logic in send_error_to_workspace_handler for u/ paths - Add toggle in workspace settings UI The implementation uses the existing cached settings to avoid additional database queries, making it efficient. Closes #7633 Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> * all * update ee private * combine migrations * sqlx * fix: migration fixes for auto_invite JSONB column - Fix TEXT[] to JSONB conversion using to_jsonb() - Add GIN index on auto_invite for efficient ? operator queries - Add index cleanup to down migration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * sqlx * all * all * all --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
co-authored by
Claude Opus 4.5
claude[bot]
windmill-internal-app[bot]
Ruben Fiszel
parent
6a1d457257
commit
a9e6ba392d
@@ -177,6 +177,8 @@ pub(crate) struct ArchiveQueryParams {
|
||||
include_key: Option<bool>,
|
||||
include_workspace_dependencies: Option<bool>,
|
||||
default_ts: Option<String>,
|
||||
/// Settings format version: "v1" (default) returns legacy flat format, "v2" returns grouped format
|
||||
settings_version: Option<String>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -253,12 +255,41 @@ struct SimplifiedGroup {
|
||||
admins: Vec<String>,
|
||||
}
|
||||
|
||||
// V2 format: New grouped format
|
||||
#[derive(Serialize)]
|
||||
struct SimplifiedSettings {
|
||||
// slack_team_id: Option<String>,
|
||||
// slack_name: Option<String>,
|
||||
// slack_command_script: Option<String>,
|
||||
// slack_email: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
auto_invite: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
webhook: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
deploy_to: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error_handler: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
success_handler: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ai_config: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
large_file_storage: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
git_sync: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
default_app: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
default_scripts: Option<Value>,
|
||||
name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mute_critical_alerts: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
color: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
operator_settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
// V1 format: Legacy flat format for backward compatibility (matches main branch exactly)
|
||||
#[derive(Serialize)]
|
||||
struct SimplifiedSettingsLegacy {
|
||||
auto_invite_enabled: bool,
|
||||
auto_invite_as: String,
|
||||
auto_invite_mode: String,
|
||||
@@ -290,6 +321,25 @@ struct SimplifiedSettings {
|
||||
operator_settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
// Internal struct for querying database
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct SettingsRow {
|
||||
auto_invite: Option<Value>,
|
||||
webhook: Option<String>,
|
||||
deploy_to: Option<String>,
|
||||
error_handler: Option<Value>,
|
||||
success_handler: Option<Value>,
|
||||
ai_config: Option<serde_json::Value>,
|
||||
large_file_storage: Option<Value>,
|
||||
git_sync: Option<Value>,
|
||||
default_app: Option<String>,
|
||||
default_scripts: Option<Value>,
|
||||
name: Option<String>,
|
||||
mute_critical_alerts: Option<bool>,
|
||||
color: Option<String>,
|
||||
operator_settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub(crate) async fn tarball_workspace(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -311,6 +361,7 @@ pub(crate) async fn tarball_workspace(
|
||||
include_key,
|
||||
include_workspace_dependencies,
|
||||
default_ts,
|
||||
settings_version,
|
||||
}): Query<ArchiveQueryParams>,
|
||||
) -> Result<([(HeaderName, String); 2], impl IntoResponse)> {
|
||||
// require_admin(authed.is_admin, &authed.username)?;
|
||||
@@ -843,41 +894,103 @@ pub(crate) async fn tarball_workspace(
|
||||
}
|
||||
|
||||
if include_settings.unwrap_or(false) {
|
||||
let settings = sqlx::query_as!(
|
||||
SimplifiedSettings,
|
||||
let row = sqlx::query_as::<_, SettingsRow>(
|
||||
r#"SELECT
|
||||
-- slack_team_id,
|
||||
-- slack_name,
|
||||
-- slack_command_script,
|
||||
-- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,
|
||||
auto_invite_domain IS NOT NULL AS "auto_invite_enabled!",
|
||||
CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS "auto_invite_as!",
|
||||
CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS "auto_invite_mode!",
|
||||
auto_invite,
|
||||
webhook,
|
||||
deploy_to,
|
||||
error_handler,
|
||||
success_handler,
|
||||
ai_config,
|
||||
error_handler_extra_args,
|
||||
error_handler_muted_on_cancel,
|
||||
large_file_storage,
|
||||
git_sync,
|
||||
default_app,
|
||||
default_scripts,
|
||||
workspace.name,
|
||||
workspace.name as name,
|
||||
mute_critical_alerts,
|
||||
color,
|
||||
operator_settings
|
||||
FROM workspace_settings
|
||||
LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id
|
||||
WHERE workspace_id = $1"#,
|
||||
&w_id
|
||||
).fetch_one(&mut *tx).await?;
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let settings_str = serde_json::to_value(settings)
|
||||
.map(|v| serde_json::to_string_pretty(&v).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
.ok_or_else(|| Error::internal_err("Error serializing settings".to_string()))?;
|
||||
// Use v2 format only if explicitly requested, otherwise use v1 (legacy) for backward compatibility
|
||||
let settings_str = if settings_version.as_deref() == Some("v2") {
|
||||
let settings = SimplifiedSettings {
|
||||
auto_invite: row.auto_invite,
|
||||
webhook: row.webhook,
|
||||
deploy_to: row.deploy_to,
|
||||
error_handler: row.error_handler,
|
||||
success_handler: row.success_handler,
|
||||
ai_config: row.ai_config,
|
||||
large_file_storage: row.large_file_storage,
|
||||
git_sync: row.git_sync,
|
||||
default_app: row.default_app,
|
||||
default_scripts: row.default_scripts,
|
||||
name: row.name.clone().unwrap_or_default(),
|
||||
mute_critical_alerts: row.mute_critical_alerts,
|
||||
color: row.color.clone(),
|
||||
operator_settings: row.operator_settings.clone(),
|
||||
};
|
||||
serde_json::to_value(settings)
|
||||
.map(|v| serde_json::to_string_pretty(&v).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
} else {
|
||||
// V1 (legacy) format: convert JSONB to flat fields (matches main branch exactly)
|
||||
let (auto_invite_enabled, auto_invite_as, auto_invite_mode) =
|
||||
if let Some(ref ai) = row.auto_invite {
|
||||
let enabled = ai.get("enabled").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let operator = ai.get("operator").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let mode = ai.get("mode").and_then(|v| v.as_str()).unwrap_or("invite");
|
||||
(
|
||||
enabled,
|
||||
if operator { "operator".to_string() } else { "developer".to_string() },
|
||||
mode.to_string(),
|
||||
)
|
||||
} else {
|
||||
(false, "developer".to_string(), "invite".to_string())
|
||||
};
|
||||
|
||||
let (error_handler, error_handler_extra_args, error_handler_muted_on_cancel) =
|
||||
if let Some(ref eh) = row.error_handler {
|
||||
let path = eh.get("path").and_then(|v| v.as_str()).map(String::from);
|
||||
let extra_args = eh.get("extra_args").cloned();
|
||||
let muted_on_cancel = eh.get("muted_on_cancel").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
(path, extra_args, muted_on_cancel)
|
||||
} else {
|
||||
(None, None, false)
|
||||
};
|
||||
|
||||
let settings = SimplifiedSettingsLegacy {
|
||||
auto_invite_enabled,
|
||||
auto_invite_as,
|
||||
auto_invite_mode,
|
||||
webhook: row.webhook,
|
||||
deploy_to: row.deploy_to,
|
||||
error_handler,
|
||||
error_handler_extra_args,
|
||||
error_handler_muted_on_cancel,
|
||||
ai_config: row.ai_config,
|
||||
large_file_storage: row.large_file_storage,
|
||||
git_sync: row.git_sync,
|
||||
default_app: row.default_app,
|
||||
default_scripts: row.default_scripts,
|
||||
name: row.name.unwrap_or_default(),
|
||||
mute_critical_alerts: row.mute_critical_alerts,
|
||||
color: row.color,
|
||||
operator_settings: row.operator_settings,
|
||||
};
|
||||
serde_json::to_value(settings)
|
||||
.map(|v| serde_json::to_string_pretty(&v).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
.ok_or_else(|| Error::internal_err("Error serializing settings".to_string()))?;
|
||||
|
||||
archive
|
||||
.write_to_archive(&settings_str, "settings.json")
|
||||
|
||||
Reference in New Issue
Block a user