From c16bef8f296645ff873f9d8d28e3dcb50a65e304 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Fri, 14 Nov 2025 18:28:54 +0100 Subject: [PATCH 01/81] feat: add support for switch and attributes in pwsh params (#7143) --- .../parsers/windmill-parser-bash/src/lib.rs | 621 +++++++++++++++--- backend/windmill-worker/src/pwsh_executor.rs | 44 +- cli/wasm/regex/windmill_parser_wasm.d.ts | 16 +- cli/wasm/regex/windmill_parser_wasm.js | 162 ++--- cli/wasm/regex/windmill_parser_wasm_bg.wasm | Bin 351019 -> 355438 bytes frontend/package-lock.json | 8 +- frontend/package.json | 2 +- 7 files changed, 645 insertions(+), 208 deletions(-) diff --git a/backend/parsers/windmill-parser-bash/src/lib.rs b/backend/parsers/windmill-parser-bash/src/lib.rs index 5bfe6b20c5..f1652ed5d6 100644 --- a/backend/parsers/windmill-parser-bash/src/lib.rs +++ b/backend/parsers/windmill-parser-bash/src/lib.rs @@ -46,8 +46,6 @@ pub fn parse_powershell_sig(code: &str) -> anyhow::Result { lazy_static::lazy_static! { static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+)\}|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?\r?$"#).unwrap(); - - static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[([\w\[\]]+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?\r?"#).unwrap(); } fn parse_bash_file(code: &str) -> anyhow::Result>> { @@ -98,10 +96,12 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti let lower_code = code.to_lowercase(); let param_start = lower_code.find("param")?; - // Verify that only comments and whitespace appear before "param" + // Verify that only comments, whitespace, and [CmdletBinding()] appear before "param" let before_param = &code[..param_start]; let mut chars = before_param.chars().peekable(); let mut in_block_comment = false; + let mut in_attribute_bracket = false; + let mut bracket_depth = 0; while let Some(ch) = chars.next() { if in_block_comment { @@ -110,6 +110,19 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti chars.next(); // consume '>' in_block_comment = false; } + } else if in_attribute_bracket { + // Track bracket depth to handle nested brackets/parens in attributes + match ch { + '[' => bracket_depth += 1, + ']' => { + bracket_depth -= 1; + if bracket_depth == 0 { + in_attribute_bracket = false; + } + } + // Allow parentheses inside attributes (e.g., [CmdletBinding()]) + _ => {} + } } else { match ch { // Start of block comment: <# @@ -126,6 +139,11 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti chars.next(); } } + // Start of attribute bracket (e.g., [CmdletBinding()]) + '[' => { + in_attribute_bracket = true; + bracket_depth = 1; + } // Whitespace is allowed c if c.is_whitespace() => {} // Any other character means there's code before param @@ -134,8 +152,8 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti } } - // If we're still in a block comment at the end, it's unclosed - invalid - if in_block_comment { + // If we're still in a block comment or unclosed attribute bracket, it's invalid + if in_block_comment || in_attribute_bracket { return None; } @@ -214,108 +232,269 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti None } -enum ParserState { - Normal, - InSingleQuote, - InDoubleQuote, -} -fn split_pwsh_args(code: &str) -> Vec<&str> { - let mut chars = code.char_indices().peekable(); - let mut state = ParserState::Normal; - let mut splits = vec![]; - let mut last_idx = 0; - while let Some((idx, char)) = chars.next() { - match (&state, char) { - (ParserState::Normal, '\'') => { - state = ParserState::InSingleQuote; - } - (ParserState::Normal, '"') => { - state = ParserState::InDoubleQuote; - } - (ParserState::InSingleQuote, '\'') => { - state = ParserState::Normal; - } - (ParserState::InDoubleQuote, '"') => { - state = ParserState::Normal; - } - (ParserState::Normal, ',') => { - splits.push(&code[last_idx..idx]); - last_idx = idx + 1; // skip the comma - } - _ => {} - } - } - - if last_idx < code.len() { - splits.push(&code[last_idx..]); - } - - splits -} - fn parse_powershell_single_typ(typ: &str) -> Typ { match typ.to_lowercase().as_str() { "string" => Typ::Str(None), "int" | "long" => Typ::Int, "decimal" | "double" | "single" => Typ::Float, "datetime" => Typ::Datetime, - "bool" => Typ::Bool, + "bool" | "switch" => Typ::Bool, "pscustomobject" => Typ::Object(ObjectType::new(None, None)), _ => Typ::Str(None), } } -fn parse_powershell_file(code: &str) -> anyhow::Result>> { - let param_wrapper = extract_powershell_param_block(code, false); - let mut args = vec![]; - if let Some(param_wrapper) = param_wrapper { - let params = split_pwsh_args(param_wrapper); - for param in params { - if let Some(cap) = RE_POWERSHELL_ARGS.captures(param) { - let typ = cap.get(1).map(|x| x.as_str().to_string()); - let name = cap.get(2).unwrap().as_str().to_string(); +/// Single-pass PowerShell parameter parser. +/// Parses the content of a param() block and extracts all parameter information. +/// +/// This function processes PowerShell parameter declarations in a single pass, handling: +/// - Parameter attributes: [Parameter(Mandatory)], [Parameter(Mandatory=$true)], [ValidateSet(...)], etc. +/// - Type annotations: [string], [int[]], [PSCustomObject], etc. +/// - Variable names: $Name, $Value, etc. +/// - Default values: = 'text', = 25, = $env:VAR, etc. +/// - Mandatory detection: Parameters with Mandatory attribute are marked as required +fn parse_powershell_parameters(content: &str) -> anyhow::Result> { + #[derive(Debug, PartialEq)] + enum State { + Normal, + InSingleQuote, + InDoubleQuote, + InBracket, + } - let mut parsed_typ = if let Some(typ) = typ { - if typ.as_str().ends_with("[]") { - Some(Typ::List(Box::new(parse_powershell_single_typ( - typ.as_str().strip_suffix("[]").unwrap(), - )))) - } else { - Some(parse_powershell_single_typ(typ.as_str())) + let mut args = Vec::new(); + let mut chars = content.char_indices().peekable(); + let mut state = State::Normal; + let mut bracket_depth: i32 = 0; + let mut paren_depth: i32 = 0; + + // Current parameter being built + let mut type_annotation: Option = None; + let mut var_name: Option = None; + let mut default_value: Option = None; + let mut is_mandatory = false; + + // Track position for extracting text + let mut last_bracket_start = None; + let mut found_dollar = false; + + while let Some((idx, ch)) = chars.next() { + match state { + State::InSingleQuote => { + if ch == '\'' { + state = State::Normal; + } + } + State::InDoubleQuote => { + if ch == '"' { + // Check for escape character + if idx > 0 && content.chars().nth(idx - 1) != Some('`') { + state = State::Normal; } - } else { - None - }; + } + } + State::InBracket => { + match ch { + '[' => bracket_depth += 1, + ']' => { + bracket_depth -= 1; + if bracket_depth == 0 { + // Extract the bracket content + if let Some(start) = last_bracket_start { + let bracket_content = &content[start + 1..idx]; - let default = if let Some(x) = cap.get(3) { - Some(json!(x.as_str().to_string())) - } else if let Some(x) = cap.get(4) { - if parsed_typ.is_none() { - if x.as_str().parse::().is_ok() { - parsed_typ = Some(Typ::Int); - } else if x.as_str().parse::().is_ok() { - parsed_typ = Some(Typ::Float); + // Check if this is a Parameter attribute with Mandatory (case-insensitive) + let lower = bracket_content.to_lowercase(); + if lower.starts_with("parameter(") || lower.starts_with("parameter ") { + // Check for Mandatory (case-insensitive) + if lower.contains("mandatory") { + // Check if it's explicitly set to false + if !lower.contains("mandatory=$false") && !lower.contains("mandatory = $false") { + is_mandatory = true; + } + } + } + + // Check if this looks like a type (simple word, possibly with []) + let is_type = !bracket_content.contains('(') + && !bracket_content.contains('=') + && (bracket_content.chars().next().unwrap_or(' ').is_alphabetic() + || bracket_content.starts_with('[')); + + if is_type && !found_dollar { + type_annotation = Some(bracket_content.to_string()); + } + } + state = State::Normal; + last_bracket_start = None; } } - serde_json::Number::from_str(x.as_str()) - .ok() - .map(serde_json::Value::Number) - } else { - None - }; + '(' => paren_depth += 1, + ')' => paren_depth = paren_depth.saturating_sub(1), + _ => {} + } + } + State::Normal => { + match ch { + '\'' => state = State::InSingleQuote, + '"' => state = State::InDoubleQuote, + '[' => { + state = State::InBracket; + bracket_depth = 1; + last_bracket_start = Some(idx); + } + '$' => { + found_dollar = true; + // Extract variable name + let name_start = idx + 1; + let mut name_end = name_start; + while let Some(&(_, next_ch)) = chars.peek() { + if next_ch.is_alphanumeric() || next_ch == '_' { + name_end += 1; + chars.next(); + } else { + break; + } + } + var_name = Some(content[name_start..name_end].to_string()); + } + '=' if found_dollar => { + // Extract default value + // Skip whitespace after = + while let Some(&(_, next_ch)) = chars.peek() { + if next_ch.is_whitespace() { + chars.next(); + } else { + break; + } + } - args.push(Arg { - name: name, - typ: parsed_typ.unwrap_or(Typ::Str(None)), - default: default.clone(), - otyp: None, - has_default: default.is_some(), - oidx: None, - }); + let default_start = chars.peek().map(|(i, _)| *i).unwrap_or(content.len()); + let mut default_end = default_start; + let mut in_string = false; + let mut string_char = ' '; + + while let Some((i, ch)) = chars.peek().copied() { + if in_string { + if ch == string_char && content.chars().nth(i.saturating_sub(1)) != Some('`') { + in_string = false; + default_end = i + 1; + chars.next(); + } else { + default_end = i + 1; + chars.next(); + } + } else if ch == '\'' || ch == '"' { + in_string = true; + string_char = ch; + default_end = i + 1; + chars.next(); + } else if ch == ',' { + break; + } else if ch.is_whitespace() && chars.clone().skip(1).next().map(|(_, c)| c) == Some(',') { + break; + } else { + default_end = i + 1; + chars.next(); + } + } + + default_value = Some(content[default_start..default_end].trim().to_string()); + } + ',' => { + // End of parameter, finalize it + if let Some(name) = var_name.take() { + args.push(finalize_parameter(name, type_annotation.take(), default_value.take(), is_mandatory)?); + } + + // Reset for next parameter + type_annotation = None; + var_name = None; + default_value = None; + is_mandatory = false; + found_dollar = false; + } + _ => {} + } } } } - Ok(Some(args)) + + // Finalize last parameter + if let Some(name) = var_name { + args.push(finalize_parameter(name, type_annotation, default_value, is_mandatory)?); + } + + Ok(args) +} + +fn finalize_parameter( + name: String, + type_annotation: Option, + default_value: Option, + is_mandatory: bool, +) -> anyhow::Result { + // Store the original PowerShell type for use in the executor + let otyp = type_annotation.clone(); + + let mut parsed_typ = if let Some(typ) = type_annotation { + if typ.ends_with("[]") { + Some(Typ::List(Box::new(parse_powershell_single_typ( + typ.strip_suffix("[]").unwrap(), + )))) + } else { + Some(parse_powershell_single_typ(&typ)) + } + } else { + None + }; + + let default = if let Some(default_str) = default_value { + // Try to parse as string (quoted) + if (default_str.starts_with('"') && default_str.ends_with('"')) + || (default_str.starts_with('\'') && default_str.ends_with('\'')) + { + Some(json!(default_str[1..default_str.len() - 1].to_string())) + } else { + // Try to parse as number + if parsed_typ.is_none() { + if default_str.parse::().is_ok() { + parsed_typ = Some(Typ::Int); + } else if default_str.parse::().is_ok() { + parsed_typ = Some(Typ::Float); + } + } + serde_json::Number::from_str(&default_str) + .ok() + .map(serde_json::Value::Number) + } + } else { + None + }; + + // has_default semantics: + // - true: parameter is optional (has a default value OR is not mandatory) + // - false: parameter is required (marked as Mandatory AND no default value) + // Simplified: A parameter is optional unless it's mandatory without a default + let has_default = default.is_some() || !is_mandatory; + + Ok(Arg { + name, + typ: parsed_typ.unwrap_or(Typ::Str(None)), + default: default.clone(), + otyp, + has_default, + oidx: None, + }) +} + +fn parse_powershell_file(code: &str) -> anyhow::Result>> { + let param_wrapper = extract_powershell_param_block(code, false); + if let Some(param_wrapper) = param_wrapper { + Ok(Some(parse_powershell_parameters(param_wrapper)?)) + } else { + Ok(Some(vec![])) + } } #[cfg(test)] @@ -402,23 +581,23 @@ non_required="${5:-}" star_kwargs: false, args: vec![ Arg { - otyp: None, + otyp: None, // No type annotation name: "Msg".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("string".to_string()), // [string] name: "Msg2".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: None, // No type annotation name: "Dflt".to_string(), typ: Typ::Str(None), default: Some(json!("default value, with comma")), @@ -426,7 +605,7 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: Some("int".to_string()), // [int] name: "Nb".to_string(), typ: Typ::Int, default: Some(json!(3)), @@ -434,7 +613,7 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: None, // Type inferred from default value name: "Nb2".to_string(), typ: Typ::Float, default: Some(json!(5.0)), @@ -442,7 +621,7 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: None, // Type inferred from default value name: "Nb3".to_string(), typ: Typ::Int, default: Some(json!(5)), @@ -450,35 +629,35 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: None, // No type annotation name: "Wahoo".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("PSCustomObject".to_string()), // [PSCustomObject] name: "Obj".to_string(), typ: Typ::Object(ObjectType::new(None, None)), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("string[]".to_string()), // [string[]] name: "Arr".to_string(), typ: Typ::List(Box::new(Typ::Str(None))), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("string".to_string()), // [string] (last type bracket with Mandatory) name: "Message".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: false, // Required (Mandatory attribute) oidx: None } ], @@ -602,6 +781,242 @@ non_required="${5:-}" extract_powershell_param_block("function test-x{ param($Name)\n}", false), None ); + + // Valid: [CmdletBinding()] before param + assert_eq!( + extract_powershell_param_block("[CmdletBinding()]\nparam($Name)", false), + Some("$Name") + ); + assert_eq!( + extract_powershell_param_block("[CmdletBinding()]\nparam($Name, $Age)", true), + Some("param($Name, $Age)") + ); + + // Valid: [CmdletBinding()] with options before param + assert_eq!( + extract_powershell_param_block( + "[CmdletBinding(SupportsShouldProcess=$true)]\nparam($Path)", + false + ), + Some("$Path") + ); + + // Valid: Multiple attributes before param + assert_eq!( + extract_powershell_param_block( + "[CmdletBinding()]\n[OutputType([string])]\nparam($Value)", + false + ), + Some("$Value") + ); + + // Valid: CmdletBinding with comments + assert_eq!( + extract_powershell_param_block( + "# My function\n[CmdletBinding()]\nparam($Name)", + false + ), + Some("$Name") + ); + + // Valid: CmdletBinding with whitespace variations + assert_eq!( + extract_powershell_param_block( + "[CmdletBinding()] \n param($Name)", + false + ), + Some("$Name") + ); + + // Invalid: Unclosed attribute bracket + assert_eq!( + extract_powershell_param_block("[CmdletBinding(\nparam($Name)", false), + None + ); + } + + #[test] + fn test_parse_powershell_sig_with_parameter_attributes() -> anyhow::Result<()> { + // Test with [Parameter(Mandatory=$true)] attribute + let code = r#"[CmdletBinding()] +param( + [Parameter(Mandatory=$true)] + [string]$Name, + [Parameter(Mandatory=$false)] + [int]$Age = 25 +)"#; + let result = parse_powershell_sig(code)?; + assert_eq!(result.args.len(), 2); + assert_eq!(result.args[0].name, "Name"); + assert_eq!(result.args[0].typ, Typ::Str(None)); + assert_eq!(result.args[0].has_default, false); + assert_eq!(result.args[1].name, "Age"); + assert_eq!(result.args[1].typ, Typ::Int); + assert_eq!(result.args[1].has_default, true); + assert_eq!(result.args[1].default, Some(json!(25))); + + // Test with complex attributes + let code2 = r#"param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateSet('Red', 'Green', 'Blue')] + [string]$Color, + [Parameter(ValueFromPipeline=$true)] + [string[]]$Items +)"#; + let result2 = parse_powershell_sig(code2)?; + assert_eq!(result2.args.len(), 2); + assert_eq!(result2.args[0].name, "Color"); + assert_eq!(result2.args[0].typ, Typ::Str(None)); + assert_eq!(result2.args[1].name, "Items"); + assert_eq!(result2.args[1].typ, Typ::List(Box::new(Typ::Str(None)))); + + Ok(()) + } + + #[test] + fn test_powershell_single_pass_parser() -> anyhow::Result<()> { + // Test the single-pass parser with a complex real-world example + let code = r#"[CmdletBinding()] +param( + [Parameter(Mandatory=$true, Position=0, HelpMessage="Enter the server name")] + [ValidateNotNullOrEmpty()] + [string]$ServerName, + + [Parameter(Mandatory=$false)] + [ValidateRange(1, 65535)] + [int]$Port = 8080, + + [Parameter(ValueFromPipeline=$true)] + [string[]]$LogFiles, + + [ValidateSet('Debug', 'Info', 'Warning', 'Error')] + [string]$LogLevel = 'Info', + + [PSCustomObject]$Config +)"#; + let result = parse_powershell_sig(code)?; + + assert_eq!(result.args.len(), 5); + + // ServerName: mandatory string with no default + assert_eq!(result.args[0].name, "ServerName"); + assert_eq!(result.args[0].typ, Typ::Str(None)); + assert_eq!(result.args[0].has_default, false); + + // Port: optional int with default + assert_eq!(result.args[1].name, "Port"); + assert_eq!(result.args[1].typ, Typ::Int); + assert_eq!(result.args[1].default, Some(json!(8080))); + assert_eq!(result.args[1].has_default, true); + + // LogFiles: string array (no mandatory, so optional) + assert_eq!(result.args[2].name, "LogFiles"); + assert_eq!(result.args[2].typ, Typ::List(Box::new(Typ::Str(None)))); + assert_eq!(result.args[2].has_default, true); // Optional (not mandatory) + + // LogLevel: string with default (ValidateSet is ignored but doesn't break parsing) + assert_eq!(result.args[3].name, "LogLevel"); + assert_eq!(result.args[3].typ, Typ::Str(None)); + assert_eq!(result.args[3].default, Some(json!("Info"))); + assert_eq!(result.args[3].has_default, true); + + // Config: PSCustomObject (no mandatory, so optional) + assert_eq!(result.args[4].name, "Config"); + assert_eq!(result.args[4].typ, Typ::Object(ObjectType::new(None, None))); + assert_eq!(result.args[4].has_default, true); // Optional (not mandatory) + + Ok(()) + } + + #[test] + fn test_powershell_mandatory_attribute() -> anyhow::Result<()> { + // Test various forms of the Mandatory attribute + let code = r#"param( + [Parameter(Mandatory)] + [string]$RequiredNoEquals, + + [Parameter(Mandatory=$true)] + [string]$RequiredWithTrue, + + [Parameter(Mandatory = $true)] + [string]$RequiredWithSpaces, + + [Parameter(Mandatory=$false)] + [string]$NotRequired, + + [Parameter(Position=0)] + [string]$NoMandatory, + + [string]$PlainRequired = "default", + + [Parameter(Mandatory=$true)] + [int]$RequiredInt +)"#; + let result = parse_powershell_sig(code)?; + + assert_eq!(result.args.len(), 7); + + // RequiredNoEquals: mandatory without =$true + assert_eq!(result.args[0].name, "RequiredNoEquals"); + assert_eq!(result.args[0].has_default, false); // Required (mandatory, no default) + + // RequiredWithTrue: mandatory with =$true + assert_eq!(result.args[1].name, "RequiredWithTrue"); + assert_eq!(result.args[1].has_default, false); // Required + + // RequiredWithSpaces: mandatory with spaces + assert_eq!(result.args[2].name, "RequiredWithSpaces"); + assert_eq!(result.args[2].has_default, false); // Required + + // NotRequired: explicitly Mandatory=$false + assert_eq!(result.args[3].name, "NotRequired"); + assert_eq!(result.args[3].has_default, true); // Optional (not mandatory) + + // NoMandatory: no Mandatory attribute + assert_eq!(result.args[4].name, "NoMandatory"); + assert_eq!(result.args[4].has_default, true); // Optional (not mandatory) + + // PlainRequired: has default value (always optional) + assert_eq!(result.args[5].name, "PlainRequired"); + assert_eq!(result.args[5].has_default, true); // Optional (has default) + assert_eq!(result.args[5].default, Some(json!("default"))); + + // RequiredInt: mandatory int + assert_eq!(result.args[6].name, "RequiredInt"); + assert_eq!(result.args[6].typ, Typ::Int); + assert_eq!(result.args[6].has_default, false); // Required + + Ok(()) + } + + #[test] + fn test_powershell_case_insensitive_parameter() -> anyhow::Result<()> { + // Test that [parameter(...)] is case-insensitive + let code = r#"param( + [parameter(Mandatory)] + [string]$LowerCase, + + [PARAMETER(MANDATORY=$TRUE)] + [string]$UpperCase, + + [Parameter(mandatory=$true)] + [string]$MixedCase +)"#; + let result = parse_powershell_sig(code)?; + + assert_eq!(result.args.len(), 3); + + // All should be detected as mandatory + assert_eq!(result.args[0].name, "LowerCase"); + assert_eq!(result.args[0].has_default, false); + + assert_eq!(result.args[1].name, "UpperCase"); + assert_eq!(result.args[1].has_default, false); + + assert_eq!(result.args[2].name, "MixedCase"); + assert_eq!(result.args[2].has_default, false); + + Ok(()) } #[test] diff --git a/backend/windmill-worker/src/pwsh_executor.rs b/backend/windmill-worker/src/pwsh_executor.rs index 4a4b85f8ac..cda448dc31 100644 --- a/backend/windmill-worker/src/pwsh_executor.rs +++ b/backend/windmill-worker/src/pwsh_executor.rs @@ -255,20 +255,42 @@ pub async fn handle_powershell_job( job.args.as_ref() }; - let args_owned = windmill_parser_bash::parse_powershell_sig(&content)? + let parsed_sig = windmill_parser_bash::parse_powershell_sig(&content)?; + + parsed_sig .args .iter() - .map(|arg| { - ( - arg.name.clone(), - job_args.and_then(|x| x.get(&arg.name).map(|x| raw_to_pwsh_param(x.get()))), - ) - }) - .collect::)>>(); + .filter_map(|arg| { + let value_opt = job_args.and_then(|x| x.get(&arg.name)); - args_owned - .into_iter() - .filter_map(|(n, v)| v.map(|v| format!("-{n} {v}"))) + // Check if this is a switch parameter (only [switch], not [bool]) + let is_switch = arg.otyp.as_ref().map(|t| { + t.to_lowercase() == "switch" + }).unwrap_or(false); + + if is_switch { + // Handle switch parameters: -SwitchName or omit + if let Some(value) = value_opt { + match serde_json::from_str::(value.get()) { + Ok(serde_json::Value::Bool(true)) => { + // Switch is enabled: just pass -SwitchName + Some(format!("-{}", arg.name)) + } + Ok(serde_json::Value::Bool(false)) | _ => { + // Switch is disabled or invalid: omit the parameter + None + } + } + } else { + // No value provided, omit the switch (defaults to false) + None + } + } else { + // Regular parameter (including [bool]): format as -ParamName Value + // For [bool] parameters, this will be -ParamName $true or -ParamName $false + value_opt.map(|v| format!("-{} {}", arg.name, raw_to_pwsh_param(v.get()))) + } + }) .collect::>() .join(" ") }; diff --git a/cli/wasm/regex/windmill_parser_wasm.d.ts b/cli/wasm/regex/windmill_parser_wasm.d.ts index 59f34f2b18..7886800b20 100644 --- a/cli/wasm/regex/windmill_parser_wasm.d.ts +++ b/cli/wasm/regex/windmill_parser_wasm.d.ts @@ -1,14 +1,14 @@ /* tslint:disable */ /* eslint-disable */ -export function parse_assets_sql(code: string): string; -export function parse_db_resource(code: string): string | undefined; -export function parse_bash(code: string): string; export function parse_mssql(code: string): string; -export function parse_oracledb(code: string): string; export function parse_powershell(code: string): string; -export function parse_bigquery(code: string): string; +export function parse_oracledb(code: string): string; export function parse_graphql(code: string): string; -export function parse_snowflake(code: string): string; -export function parse_duckdb(code: string): string; -export function parse_sql(code: string): string; +export function parse_assets_sql(code: string): string; export function parse_mysql(code: string): string; +export function parse_bash(code: string): string; +export function parse_duckdb(code: string): string; +export function parse_db_resource(code: string): string | undefined; +export function parse_bigquery(code: string): string; +export function parse_snowflake(code: string): string; +export function parse_sql(code: string): string; diff --git a/cli/wasm/regex/windmill_parser_wasm.js b/cli/wasm/regex/windmill_parser_wasm.js index 25a60a9b8e..b3b62ebd32 100644 --- a/cli/wasm/regex/windmill_parser_wasm.js +++ b/cli/wasm/regex/windmill_parser_wasm.js @@ -68,60 +68,6 @@ function getStringFromWasm0(ptr, len) { ptr = ptr >>> 0; return decodeText(ptr, len); } -/** - * @param {string} code - * @returns {string} - */ -export function parse_assets_sql(code) { - let deferred2_0; - let deferred2_1; - try { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_assets_sql(ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - -/** - * @param {string} code - * @returns {string | undefined} - */ -export function parse_db_resource(code) { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_db_resource(ptr0, len0); - let v2; - if (ret[0] !== 0) { - v2 = getStringFromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - } - return v2; -} - -/** - * @param {string} code - * @returns {string} - */ -export function parse_bash(code) { - let deferred2_0; - let deferred2_1; - try { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_bash(ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - /** * @param {string} code * @returns {string} @@ -141,25 +87,6 @@ export function parse_mssql(code) { } } -/** - * @param {string} code - * @returns {string} - */ -export function parse_oracledb(code) { - let deferred2_0; - let deferred2_1; - try { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_oracledb(ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - /** * @param {string} code * @returns {string} @@ -183,13 +110,13 @@ export function parse_powershell(code) { * @param {string} code * @returns {string} */ -export function parse_bigquery(code) { +export function parse_oracledb(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_bigquery(ptr0, len0); + const ret = wasm.parse_oracledb(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); @@ -221,13 +148,51 @@ export function parse_graphql(code) { * @param {string} code * @returns {string} */ -export function parse_snowflake(code) { +export function parse_assets_sql(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_snowflake(ptr0, len0); + const ret = wasm.parse_assets_sql(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +/** + * @param {string} code + * @returns {string} + */ +export function parse_mysql(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_mysql(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +/** + * @param {string} code + * @returns {string} + */ +export function parse_bash(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_bash(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); @@ -255,17 +220,33 @@ export function parse_duckdb(code) { } } +/** + * @param {string} code + * @returns {string | undefined} + */ +export function parse_db_resource(code) { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_db_resource(ptr0, len0); + let v2; + if (ret[0] !== 0) { + v2 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v2; +} + /** * @param {string} code * @returns {string} */ -export function parse_sql(code) { +export function parse_bigquery(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_sql(ptr0, len0); + const ret = wasm.parse_bigquery(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); @@ -278,13 +259,32 @@ export function parse_sql(code) { * @param {string} code * @returns {string} */ -export function parse_mysql(code) { +export function parse_snowflake(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_mysql(ptr0, len0); + const ret = wasm.parse_snowflake(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +/** + * @param {string} code + * @returns {string} + */ +export function parse_sql(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_sql(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); diff --git a/cli/wasm/regex/windmill_parser_wasm_bg.wasm b/cli/wasm/regex/windmill_parser_wasm_bg.wasm index 45a144132db83055b45b7a905509a3bce50c05a5..34d6284a98f4a78b2f801adbd5ba6b71f666dd54 100644 GIT binary patch delta 74047 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 delta 74393 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 Date: Fri, 14 Nov 2025 19:02:30 +0100 Subject: [PATCH 02/81] chore(main): release 1.576.0 (#7140) * chore(main): release 1.576.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 14 ++ backend/Cargo.lock | 130 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 96 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd125ea43..fa67e06672 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.576.0](https://github.com/windmill-labs/windmill/compare/v1.575.4...v1.576.0) (2025-11-14) + + +### Features + +* add support for switch and attributes in pwsh params ([#7143](https://github.com/windmill-labs/windmill/issues/7143)) ([c16bef8](https://github.com/windmill-labs/windmill/commit/c16bef8f296645ff873f9d8d28e3dcb50a65e304)) +* **ai:** handle aws bedrock as provider ([#7131](https://github.com/windmill-labs/windmill/issues/7131)) ([30eb9aa](https://github.com/windmill-labs/windmill/commit/30eb9aae25eeb563ad119ef93f3ff1ab17c66d75)) +* webhook by flow version ([#7062](https://github.com/windmill-labs/windmill/issues/7062)) ([09cdfb4](https://github.com/windmill-labs/windmill/commit/09cdfb4556748903dc5bbf53ef3356ac97c57d90)) + + +### Bug Fixes + +* use proper TLS connector for DuckLake instance catalog setup ([#7138](https://github.com/windmill-labs/windmill/issues/7138)) ([cf36fe3](https://github.com/windmill-labs/windmill/commit/cf36fe3bb1beec80fa84dc342a8a38cc7369bc4d)) + ## [1.575.4](https://github.com/windmill-labs/windmill/compare/v1.575.3...v1.575.4) (2025-11-13) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 6452d027a9..8d5f4e0ac0 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -199,22 +199,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1026,7 +1026,7 @@ dependencies = [ "http 1.3.1", "http-body 0.4.6", "hyper 0.14.32", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-rustls 0.24.2", "hyper-rustls 0.27.7", "hyper-util", @@ -1182,7 +1182,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "itoa", "matchit", @@ -1569,7 +1569,7 @@ dependencies = [ "hex", "http 1.3.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-named-pipe", "hyper-util", "hyperlocal", @@ -1799,9 +1799,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] @@ -1945,9 +1945,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.45" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "jobserver", @@ -3625,7 +3625,7 @@ dependencies = [ "hickory-resolver", "http 1.3.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-rustls 0.27.7", "hyper-util", "ipnet", @@ -3713,7 +3713,7 @@ dependencies = [ "http 1.3.1", "httparse", "hyper 0.14.32", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "itertools 0.10.5", "memmem", @@ -3906,7 +3906,7 @@ dependencies = [ "hkdf", "http 1.3.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "idna", "indexmap 2.11.1", @@ -4177,7 +4177,7 @@ dependencies = [ "http 1.3.1", "http-body-util", "hyper 0.14.32", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "libc", "log", @@ -4231,7 +4231,7 @@ dependencies = [ "deno_error", "deno_tls", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-rustls 0.27.7", "hyper-util", "log", @@ -4361,7 +4361,7 @@ dependencies = [ "h2 0.4.12", "http 1.3.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "once_cell", "rustls-tokio-stream", @@ -5225,7 +5225,7 @@ dependencies = [ "base64 0.21.7", "bytes", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "pin-project", "rand 0.8.5", @@ -5292,9 +5292,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixedbitset" @@ -6604,9 +6604,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -6635,7 +6635,7 @@ dependencies = [ "futures-util", "headers", "http 1.3.1", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-rustls 0.27.7", "hyper-util", "pin-project-lite", @@ -6652,7 +6652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -6683,7 +6683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "log", "rustls 0.23.29", @@ -6701,7 +6701,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -6729,7 +6729,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -6750,7 +6750,7 @@ dependencies = [ "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.8.0", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", @@ -6771,7 +6771,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -7419,7 +7419,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-http-proxy", "hyper-rustls 0.27.7", "hyper-timeout", @@ -8946,7 +8946,7 @@ dependencies = [ "http-body-util", "httparse", "humantime", - "hyper 1.8.0", + "hyper 1.8.1", "itertools 0.14.0", "md-5 0.10.6", "parking_lot 0.12.5", @@ -10712,7 +10712,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-rustls 0.27.7", "hyper-tls 0.6.0", "hyper-util", @@ -10772,7 +10772,7 @@ dependencies = [ "futures", "getrandom 0.2.16", "http 1.3.1", - "hyper 1.8.0", + "hyper 1.8.1", "parking_lot 0.11.2", "reqwest 0.12.24", "reqwest-middleware", @@ -13945,7 +13945,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.0", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -15148,7 +15148,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15208,7 +15208,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "argon2", @@ -15251,7 +15251,7 @@ dependencies = [ "hf-hub", "hmac", "http 1.3.1", - "hyper 1.8.0", + "hyper 1.8.1", "indexmap 2.11.1", "itertools 0.14.0", "jsonwebtoken 8.3.0", @@ -15329,7 +15329,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.575.4" +version = "1.576.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15344,7 +15344,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.575.4" +version = "1.576.0" dependencies = [ "chrono", "lazy_static", @@ -15358,7 +15358,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "axum", @@ -15377,7 +15377,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "async-recursion", @@ -15405,7 +15405,7 @@ dependencies = [ "globset", "hex", "hmac", - "hyper 1.8.0", + "hyper 1.8.1", "indexmap 2.11.1", "itertools 0.14.0", "jsonwebtoken 8.3.0", @@ -15462,7 +15462,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.575.4" +version = "1.576.0" dependencies = [ "regex", "serde", @@ -15477,7 +15477,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "bytes", @@ -15501,7 +15501,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.575.4" +version = "1.576.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15513,7 +15513,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.575.4" +version = "1.576.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "lazy_static", @@ -15534,7 +15534,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "serde_json", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "gosyn", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "lazy_static", @@ -15570,7 +15570,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "serde_json", @@ -15582,7 +15582,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "nu-parser", @@ -15593,7 +15593,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15604,7 +15604,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15616,7 +15616,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "async-recursion", @@ -15639,7 +15639,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "lazy_static", @@ -15653,7 +15653,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15670,7 +15670,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "lazy_static", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "lazy_static", @@ -15702,7 +15702,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "serde", @@ -15713,7 +15713,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "async-recursion", @@ -15748,7 +15748,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.575.4" +version = "1.576.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.575.4" +version = "1.576.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 457c9f91dd..c78695440c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.575.4" +version = "1.576.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.575.4" +version = "1.576.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index e57812e2e2..baa17ffac9 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.575.4 + version: 1.576.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index d5d3bd172f..34dfefebb0 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.575.4"; +export const VERSION = "v1.576.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 6dba2bce7a..94ac4d1093 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.575.4"; +export const VERSION = "1.576.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e60edf14cf..60d8af50d4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.575.4", + "version": "1.576.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.575.4", + "version": "1.576.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 67a75fac8b..f16b6cdb18 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.575.4", + "version": "1.576.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index fb1c0f45a2..153bb83088 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.575.4" -wmill_pg = ">=1.575.4" +wmill = ">=1.576.0" +wmill_pg = ">=1.576.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 5e3d9dad11..a74570dabc 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.575.4 + version: 1.576.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index a843ee48ad..3244fb4436 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.575.4' + ModuleVersion = '1.576.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 25994859cc..f1971f1e0b 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.575.4" +version = "1.576.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 148cb808dd..d44a5c6297 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.575.4" +version = "1.576.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index b2c821e8ff..fb7186f17e 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.575.4", + "version": "1.576.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 3f178725a7..494ce7405d 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.575.4", + "version": "1.576.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index b465773b4f..7eec38d94a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.575.4 +1.576.0 From d3fc459b407682bf588236236916363d94f3e1ff Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:14:28 +0100 Subject: [PATCH 03/81] fix: DuckDB FFI crash fix (#7145) --- backend/windmill-duckdb-ffi-internal/Cargo.lock | 10 ++++++---- backend/windmill-duckdb-ffi-internal/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/windmill-duckdb-ffi-internal/Cargo.lock b/backend/windmill-duckdb-ffi-internal/Cargo.lock index a53a988f30..92bc697c87 100644 --- a/backend/windmill-duckdb-ffi-internal/Cargo.lock +++ b/backend/windmill-duckdb-ffi-internal/Cargo.lock @@ -415,8 +415,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "duckdb" -version = "1.4.1" -source = "git+https://github.com/diegoimbert/duckdb-rs?branch=main#0df52a6941c9996d7ec60d585eaf6430db8c48cf" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46d5568337ee1f7ea8779e1d9aa2eafcdf156458713ce65afb246c5d2cf5850" dependencies = [ "arrow", "cast", @@ -702,8 +703,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libduckdb-sys" -version = "1.4.1" -source = "git+https://github.com/diegoimbert/duckdb-rs?branch=main#0df52a6941c9996d7ec60d585eaf6430db8c48cf" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6650a7ea86fce24fe1fbf5b037671a8b77c59d135703fc6085b8a1827e66e977" dependencies = [ "cc", "flate2", diff --git a/backend/windmill-duckdb-ffi-internal/Cargo.toml b/backend/windmill-duckdb-ffi-internal/Cargo.toml index 9560e1d0e6..610dfb8fcc 100644 --- a/backend/windmill-duckdb-ffi-internal/Cargo.toml +++ b/backend/windmill-duckdb-ffi-internal/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] chrono = "0.4.41" -duckdb = { git = "https://github.com/diegoimbert/duckdb-rs", branch = "main", features = ["bundled"] } +duckdb = { version = "1.4.2", features = ["bundled"] } rust_decimal = "1.37.2" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "^1", features = ["preserve_order", "raw_value"] } From 5c893becfd3caca7c61c8898fd2ed9a0d2e33646 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 14 Nov 2025 23:19:58 +0100 Subject: [PATCH 04/81] chore(main): release 1.576.1 (#7146) * chore(main): release 1.576.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 54 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa67e06672..846f4eae2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.576.1](https://github.com/windmill-labs/windmill/compare/v1.576.0...v1.576.1) (2025-11-14) + + +### Bug Fixes + +* DuckDB FFI crash fix ([#7145](https://github.com/windmill-labs/windmill/issues/7145)) ([d3fc459](https://github.com/windmill-labs/windmill/commit/d3fc459b407682bf588236236916363d94f3e1ff)) + ## [1.576.0](https://github.com/windmill-labs/windmill/compare/v1.575.4...v1.576.0) (2025-11-14) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 8d5f4e0ac0..960c57b32f 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15148,7 +15148,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -15208,7 +15208,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "argon2", @@ -15329,7 +15329,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.576.0" +version = "1.576.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15344,7 +15344,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.576.0" +version = "1.576.1" dependencies = [ "chrono", "lazy_static", @@ -15358,7 +15358,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "axum", @@ -15377,7 +15377,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "async-recursion", @@ -15462,7 +15462,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.576.0" +version = "1.576.1" dependencies = [ "regex", "serde", @@ -15477,7 +15477,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "bytes", @@ -15501,7 +15501,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.576.0" +version = "1.576.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15513,7 +15513,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.576.0" +version = "1.576.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "lazy_static", @@ -15534,7 +15534,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "serde_json", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "gosyn", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "lazy_static", @@ -15570,7 +15570,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "serde_json", @@ -15582,7 +15582,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "nu-parser", @@ -15593,7 +15593,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15604,7 +15604,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15616,7 +15616,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "async-recursion", @@ -15639,7 +15639,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "lazy_static", @@ -15653,7 +15653,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15670,7 +15670,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "lazy_static", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "lazy_static", @@ -15702,7 +15702,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "serde", @@ -15713,7 +15713,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "async-recursion", @@ -15748,7 +15748,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.576.0" +version = "1.576.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.576.0" +version = "1.576.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c78695440c..55d9842300 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.576.0" +version = "1.576.1" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.576.0" +version = "1.576.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index baa17ffac9..bc7469ee56 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.576.0 + version: 1.576.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 34dfefebb0..e8b4b9fd83 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.576.0"; +export const VERSION = "v1.576.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 94ac4d1093..d67c968b8b 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.576.0"; +export const VERSION = "1.576.1"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 60d8af50d4..d0e1b807ba 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.576.0", + "version": "1.576.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.576.0", + "version": "1.576.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index f16b6cdb18..1313ba3b07 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.576.0", + "version": "1.576.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 153bb83088..b78b3cb6e3 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.576.0" -wmill_pg = ">=1.576.0" +wmill = ">=1.576.1" +wmill_pg = ">=1.576.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index a74570dabc..0d53e44356 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.576.0 + version: 1.576.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 3244fb4436..f3b6447085 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.576.0' + ModuleVersion = '1.576.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index f1971f1e0b..322507c7d0 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.576.0" +version = "1.576.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index d44a5c6297..ceb8715758 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.576.0" +version = "1.576.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index fb7186f17e..76a994dada 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.576.0", + "version": "1.576.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 494ce7405d..871fc27f08 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.576.0", + "version": "1.576.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 7eec38d94a..9d295502df 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.576.0 +1.576.1 From fa1bc3c71185fcf52c06d39e71ae7c85166fa367 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:56:45 +0100 Subject: [PATCH 05/81] DuckDB test to ensure FFI doesn't crash on simple query (#7147) * test_duckdb_ffi * build dev duckdb lib * cache --- .github/workflows/backend-test.yml | 10 +++++++++- backend/run_until_fail.sh | 3 ++- backend/tests/worker.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 5fa4a289f2..366e6bd27d 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -67,13 +67,21 @@ jobs: - name: Substitute EE code (EE logic is behind feature flag) run: | ./substitute_ee_code.sh --copy --dir ./windmill-ee-private + - name: Cache DuckDB FFI module build + uses: actions/cache@v3 + with: + path: ./backend/windmill-duckdb-ffi-internal/target + key: ${{ runner.os }}-duckdb-ffi-${{ hashFiles('./backend/windmill-duckdb-ffi-internal/src/**/*.rs', './backend/windmill-duckdb-ffi-internal/Cargo.toml', './backend/windmill-duckdb-ffi-internal/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-duckdb-ffi- - name: cargo test timeout-minutes: 16 run: deno --version && bun -v && go version && python3 --version && + cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. && SQLX_OFFLINE=true DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features - enterprise,deno_core,license,python,rust,scoped_cache,private --all -- + enterprise,deno_core,license,python,duckdb,rust,scoped_cache,private --all -- --nocapture diff --git a/backend/run_until_fail.sh b/backend/run_until_fail.sh index d8c461504b..935bacc88c 100755 --- a/backend/run_until_fail.sh +++ b/backend/run_until_fail.sh @@ -1,6 +1,7 @@ #!/bin/bash # Run the command repeatedly until it fails (exits with non-zero code) +cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. while true; do DISABLE_EMBEDDING=true \ RUST_LOG=info \ @@ -9,7 +10,7 @@ while true; do GO_PATH=$(which go) \ UV_PATH=$(which uv) \ CARGO_PATH=$(which cargo) \ - cargo test --features enterprise,deno_core,license,python,rust,scoped_cache \ + cargo test --features enterprise,deno_core,license,python,duckdb,rust,scoped_cache \ -- --nocapture --test-threads=8 | tee /tmp/test.log # Capture the exit code of the cargo test command (not tee) diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 57d7833c9d..0a92dfc74d 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -2944,3 +2944,33 @@ async fn test_workflow_as_code(db: Pool) -> anyhow::Result<()> { .await; Ok(()) } + +#[cfg(feature = "duckdb")] +#[sqlx::test(fixtures("base"))] +async fn test_duckdb_ffi(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + + let server = ApiServer::start(db.clone()).await?; + + let content = "-- result_collection=last_statement_first_row_scalar\nSELECT 'Hello world!';"; + + let flow: FlowValue = serde_json::from_value(serde_json::json!({ + "modules": [{ + "value": { + "type": "rawscript", + "language": "duckdb", + "content": content, + }, + }], + })) + .unwrap(); + + let result = + RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None, restarted_from: None }) + .run_until_complete(&db, false, server.addr.port()) + .await + .json_result() + .unwrap(); + assert_eq!(result, serde_json::json!("Hello world!")); + Ok(()) +} From 6426ebf8cb713443904065064b6a07eb1db0761a Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:10:05 +0100 Subject: [PATCH 06/81] fix: temporary fix for duckdb type_aliases causing issues (#7148) --- .../windmill-duckdb-ffi-internal/src/lib.rs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/backend/windmill-duckdb-ffi-internal/src/lib.rs b/backend/windmill-duckdb-ffi-internal/src/lib.rs index 501b5c5dee..25f8e5aba8 100644 --- a/backend/windmill-duckdb-ffi-internal/src/lib.rs +++ b/backend/windmill-duckdb-ffi-internal/src/lib.rs @@ -245,7 +245,7 @@ fn do_duckdb_inner( } // Statement needs to be stepped at least once or stmt.column_names() will panic let mut column_names = None; - let mut type_aliases = None; + // let mut type_aliases = None; loop { let row = rows.next(); match row { @@ -260,17 +260,18 @@ fn do_duckdb_inner( column_names.as_ref().unwrap() } }; - let type_aliases = match type_aliases.as_ref() { - Some(type_aliases) => type_aliases, - None => { - type_aliases = Some( - (0..stmt.column_count()) - .map(|i| stmt.column_logical_type(i).get_alias()) - .collect::>(), - ); - type_aliases.as_ref().unwrap() - } - }; + // let type_aliases = match type_aliases.as_ref() { + // Some(type_aliases) => type_aliases, + // None => { + // type_aliases = Some( + // (0..stmt.column_count()) + // .map(|i| stmt.column_logical_type(i).get_alias()) + // .collect::>(), + // ); + // type_aliases.as_ref().unwrap() + // } + // }; + let type_aliases = (0..stmt.column_count()).map(|_| None).collect::>(); let row = row_to_value(row, &column_names.as_slice(), &type_aliases.as_slice()) .map_err(|e| e.to_string())?; From 7215aa971201ef8c9248320ff7feb55079aaab60 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 15 Nov 2025 19:17:08 +0100 Subject: [PATCH 07/81] chore(main): release 1.576.2 (#7149) * chore(main): release 1.576.2 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 54 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 846f4eae2f..1a0973aab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.576.2](https://github.com/windmill-labs/windmill/compare/v1.576.1...v1.576.2) (2025-11-15) + + +### Bug Fixes + +* temporary fix for duckdb type_aliases causing issues ([#7148](https://github.com/windmill-labs/windmill/issues/7148)) ([6426ebf](https://github.com/windmill-labs/windmill/commit/6426ebf8cb713443904065064b6a07eb1db0761a)) + ## [1.576.1](https://github.com/windmill-labs/windmill/compare/v1.576.0...v1.576.1) (2025-11-14) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 960c57b32f..8907781c9e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15148,7 +15148,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "aws-sdk-config", @@ -15208,7 +15208,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "argon2", @@ -15329,7 +15329,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.576.1" +version = "1.576.2" dependencies = [ "base64 0.22.1", "chrono", @@ -15344,7 +15344,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.576.1" +version = "1.576.2" dependencies = [ "chrono", "lazy_static", @@ -15358,7 +15358,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "axum", @@ -15377,7 +15377,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "async-recursion", @@ -15462,7 +15462,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.576.1" +version = "1.576.2" dependencies = [ "regex", "serde", @@ -15477,7 +15477,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "bytes", @@ -15501,7 +15501,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.576.1" +version = "1.576.2" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15513,7 +15513,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.576.1" +version = "1.576.2" dependencies = [ "convert_case 0.6.0", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "lazy_static", @@ -15534,7 +15534,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "serde_json", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "gosyn", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "lazy_static", @@ -15570,7 +15570,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "serde_json", @@ -15582,7 +15582,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "nu-parser", @@ -15593,7 +15593,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15604,7 +15604,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15616,7 +15616,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "async-recursion", @@ -15639,7 +15639,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "lazy_static", @@ -15653,7 +15653,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15670,7 +15670,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "lazy_static", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "lazy_static", @@ -15702,7 +15702,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "serde", @@ -15713,7 +15713,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "async-recursion", @@ -15748,7 +15748,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.576.1" +version = "1.576.2" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.576.1" +version = "1.576.2" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 55d9842300..d1cab72381 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.576.1" +version = "1.576.2" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.576.1" +version = "1.576.2" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index bc7469ee56..5bcafc5c78 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.576.1 + version: 1.576.2 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index e8b4b9fd83..a83713c98a 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.576.1"; +export const VERSION = "v1.576.2"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index d67c968b8b..beceeeb120 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.576.1"; +export const VERSION = "1.576.2"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d0e1b807ba..bfcb5919b2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.576.1", + "version": "1.576.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.576.1", + "version": "1.576.2", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 1313ba3b07..0727867786 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.576.1", + "version": "1.576.2", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index b78b3cb6e3..cb35dfaa73 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.576.1" -wmill_pg = ">=1.576.1" +wmill = ">=1.576.2" +wmill_pg = ">=1.576.2" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 0d53e44356..e912b1a0a7 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.576.1 + version: 1.576.2 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index f3b6447085..ba38065bb7 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.576.1' + ModuleVersion = '1.576.2' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 322507c7d0..da0f5f1907 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.576.1" +version = "1.576.2" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index ceb8715758..a1ec93bdbd 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.576.1" +version = "1.576.2" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 76a994dada..92342b4f31 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.576.1", + "version": "1.576.2", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 871fc27f08..ef0ebfc5d4 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.576.1", + "version": "1.576.2", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 9d295502df..e0426c7b71 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.576.1 +1.576.2 From f1029d0f14f0125e3a90db4ed9de3e58868e93d3 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 15 Nov 2025 19:07:13 +0000 Subject: [PATCH 08/81] s3 endpoints improvements --- backend/ee-repo-ref.txt | 2 +- backend/windmill-common/src/s3_helpers.rs | 25 +++++++++++++++++-- .../workspaceSettings/StorageSettings.svelte | 2 ++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index f6823b4692..7d5311398e 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -31ee9d3449f05cd0328c0fcf43e2b161dee767b9 \ No newline at end of file +423c3c2d175b15b7c0f010e08f1848a7710e31be \ No newline at end of file diff --git a/backend/windmill-common/src/s3_helpers.rs b/backend/windmill-common/src/s3_helpers.rs index 899e3a2fd5..69fbaa4b29 100644 --- a/backend/windmill-common/src/s3_helpers.rs +++ b/backend/windmill-common/src/s3_helpers.rs @@ -396,6 +396,21 @@ pub struct S3Resource { pub port: Option, } +impl S3Resource { + pub fn endpoint_with_region_fallback(&self, region_fallback: Option) -> String { + if self.endpoint.is_empty() { + let final_region = if self.region.is_empty() { + region_fallback.unwrap_or_else(|| "us-east-1".to_string()) + } else { + self.region.clone() + }; + format!("s3.{}.amazonaws.com", final_region) + } else { + self.endpoint.clone() + } + } +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct AzureBlobResource { pub endpoint: Option, @@ -642,7 +657,7 @@ pub async fn build_s3_client(s3_resource_ref: &S3Resource) -> error::Result
+ +
+ + {/snippet} + +
+
+ {@render sectionHeader('Scripts', selectAllScripts, clearAllScripts)} + {#if allScripts.length > 0} + + {:else} +

No scripts available

+ {/if} +
+ +
+ {@render sectionHeader('Flows', selectAllFlows, clearAllFlows)} + {#if allFlows.length > 0} + + {:else} +

No flows available

+ {/if} +
+ +
+ {@render sectionHeader('API Endpoints', selectAllEndpoints, clearAllEndpoints)} + e.name))} + placeholder="Select endpoints" + bind:value={selectedEndpoints} + /> +
+ +
+ Selected: {selectedScripts.length} scripts, {selectedFlows.length} flows, {selectedEndpoints.length} + endpoints +
+
+ {/if} + {:else if mcpCreationMode && (newMcpScope !== 'folder' || selectedFolder.length > 0)} {#if loadingRunnables}
createToken(mcpCreationMode)} disabled={mcpCreationMode && - (newTokenWorkspace == undefined || (newMcpScope === 'folder' && !selectedFolder))} + (newTokenWorkspace == undefined || + (newMcpScope === 'folder' && !selectedFolder) || + (newMcpScope === 'custom' && + selectedScripts.length === 0 && + selectedFlows.length === 0 && + selectedEndpoints.length === 0))} variant="accent" > New token From 13216bc2a3a13733c18afd16b3b2fe3616d993e6 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 17 Nov 2025 23:00:21 +0000 Subject: [PATCH 26/81] add tracing on email receiving --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 4eee5664d7..04d1178335 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -136bcf8cdffe4de5ad2128c9b14961294dc7e20f +eddbf00e260df9e7357256eed449250c63b72dce \ No newline at end of file From 9b7527c379ec6934cff8667311cecef4e244deb1 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 17 Nov 2025 23:07:02 +0000 Subject: [PATCH 27/81] add tracing on email receiving --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 04d1178335..51bac9a355 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -eddbf00e260df9e7357256eed449250c63b72dce \ No newline at end of file +4dd9a05c122ff4a1559fc2ddf84fa3ede3efd5ca \ No newline at end of file From 499d7d4098758726a8cb2bf3e4837927b8fd70a4 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Mon, 17 Nov 2025 18:12:13 -0500 Subject: [PATCH 28/81] feat: rhel8 + fix rhel9 (#7165) --- .github/workflows/build-publish-rh8-image.yml | 140 ++++++++++++++++++ docker/RHEL8/Dockerfile | 79 ++++++++++ docker/RHEL9/Dockerfile | 2 +- 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-publish-rh8-image.yml create mode 100644 docker/RHEL8/Dockerfile diff --git a/.github/workflows/build-publish-rh8-image.yml b/.github/workflows/build-publish-rh8-image.yml new file mode 100644 index 0000000000..aabb17a592 --- /dev/null +++ b/.github/workflows/build-publish-rh8-image.yml @@ -0,0 +1,140 @@ +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +name: Build and publish windmill for RHEL8 +on: workflow_dispatch + +permissions: write-all + +jobs: + build_ee: + runs-on: ubicloud + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Read EE repo commit hash + run: | + echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV" + + - uses: actions/checkout@v4 + with: + repository: windmill-labs/windmill-ee-private + path: ./windmill-ee-private + ref: ${{ env.ee_repo_ref }} + token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }} + fetch-depth: 0 + + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v2 + - uses: depot/setup-action@v1 + + - name: Docker meta + id: meta-ee-public + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-rhel8 + flavor: | + latest=false + tags: | + type=sha + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Substitute EE code + run: | + ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private + + - name: Copy RHEL8 Dockerfile + run: | + cp ./docker/RHEL8/Dockerfile ./Dockerfile + + - name: Build and push publicly ee amd64 + uses: depot/build-push-action@v1 + with: + context: . + platforms: linux/amd64 + push: true + build-args: | + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private + secrets: | + rh_username=${{ secrets.RH_USERNAME }} + rh_password=${{ secrets.RH_PASSWORD }} + tags: | + ${{ steps.meta-ee-public.outputs.tags }}-amd64 + labels: | + ${{ steps.meta-ee-public.outputs.labels }}-amd64 + org.opencontainers.image.licenses=Windmill-Enterprise-License + + - name: Build and push publicly ee arm64 + uses: depot/build-push-action@v1 + with: + context: . + platforms: linux/arm64 + push: true + build-args: | + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private + secrets: | + rh_username=${{ secrets.RH_USERNAME }} + rh_password=${{ secrets.RH_PASSWORD }} + tags: | + ${{ steps.meta-ee-public.outputs.tags }}-arm64 + labels: | + ${{ steps.meta-ee-public.outputs.labels }}-arm64 + org.opencontainers.image.licenses=Windmill-Enterprise-License + + - uses: shrink/actions-docker-extract@v3 + id: extract-ee-amd64 + with: + image: ${{ steps.meta-ee-public.outputs.tags}}-amd64 + path: "/windmill/target/release/windmill" + + - uses: shrink/actions-docker-extract@v3 + id: extract-duckdb-ffi-internal + with: + image: ${{ steps.meta-ee-public.outputs.tags}}-amd64 + path: "/usr/src/app/libwindmill_duckdb_ffi_internal.so" + + # - uses: shrink/actions-docker-extract@v3 + # id: extract-ee-arm64 + # with: + # image: ${{ steps.meta-ee-public.outputs.tags}}-arm64 + # path: "/windmill/target/release/windmill" + + - name: Rename binary with corresponding architecture + run: | + mv "${{ steps.extract-ee-amd64.outputs.destination }}/windmill" "${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8" + # mv "${{ steps.extract-ee-arm64.outputs.destination }}/windmill" "${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel8" + + - uses: actions/upload-artifact@v4 + with: + name: RHEL8-amd64 build + path: ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8 + + - uses: actions/upload-artifact@v4 + with: + name: RHEL8-amd64 dynamic libraries build + path: ${{ steps.extract-duckdb-ffi-internal.outputs.destination }}/libwindmill_duckdb_ffi_internal.so + + # - uses: actions/upload-artifact@v4 + # with: + # name: RHEL8-arm64 build + # path: + # ${{ steps.extract-ee-arm64.outputs.destination + # }}/windmill-ee-arm64-rhel8 + + # - name: Attach binary to release + # uses: softprops/action-gh-release@v2 + # if: startsWith(github.ref, 'refs/tags/') + # with: + # files: | + # ${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel8 + # ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8 diff --git a/docker/RHEL8/Dockerfile b/docker/RHEL8/Dockerfile new file mode 100644 index 0000000000..2d4ba3fb3a --- /dev/null +++ b/docker/RHEL8/Dockerfile @@ -0,0 +1,79 @@ +ARG DEBIAN_IMAGE=debian:bookworm-slim +ARG RUST_IMAGE=registry.access.redhat.com/ubi8/ubi:latest +ARG PYTHON_IMAGE=python:3.11.10-slim-bookworm + +FROM ${RUST_IMAGE} AS rust_base + +RUN yum update -y && \ + yum install -y git openssl-devel npm nodejs rustfmt + +# Install rust manually +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef --version ^0.1 + +WORKDIR /windmill + +ENV SQLX_OFFLINE=true +# ENV CARGO_INCREMENTAL=1 + +FROM node:20-alpine as frontend + +# install dependencies +WORKDIR /frontend +COPY ./frontend/package.json ./frontend/package-lock.json ./ +RUN npm ci + +# Copy all local files into the image. +COPY frontend . +RUN mkdir /backend +COPY /backend/windmill-api/openapi.yaml /backend/windmill-api/openapi.yaml +COPY /openflow.openapi.yaml /openflow.openapi.yaml +COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.sh + +RUN cd /backend/windmill-api && . ./build_openapi.sh +COPY /backend/parsers/windmill-parser-wasm/pkg/ /backend/parsers/windmill-parser-wasm/pkg/ +COPY /typescript-client/docs/ /frontend/static/tsdocs/ + +RUN npm run generate-backend-client +ENV NODE_OPTIONS "--max-old-space-size=10240" +RUN npm run build + + +FROM rust_base AS planner + +COPY ./openflow.openapi.yaml /openflow.openapi.yaml +COPY ./backend ./ + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef prepare --recipe-path recipe.json + +FROM rust_base AS builder +ARG features="" + +COPY --from=planner /windmill/recipe.json recipe.json + +RUN --mount=type=secret,id=rh_username \ + --mount=type=secret,id=rh_password \ + subscription-manager register --username $(cat /run/secrets/rh_username) --password $(cat /run/secrets/rh_password) + +RUN subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms + +RUN yum update -y && \ + yum install -y perl-interpreter perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel clang llvm-devel cmake libtool-ltdl-devel + +# RUN --mount=type=cache,target=/usr/local/cargo/registry \ +# CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json + +COPY ./openflow.openapi.yaml /openflow.openapi.yaml +COPY ./backend ./ + +COPY --from=frontend /frontend /frontend +COPY --from=frontend /backend/windmill-api/openapi-deref.yaml ./windmill-api/openapi-deref.yaml +COPY .git/ .git/ + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features" + +RUN subscription-manager unregister diff --git a/docker/RHEL9/Dockerfile b/docker/RHEL9/Dockerfile index 7399fd690a..ace996cab5 100644 --- a/docker/RHEL9/Dockerfile +++ b/docker/RHEL9/Dockerfile @@ -61,7 +61,7 @@ RUN --mount=type=secret,id=rh_username \ RUN subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms RUN yum update -y && \ - yum install -y perl-FindBin perl-IPC-Cmd libxml2-devel xmlsec1-devel xmlsec1-openssl-devel clang llvm-devel cmake libtool-ltdl-devel + yum install -y perl-FindBin perl-IPC-Cmd perl-Time-Piece libxml2-devel xmlsec1-devel xmlsec1-openssl-devel clang llvm-devel cmake libtool-ltdl-devel # RUN --mount=type=cache,target=/usr/local/cargo/registry \ # CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json From 25c9223ba0a5b93eefd072fc4865b9249ee20b63 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 00:26:59 +0100 Subject: [PATCH 29/81] chore(main): release 1.579.0 (#7161) * chore(main): release 1.579.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 15 +++++ backend/Cargo.lock | 62 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 63 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b4a82ee4..c27a4050fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [1.579.0](https://github.com/windmill-labs/windmill/compare/v1.578.0...v1.579.0) (2025-11-17) + + +### Features + +* **ai:** handle aws bedrock as provider ([#7155](https://github.com/windmill-labs/windmill/issues/7155)) ([79ac631](https://github.com/windmill-labs/windmill/commit/79ac6312e87afa3646bddc0f7e66fc4367dbff7c)) +* **mcp:** granular token scopes for scripts, flows, and endpoints ([#7130](https://github.com/windmill-labs/windmill/issues/7130)) ([88d04b9](https://github.com/windmill-labs/windmill/commit/88d04b9cbeee98f3256b78e9d34beb930cd729ec)) +* rhel8 + fix rhel9 ([#7165](https://github.com/windmill-labs/windmill/issues/7165)) ([499d7d4](https://github.com/windmill-labs/windmill/commit/499d7d4098758726a8cb2bf3e4837927b8fd70a4)) + + +### Bug Fixes + +* **backend:** worker count in latest worker usage ([#7160](https://github.com/windmill-labs/windmill/issues/7160)) ([b87d2cc](https://github.com/windmill-labs/windmill/commit/b87d2cc64cb54b602ee599fcde7f0fd3c8931550)) +* fix custom email triggers enabled ([#7164](https://github.com/windmill-labs/windmill/issues/7164)) ([90b5569](https://github.com/windmill-labs/windmill/commit/90b5569c911f9025b0e6b5318f57705efbd9bd17)) + ## [1.578.0](https://github.com/windmill-labs/windmill/compare/v1.577.0...v1.578.0) (2025-11-17) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index d55a6487c9..76ec2ea3fb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2094,9 +2094,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" dependencies = [ "clap_builder", "clap_derive", @@ -2104,9 +2104,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" dependencies = [ "anstream", "anstyle", @@ -15188,7 +15188,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15250,7 +15250,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "argon2", @@ -15371,7 +15371,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.578.0" +version = "1.579.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15386,7 +15386,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.578.0" +version = "1.579.0" dependencies = [ "chrono", "lazy_static", @@ -15400,7 +15400,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "axum", @@ -15419,7 +15419,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "async-recursion", @@ -15507,7 +15507,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.578.0" +version = "1.579.0" dependencies = [ "regex", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "bytes", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.578.0" +version = "1.579.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.578.0" +version = "1.579.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15567,7 +15567,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "lazy_static", @@ -15579,7 +15579,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "serde_json", @@ -15591,7 +15591,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "gosyn", @@ -15603,7 +15603,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "lazy_static", @@ -15615,7 +15615,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "serde_json", @@ -15627,7 +15627,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "nu-parser", @@ -15638,7 +15638,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15661,7 +15661,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "async-recursion", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "lazy_static", @@ -15698,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15715,7 +15715,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "lazy_static", @@ -15729,7 +15729,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "lazy_static", @@ -15747,7 +15747,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "serde", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "async-recursion", @@ -15793,7 +15793,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.578.0" +version = "1.579.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15803,7 +15803,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.578.0" +version = "1.579.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b196852f58..b396ce6949 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.578.0" +version = "1.579.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.578.0" +version = "1.579.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 72d3ac3407..83651cece7 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.578.0 + version: 1.579.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index ecee3e3434..d509942ef2 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.578.0"; +export const VERSION = "v1.579.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 682fedd85f..70baafb5ce 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.578.0"; +export const VERSION = "1.579.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f404b4910e..7a7ef8928e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.578.0", + "version": "1.579.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.578.0", + "version": "1.579.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 09d94af50d..db3c55d311 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.578.0", + "version": "1.579.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 97c0e43736..8cb880680b 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.578.0" -wmill_pg = ">=1.578.0" +wmill = ">=1.579.0" +wmill_pg = ">=1.579.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 3c43d04b63..e3216466ec 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.578.0 + version: 1.579.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index f475c8d0e0..9abcc2edcf 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.578.0' + ModuleVersion = '1.579.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 92d46d3330..129fe2686d 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.578.0" +version = "1.579.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index ea146232cd..facff31609 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.578.0" +version = "1.579.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index f38e455c56..0a07acb78a 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.578.0", + "version": "1.579.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 564c31c948..294dc0b0d5 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.578.0", + "version": "1.579.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 8e8031afb2..ecf50ba8f0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.578.0 +1.579.0 From 09a6e1feaa79ce3f8548f8090fddbf46abb08b18 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 00:09:51 +0000 Subject: [PATCH 30/81] fix: fix s3 object download frontend freezes --- .../common/fileDownload/FileDownload.svelte | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/components/common/fileDownload/FileDownload.svelte b/frontend/src/lib/components/common/fileDownload/FileDownload.svelte index 8887e6b4e0..0062c171aa 100644 --- a/frontend/src/lib/components/common/fileDownload/FileDownload.svelte +++ b/frontend/src/lib/components/common/fileDownload/FileDownload.svelte @@ -12,20 +12,22 @@ let { s3object, workspaceId = undefined, appPath = undefined }: Props = $props() - - - - {s3object.storage ? `s3://${s3object.storage}/${s3object.s3}` : `s3:///${s3object.s3}`} - - + href={`${base}/api/w/${workspaceId ?? $workspaceStore}${ + appPath ? `/apps_u/download_s3_file/${appPath}` : '/job_helpers/download_s3_file' + }?${appPath ? 's3' : 'file_key'}=${encodeURIComponent(s3object?.s3 ?? '')}${ + s3object?.storage ? `&storage=${s3object.storage}` : '' + }${appPath && s3object?.presigned ? `&${s3object?.presigned}` : ''}`} + download={s3object?.s3?.split?.('/')?.pop() ?? 'unnamed_download.file'} + > + + + {s3object?.storage ? `s3://${s3object.storage}/${s3object.s3}` : `s3:///${s3object.s3}`} + + +{/if} From 610f90b19eab43fde1fed601ba6d2cc8dfd3ed62 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 01:19:08 +0100 Subject: [PATCH 31/81] chore(main): release 1.579.1 (#7166) * chore(main): release 1.579.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 54 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c27a4050fe..36009604d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.579.1](https://github.com/windmill-labs/windmill/compare/v1.579.0...v1.579.1) (2025-11-18) + + +### Bug Fixes + +* fix s3 object download frontend freezes ([09a6e1f](https://github.com/windmill-labs/windmill/commit/09a6e1feaa79ce3f8548f8090fddbf46abb08b18)) + ## [1.579.0](https://github.com/windmill-labs/windmill/compare/v1.578.0...v1.579.0) (2025-11-17) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 76ec2ea3fb..ed35b2b940 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15188,7 +15188,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -15250,7 +15250,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "argon2", @@ -15371,7 +15371,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.579.0" +version = "1.579.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15386,7 +15386,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.579.0" +version = "1.579.1" dependencies = [ "chrono", "lazy_static", @@ -15400,7 +15400,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "axum", @@ -15419,7 +15419,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "async-recursion", @@ -15507,7 +15507,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.579.0" +version = "1.579.1" dependencies = [ "regex", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "bytes", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.579.0" +version = "1.579.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.579.0" +version = "1.579.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15567,7 +15567,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "lazy_static", @@ -15579,7 +15579,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "serde_json", @@ -15591,7 +15591,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "gosyn", @@ -15603,7 +15603,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "lazy_static", @@ -15615,7 +15615,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "serde_json", @@ -15627,7 +15627,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "nu-parser", @@ -15638,7 +15638,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15661,7 +15661,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "async-recursion", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "lazy_static", @@ -15698,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15715,7 +15715,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "lazy_static", @@ -15729,7 +15729,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "lazy_static", @@ -15747,7 +15747,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "serde", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "async-recursion", @@ -15793,7 +15793,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.579.0" +version = "1.579.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15803,7 +15803,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.579.0" +version = "1.579.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b396ce6949..b1ddecc49e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.579.0" +version = "1.579.1" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.579.0" +version = "1.579.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 83651cece7..8d72610fad 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.579.0 + version: 1.579.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index d509942ef2..447826de29 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.579.0"; +export const VERSION = "v1.579.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 70baafb5ce..5a2d934040 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.579.0"; +export const VERSION = "1.579.1"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7a7ef8928e..988f9a52bb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.579.0", + "version": "1.579.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.579.0", + "version": "1.579.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index db3c55d311..d424efa23c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.579.0", + "version": "1.579.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 8cb880680b..30561cf3ee 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.579.0" -wmill_pg = ">=1.579.0" +wmill = ">=1.579.1" +wmill_pg = ">=1.579.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index e3216466ec..58f44122bb 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.579.0 + version: 1.579.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 9abcc2edcf..1469a4fac8 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.579.0' + ModuleVersion = '1.579.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 129fe2686d..2dc4ab23ed 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.579.0" +version = "1.579.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index facff31609..d55947ade4 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.579.0" +version = "1.579.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 0a07acb78a..bc8e1103a2 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.579.0", + "version": "1.579.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 294dc0b0d5..4332c4eb89 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.579.0", + "version": "1.579.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index ecf50ba8f0..47529062aa 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.579.0 +1.579.1 From 4e9c22a0bdb59a50af2f6bc7614f594dbdc23566 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Mon, 17 Nov 2025 19:37:52 -0500 Subject: [PATCH 32/81] rhel duckdb lib (#7167) * feat: rhel8 + fix rhel9 * duckdb lib --- docker/RHEL8/Dockerfile | 6 ++++++ docker/RHEL9/Dockerfile | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docker/RHEL8/Dockerfile b/docker/RHEL8/Dockerfile index 2d4ba3fb3a..453d5bf3db 100644 --- a/docker/RHEL8/Dockerfile +++ b/docker/RHEL8/Dockerfile @@ -76,4 +76,10 @@ COPY .git/ .git/ RUN --mount=type=cache,target=/usr/local/cargo/registry \ CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features" +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p windmill_duckdb_ffi_internal + +RUN mkdir -p /usr/src/app && \ + cp /windmill/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ + RUN subscription-manager unregister diff --git a/docker/RHEL9/Dockerfile b/docker/RHEL9/Dockerfile index ace996cab5..6c0ef21293 100644 --- a/docker/RHEL9/Dockerfile +++ b/docker/RHEL9/Dockerfile @@ -76,4 +76,10 @@ COPY .git/ .git/ RUN --mount=type=cache,target=/usr/local/cargo/registry \ CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features" +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p windmill_duckdb_ffi_internal + +RUN mkdir -p /usr/src/app && \ + cp /windmill/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ + RUN subscription-manager unregister From d08c0916f72a67f01e0c4475f03f9d1d33c10905 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 00:48:50 +0000 Subject: [PATCH 33/81] fix: ducklake manager table explorer issue --- frontend/src/lib/components/dbOps.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/dbOps.ts b/frontend/src/lib/components/dbOps.ts index 3a9e413d7b..fa1d265dbb 100644 --- a/frontend/src/lib/components/dbOps.ts +++ b/frontend/src/lib/components/dbOps.ts @@ -179,11 +179,11 @@ export async function getDucklakeSchema({ args: {} } }) - const stringified = Array.isArray(result) && result.length && (result?.[0]?.['result'] ?? '[]') + const mainSchema = Array.isArray(result) && result.length && (result?.[0]?.['result'] ?? '[]') - if (!stringified) throw new Error('Failed to get Ducklake schema: ' + JSON.stringify(result)) + if (!mainSchema) throw new Error('Failed to get Ducklake schema: ' + JSON.stringify(result)) let schema: Omit = { - schema: { main: JSON.parse(stringified) }, + schema: { main: mainSchema }, publicOnly: true, lang: 'ducklake' } From aad43768d43dd6bc8cea7ae8df8e6a33fe47ebfd Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 01:58:17 +0100 Subject: [PATCH 34/81] chore(main): release 1.579.2 (#7168) * chore(main): release 1.579.2 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 54 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36009604d8..1fd328ff7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.579.2](https://github.com/windmill-labs/windmill/compare/v1.579.1...v1.579.2) (2025-11-18) + + +### Bug Fixes + +* ducklake manager table explorer issue ([d08c091](https://github.com/windmill-labs/windmill/commit/d08c0916f72a67f01e0c4475f03f9d1d33c10905)) + ## [1.579.1](https://github.com/windmill-labs/windmill/compare/v1.579.0...v1.579.1) (2025-11-18) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index ed35b2b940..f535756953 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15188,7 +15188,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "aws-sdk-config", @@ -15250,7 +15250,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "argon2", @@ -15371,7 +15371,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.579.1" +version = "1.579.2" dependencies = [ "base64 0.22.1", "chrono", @@ -15386,7 +15386,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.579.1" +version = "1.579.2" dependencies = [ "chrono", "lazy_static", @@ -15400,7 +15400,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "axum", @@ -15419,7 +15419,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "async-recursion", @@ -15507,7 +15507,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.579.1" +version = "1.579.2" dependencies = [ "regex", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "bytes", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.579.1" +version = "1.579.2" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.579.1" +version = "1.579.2" dependencies = [ "convert_case 0.6.0", "serde", @@ -15567,7 +15567,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "lazy_static", @@ -15579,7 +15579,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "serde_json", @@ -15591,7 +15591,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "gosyn", @@ -15603,7 +15603,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "lazy_static", @@ -15615,7 +15615,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "serde_json", @@ -15627,7 +15627,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "nu-parser", @@ -15638,7 +15638,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15661,7 +15661,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "async-recursion", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "lazy_static", @@ -15698,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15715,7 +15715,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "lazy_static", @@ -15729,7 +15729,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "lazy_static", @@ -15747,7 +15747,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "serde", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "async-recursion", @@ -15793,7 +15793,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.579.1" +version = "1.579.2" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15803,7 +15803,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.579.1" +version = "1.579.2" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b1ddecc49e..39f6d688f7 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.579.1" +version = "1.579.2" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.579.1" +version = "1.579.2" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 8d72610fad..928e5d007a 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.579.1 + version: 1.579.2 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 447826de29..f03e8dc8d8 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.579.1"; +export const VERSION = "v1.579.2"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 5a2d934040..2dd914a174 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.579.1"; +export const VERSION = "1.579.2"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 988f9a52bb..3aa6091a03 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.579.1", + "version": "1.579.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.579.1", + "version": "1.579.2", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index d424efa23c..2cafa2f164 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.579.1", + "version": "1.579.2", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 30561cf3ee..83039a03a0 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.579.1" -wmill_pg = ">=1.579.1" +wmill = ">=1.579.2" +wmill_pg = ">=1.579.2" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 58f44122bb..767ac52c39 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.579.1 + version: 1.579.2 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 1469a4fac8..c9bf412999 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.579.1' + ModuleVersion = '1.579.2' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 2dc4ab23ed..1176f685b3 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.579.1" +version = "1.579.2" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index d55947ade4..6eee2cdc7c 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.579.1" +version = "1.579.2" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index bc8e1103a2..b2486f5590 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.579.1", + "version": "1.579.2", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 4332c4eb89..58620fb9f4 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.579.1", + "version": "1.579.2", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 47529062aa..70d301e6a1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.579.1 +1.579.2 From 423ed04cb928ef845f767ad4c206d768b03c9f0e Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 01:34:32 +0000 Subject: [PATCH 35/81] irsa attempts --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 51bac9a355..e83596a378 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -4dd9a05c122ff4a1559fc2ddf84fa3ede3efd5ca \ No newline at end of file +72d263f41c59a5bcbdf8c49238a3123d6e49c612 \ No newline at end of file From 2058f27e03468d45813f340b8563f935ca2142f4 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 01:37:18 +0000 Subject: [PATCH 36/81] fix: support IRSA for duckdb s3 proxy --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index e83596a378..f885c61364 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -72d263f41c59a5bcbdf8c49238a3123d6e49c612 \ No newline at end of file +785ec89a5fb08d62b5ef59d1860089ed1266ae8b \ No newline at end of file From f371fbeb9bb0946bd29a6413ee7ede75dedda5d9 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 08:12:54 +0000 Subject: [PATCH 37/81] fix: improve delete to handle ai chat --- ...0fb02e7ffea83ae94a670b598b6dada0b3d0914629.json | 14 ++++++++++++++ ...ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352.json | 14 ++++++++++++++ ...04ff3153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- backend/windmill-api/src/workspaces_extra.rs | 11 +++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 backend/.sqlx/query-08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629.json create mode 100644 backend/.sqlx/query-56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352.json diff --git a/backend/.sqlx/query-08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629.json b/backend/.sqlx/query-08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629.json new file mode 100644 index 0000000000..12d5a410f8 --- /dev/null +++ b/backend/.sqlx/query-08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM flow_conversation WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629" +} diff --git a/backend/.sqlx/query-56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352.json b/backend/.sqlx/query-56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352.json new file mode 100644 index 0000000000..7be13f4746 --- /dev/null +++ b/backend/.sqlx/query-56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM ai_agent_memory WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352" +} diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 36ddb8ab9f..713ccb9dd3 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - true + null ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/windmill-api/src/workspaces_extra.rs b/backend/windmill-api/src/workspaces_extra.rs index 2df23f3ce9..ca4f9db6a7 100644 --- a/backend/windmill-api/src/workspaces_extra.rs +++ b/backend/windmill-api/src/workspaces_extra.rs @@ -448,6 +448,17 @@ pub(crate) async fn delete_workspace( require_super_admin(&db, &authed.email).await?; } + sqlx::query!("DELETE FROM ai_agent_memory WHERE workspace_id = $1", &w_id) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "DELETE FROM flow_conversation WHERE workspace_id = $1", + &w_id + ) + .execute(&mut *tx) + .await?; + sqlx::query!("DELETE FROM workspace_env WHERE workspace_id = $1", &w_id) .execute(&mut *tx) .await?; From b112c218db86f0161ac24fe5a5ec877ad3182188 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 08:44:32 +0000 Subject: [PATCH 38/81] nit test --- .github/workflows/backend-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 366e6bd27d..e55cf5c004 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -80,7 +80,7 @@ jobs: cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. && SQLX_OFFLINE=true DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill - DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never + DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never CARGO_NET_GIT_FETCH_WITH_CLI=true DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,license,python,duckdb,rust,scoped_cache,private --all -- From 478e19379fa75e57ca1fe5931af73f04a93ce809 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 09:40:57 +0000 Subject: [PATCH 39/81] buffer cloud hosted usage --- backend/Cargo.lock | 2 + backend/Cargo.toml | 1 + backend/windmill-api/src/lib.rs | 9 +- backend/windmill-queue/Cargo.toml | 2 + backend/windmill-queue/src/cloud_usage.rs | 203 ++++++++++++++++++++++ backend/windmill-queue/src/jobs.rs | 45 +---- backend/windmill-queue/src/lib.rs | 5 + 7 files changed, 221 insertions(+), 46 deletions(-) create mode 100644 backend/windmill-queue/src/cloud_usage.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f535756953..2ea6c7fb7b 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15767,12 +15767,14 @@ dependencies = [ "chrono", "chrono-tz", "cron", + "dashmap 6.1.0", "futures", "futures-core", "hex", "hmac", "itertools 0.14.0", "lazy_static", + "once_cell", "prometheus", "quick_cache", "regex", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 39f6d688f7..6a5197d215 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -332,6 +332,7 @@ dyn-iter = "0.2.0" rsa = "^0" async_zip = { version = "0.0.17", features = ["tokio", "tokio-fs", "deflate", "chrono"] } once_cell = "1.17.1" +dashmap = "6.1.0" gosyn = "0.2.6" bytes = "1.4.0" gethostname = "0.4.3" diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 3a83b11ce8..96bf0ed085 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -75,12 +75,12 @@ pub mod agent_workers_ee; #[cfg(feature = "agent_worker_server")] mod agent_workers_oss; mod ai; -mod bedrock; mod apps; pub mod args; mod assets; mod audit; pub mod auth; +mod bedrock; mod capture; mod concurrency_groups; mod configs; @@ -155,6 +155,7 @@ mod smtp_server_oss; pub mod teams_approvals_ee; mod teams_approvals_oss; +mod public_app_layer; mod static_assets; #[cfg(all(feature = "stripe", feature = "enterprise", feature = "private"))] pub mod stripe_ee; @@ -184,7 +185,6 @@ pub mod workspaces_ee; mod workspaces_export; mod workspaces_extra; mod workspaces_oss; -mod public_app_layer; #[cfg(feature = "mcp")] mod mcp; @@ -325,6 +325,11 @@ pub async fn run_server( #[cfg(feature = "embedding")] load_embeddings_db(&db); + #[cfg(feature = "cloud")] + if *CLOUD_HOSTED { + windmill_queue::init_usage_buffer(db.clone()); + } + let mut start_smtp_server = false; if let Some(smtp_settings) = load_value_from_global_settings(&db, EMAIL_DOMAIN_SETTING).await? diff --git a/backend/windmill-queue/Cargo.toml b/backend/windmill-queue/Cargo.toml index 6349aee84e..16178502fe 100644 --- a/backend/windmill-queue/Cargo.toml +++ b/backend/windmill-queue/Cargo.toml @@ -47,3 +47,5 @@ regex.workspace = true backon.workspace = true quick_cache.workspace = true thiserror.workspace = true +dashmap.workspace = true +once_cell.workspace = true diff --git a/backend/windmill-queue/src/cloud_usage.rs b/backend/windmill-queue/src/cloud_usage.rs new file mode 100644 index 0000000000..7ded6977ef --- /dev/null +++ b/backend/windmill-queue/src/cloud_usage.rs @@ -0,0 +1,203 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2022 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +use chrono::Datelike; +use dashmap::DashMap; +use sqlx::{Pool, Postgres}; +use std::sync::Arc; +use tokio::sync::Notify; + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +struct UsageKey { + id: String, + is_workspace: bool, + month: i32, +} + +pub struct UsageBuffer { + buffer: Arc>, + db: Pool, + shutdown_notify: Arc, +} + +impl UsageBuffer { + pub fn new(db: Pool) -> Arc { + let buffer = Arc::new(Self { + buffer: Arc::new(DashMap::new()), + db, + shutdown_notify: Arc::new(Notify::new()), + }); + + // Spawn the periodic flush task + let buffer_clone = buffer.clone(); + tokio::spawn(async move { + buffer_clone.flush_loop().await; + }); + + buffer + } + + pub fn increment(&self, workspace_id: String, email: Option) { + let month = Self::current_month(); + + // Increment workspace usage + self.buffer + .entry(UsageKey { id: workspace_id, is_workspace: true, month }) + .and_modify(|counter| *counter += 1) + .or_insert(1); + + // Increment user usage if email is provided + if let Some(email) = email { + self.buffer + .entry(UsageKey { id: email, is_workspace: false, month }) + .and_modify(|counter| *counter += 1) + .or_insert(1); + } + } + + async fn flush_loop(&self) { + let mut interval = tokio::time::interval(std::time::Duration::from_secs(60)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + tokio::select! { + _ = interval.tick() => { + self.flush().await; + } + _ = self.shutdown_notify.notified() => { + // Final flush on shutdown + self.flush().await; + break; + } + } + } + } + + async fn flush(&self) { + if self.buffer.is_empty() { + return; + } + + // Drain all buffered usage counts + let mut to_flush = Vec::new(); + self.buffer.retain(|key, value| { + to_flush.push((key.clone(), *value)); + false + }); + + if to_flush.is_empty() { + return; + } + + tracing::debug!( + "Flushing {} buffered usage entries to database", + to_flush.len() + ); + + // Batch update to database + for (key, count) in to_flush { + let result = tokio::time::timeout( + std::time::Duration::from_secs(10), + sqlx::query!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, $2, $3, $4) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $4", + &key.id, + key.is_workspace, + key.month, + count + ) + .execute(&self.db), + ) + .await; + + match result { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + tracing::error!( + "Failed to flush usage for {} (is_workspace: {}): {:#}", + key.id, + key.is_workspace, + e + ); + } + Err(_) => { + tracing::error!( + "Usage flush timed out for {} (is_workspace: {})", + key.id, + key.is_workspace + ); + } + } + } + } + + fn current_month() -> i32 { + let now = chrono::Utc::now(); + (now.year() * 12 + now.month() as i32) as i32 + } +} + +lazy_static::lazy_static! { + static ref USAGE_BUFFER: once_cell::sync::OnceCell> = once_cell::sync::OnceCell::new(); +} + +pub fn init_usage_buffer(db: Pool) { + USAGE_BUFFER.get_or_init(|| UsageBuffer::new(db)); +} + +pub fn increment_usage_async(db: Pool, workspace_id: String, email: Option) { + if let Some(buffer) = USAGE_BUFFER.get() { + buffer.increment(workspace_id, email); + } else { + tracing::warn!("Usage buffer not initialized, falling back to direct database update"); + // Fallback to old implementation if buffer not initialized + tokio::task::spawn(async move { + let result = tokio::time::timeout(std::time::Duration::from_secs(10), async { + // Update workspace usage + let workspace_result = sqlx::query!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", + &workspace_id + ) + .execute(&db) + .await; + + if let Err(e) = workspace_result { + tracing::error!("Failed to update workspace usage for {}: {:#}", workspace_id, e); + } + + // Update user usage if email is provided (non-premium workspaces only) + if let Some(ref email) = email { + let user_result = sqlx::query!( + "INSERT INTO usage (id, is_workspace, month_, usage) + VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", + email + ) + .execute(&db) + .await; + + if let Err(e) = user_result { + tracing::error!("Failed to update user usage for {}: {:#}", email, e); + } + } + }) + .await; + + if let Err(_) = result { + tracing::error!( + "Usage update timed out after 10s for workspace {} and email {:?}", + workspace_id, + email + ); + } + }); + } +} diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 7783751944..c050d56659 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -3809,50 +3809,7 @@ async fn check_usage_limits( } #[cfg(feature = "cloud")] -fn increment_usage_async(db: Pool, workspace_id: String, email: Option) { - tokio::task::spawn(async move { - let result = tokio::time::timeout(std::time::Duration::from_secs(10), async { - // Update workspace usage - let workspace_result = sqlx::query!( - "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", - &workspace_id - ) - .execute(&db) - .await; - - if let Err(e) = workspace_result { - tracing::error!("Failed to update workspace usage for {}: {:#}", workspace_id, e); - } - - // Update user usage if email is provided (non-premium workspaces only) - if let Some(ref email) = email { - let user_result = sqlx::query!( - "INSERT INTO usage (id, is_workspace, month_, usage) - VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", - email - ) - .execute(&db) - .await; - - if let Err(e) = user_result { - tracing::error!("Failed to update user usage for {}: {:#}", email, e); - } - } - }) - .await; - - if let Err(_) = result { - tracing::error!( - "Usage update timed out after 10s for workspace {} and email {:?}", - workspace_id, - email - ); - } - }); -} +use crate::cloud_usage::increment_usage_async; // #[instrument(level = "trace", skip_all)] pub async fn push<'c, 'd>( diff --git a/backend/windmill-queue/src/lib.rs b/backend/windmill-queue/src/lib.rs index 9b6025e515..3ea2e2c1aa 100644 --- a/backend/windmill-queue/src/lib.rs +++ b/backend/windmill-queue/src/lib.rs @@ -14,3 +14,8 @@ pub mod schedule; pub use jobs::*; pub mod flow_status; pub mod tags; + +#[cfg(feature = "cloud")] +pub mod cloud_usage; +#[cfg(feature = "cloud")] +pub use cloud_usage::init_usage_buffer; From c3e59fe064fc3b9d4c05958eea54601ff3410899 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 10:45:45 +0100 Subject: [PATCH 40/81] fix: change uv tool dir from /root to /usr/local/uv --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index b3f18b4e17..7aeb2b218a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -114,7 +114,10 @@ ARG WITH_GIT=true ARG LATEST_STABLE_PY=3.11.10 ENV UV_PYTHON_INSTALL_DIR=/tmp/windmill/cache/py_runtime ENV UV_PYTHON_PREFERENCE=only-managed + +RUN mkdir -p /usr/local/uv ENV UV_TOOL_BIN_DIR=/usr/local/bin +ENV UV_TOOL_DIR=/usr/local/uv ENV PATH /usr/local/bin:/root/.local/bin:$PATH From 64a9c4f7d21e6a2ac0893d4f3a7b143de3e5f481 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 12:12:24 +0000 Subject: [PATCH 41/81] sqlx --- ...1d984ff5ce7086ac511e8647e2024d9dbe0af56.json | 14 -------------- ...1c311afe67d339ca022ba61c2767c004b038ef0.json | 14 -------------- ...81b525fc3ec54fdbca5d12647c1da88c9983b10.json | 14 ++++++++++++++ ...ccb5cb0c7146c11adae8c287c0c9e71ac68f7a7.json | 14 ++++++++++++++ ...01a5f235f39558dee88cbb486d7ac988d0a44d1.json | 17 +++++++++++++++++ 5 files changed, 45 insertions(+), 28 deletions(-) delete mode 100644 backend/.sqlx/query-045b26db0cefe6eaac0e572661d984ff5ce7086ac511e8647e2024d9dbe0af56.json delete mode 100644 backend/.sqlx/query-42baae3c69367bbb21771a9711c311afe67d339ca022ba61c2767c004b038ef0.json create mode 100644 backend/.sqlx/query-6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10.json create mode 100644 backend/.sqlx/query-88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7.json create mode 100644 backend/.sqlx/query-cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1.json diff --git a/backend/.sqlx/query-045b26db0cefe6eaac0e572661d984ff5ce7086ac511e8647e2024d9dbe0af56.json b/backend/.sqlx/query-045b26db0cefe6eaac0e572661d984ff5ce7086ac511e8647e2024d9dbe0af56.json deleted file mode 100644 index 9affa78033..0000000000 --- a/backend/.sqlx/query-045b26db0cefe6eaac0e572661d984ff5ce7086ac511e8647e2024d9dbe0af56.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "045b26db0cefe6eaac0e572661d984ff5ce7086ac511e8647e2024d9dbe0af56" -} diff --git a/backend/.sqlx/query-42baae3c69367bbb21771a9711c311afe67d339ca022ba61c2767c004b038ef0.json b/backend/.sqlx/query-42baae3c69367bbb21771a9711c311afe67d339ca022ba61c2767c004b038ef0.json deleted file mode 100644 index de5b99791f..0000000000 --- a/backend/.sqlx/query-42baae3c69367bbb21771a9711c311afe67d339ca022ba61c2767c004b038ef0.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "42baae3c69367bbb21771a9711c311afe67d339ca022ba61c2767c004b038ef0" -} diff --git a/backend/.sqlx/query-6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10.json b/backend/.sqlx/query-6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10.json new file mode 100644 index 0000000000..1059fac898 --- /dev/null +++ b/backend/.sqlx/query-6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10" +} diff --git a/backend/.sqlx/query-88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7.json b/backend/.sqlx/query-88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7.json new file mode 100644 index 0000000000..b664b2a91a --- /dev/null +++ b/backend/.sqlx/query-88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7" +} diff --git a/backend/.sqlx/query-cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1.json b/backend/.sqlx/query-cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1.json new file mode 100644 index 0000000000..9e4ee46248 --- /dev/null +++ b/backend/.sqlx/query-cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, $2, $3, $4)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $4", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Bool", + "Int4", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1" +} From f3e62a2e0f9847c770546196c37431494a7d4225 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 12:29:24 +0000 Subject: [PATCH 42/81] test fix --- .github/workflows/backend-test.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index e55cf5c004..748f5add26 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -76,12 +76,14 @@ jobs: ${{ runner.os }}-duckdb-ffi- - name: cargo test timeout-minutes: 16 - run: deno --version && bun -v && go version && python3 --version && - cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. && - SQLX_OFFLINE=true - DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill - DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never CARGO_NET_GIT_FETCH_WITH_CLI=true - DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) - UV_PATH=$(which uv) cargo test --features - enterprise,deno_core,license,python,duckdb,rust,scoped_cache,private --all -- - --nocapture + env: + SQLX_OFFLINE: true + DATABASE_URL: postgres://postgres:changeme@localhost:5432/windmill + DISABLE_EMBEDDING: true + RUST_LOG: info + RUST_LOG_STYLE: never + CARGO_NET_GIT_FETCH_WITH_CLI: true + run: | + deno --version && bun -v && go version && python3 --version + cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. + DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,license,python,duckdb,rust,scoped_cache,private --all -- --nocapture From a3cf674cd0adc278b39e9df9762223b055df2395 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Nov 2025 12:59:00 +0000 Subject: [PATCH 43/81] test fix --- backend/.cargo/config.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/.cargo/config.toml b/backend/.cargo/config.toml index 5babef3f8a..234ac9a50f 100644 --- a/backend/.cargo/config.toml +++ b/backend/.cargo/config.toml @@ -13,4 +13,7 @@ rustflags = [ "-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup", "-C", "link-args=-Wl,-rpath,$ORIGIN/" -] \ No newline at end of file +] + +[net] +git-fetch-with-cli = true \ No newline at end of file From 4acd5e526fd25d6f7a95e2836851cc5e3fcc5477 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Tue, 18 Nov 2025 10:44:23 -0500 Subject: [PATCH 44/81] fix rhel9 add rhel8 ci (#7172) --- docker/RHEL8/Dockerfile | 5 +++-- docker/RHEL9/Dockerfile | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docker/RHEL8/Dockerfile b/docker/RHEL8/Dockerfile index 453d5bf3db..c8c4fa0588 100644 --- a/docker/RHEL8/Dockerfile +++ b/docker/RHEL8/Dockerfile @@ -77,9 +77,10 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features" RUN --mount=type=cache,target=/usr/local/cargo/registry \ - CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p windmill_duckdb_ffi_internal + cd windmill-duckdb-ffi-internal && \ + CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release RUN mkdir -p /usr/src/app && \ - cp /windmill/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ + cp windmill-duckdb-ffi-internal/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ RUN subscription-manager unregister diff --git a/docker/RHEL9/Dockerfile b/docker/RHEL9/Dockerfile index 6c0ef21293..098091ef17 100644 --- a/docker/RHEL9/Dockerfile +++ b/docker/RHEL9/Dockerfile @@ -77,9 +77,10 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features" RUN --mount=type=cache,target=/usr/local/cargo/registry \ - CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p windmill_duckdb_ffi_internal + cd windmill-duckdb-ffi-internal && \ + CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release RUN mkdir -p /usr/src/app && \ - cp /windmill/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ + cp windmill-duckdb-ffi-internal/target/release/libwindmill_duckdb_ffi_internal.so /usr/src/app/ RUN subscription-manager unregister From 55482210921fe2eb0fd158abd4f7369495f2dfd7 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Tue, 18 Nov 2025 18:02:04 -0500 Subject: [PATCH 45/81] feat: support secondary promotion repos in git sync settings (#7173) --- .../git_sync/GitSyncContext.svelte.ts | 4 +- .../git_sync/GitSyncRepositoryCard.svelte | 17 +++- .../components/git_sync/GitSyncSection.svelte | 91 +++++++++++++------ 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/frontend/src/lib/components/git_sync/GitSyncContext.svelte.ts b/frontend/src/lib/components/git_sync/GitSyncContext.svelte.ts index c23ccf590e..33c2f2fa5c 100644 --- a/frontend/src/lib/components/git_sync/GitSyncContext.svelte.ts +++ b/frontend/src/lib/components/git_sync/GitSyncContext.svelte.ts @@ -595,7 +595,7 @@ export function createGitSyncContext(workspace: string) { return result } - function getLegacyPromotionRepositories(): { repo: GitSyncRepository, idx: number }[] { + function getSecondaryPromotionRepositories(): { repo: GitSyncRepository, idx: number }[] { const result: { repo: GitSyncRepository, idx: number }[] = [] let foundFirst = false repositories.forEach((repo, idx) => { @@ -736,7 +736,7 @@ export function createGitSyncContext(workspace: string) { getPrimarySyncRepository, getPrimaryPromotionRepository, getSecondarySyncRepositories, - getLegacyPromotionRepositories, + getSecondaryPromotionRepositories, // Helper methods getTargetBranch, diff --git a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte index 85c19362d7..c5700622d4 100644 --- a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte +++ b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte @@ -94,10 +94,19 @@ : isLegacy ? 'Legacy promotion repository' : isSecondary - ? 'Secondary sync repository' + ? repo?.use_individual_branch + ? 'Secondary promotion repository' + : 'Secondary sync repository' : `Repository #${(idx ?? 0) + 1}` ) + // Determine the actual mode based on repository configuration + const repoMode = $derived<'sync' | 'promotion'>( + variant === 'primary-promotion' || variant === 'legacy' || repo?.use_individual_branch + ? 'promotion' + : 'sync' + ) + // Determine display description based on variant and mode const displayDescription = $derived( variant === 'primary-sync' || variant === 'primary-promotion' @@ -353,7 +362,7 @@ {#if repo.isUnsavedConnection && !emptyString(repo.git_repo_resource_path) && idx !== null} {:else}
diff --git a/frontend/src/lib/components/git_sync/GitSyncSection.svelte b/frontend/src/lib/components/git_sync/GitSyncSection.svelte index 41269a82bd..52b4b4326d 100644 --- a/frontend/src/lib/components/git_sync/GitSyncSection.svelte +++ b/frontend/src/lib/components/git_sync/GitSyncSection.svelte @@ -30,14 +30,15 @@ const primarySync = $derived(gitSyncContext?.getPrimarySyncRepository() || null) const primaryPromotion = $derived(gitSyncContext?.getPrimaryPromotionRepository() || null) const secondarySync = $derived(gitSyncContext?.getSecondarySyncRepositories() || []) - const legacyPromotion = $derived(gitSyncContext?.getLegacyPromotionRepositories() || []) + const secondaryPromotion = $derived(gitSyncContext?.getSecondaryPromotionRepositories() || []) // State for collapsible sections let secondarySyncExpanded = $state(false) - let legacyPromotionExpanded = $state(false) + let secondaryPromotionExpanded = $state(false) // Check if any secondary repositories are unsaved const hasUnsavedSecondary = $derived(secondarySync.some((s) => s.repo.isUnsavedConnection)) + const hasUnsavedSecondaryPromotion = $derived(secondaryPromotion.some((s) => s.repo.isUnsavedConnection)) {#if !gitSyncContext} @@ -169,38 +170,70 @@ isCollapsible={false} showEmptyState={primaryPromotion?.repo === null} /> -
- - {#if legacyPromotion.length > 0} - - Multiple promotion repositories are no longer supported. Please reduce to a single - promotion repository. Only deletion is allowed for the additional repositories below. - -
- + + {#if primaryPromotion && !primaryPromotion.repo?.isUnsavedConnection} + {#if secondaryPromotion.length > 0 || secondaryPromotionExpanded} +
+ - {#if legacyPromotionExpanded} -
- {#each legacyPromotion as { repo, idx } (repo.git_repo_resource_path)} -
- + {#if secondaryPromotionExpanded} +
+ {#if secondaryPromotion.length === 0} +
+ No secondary promotion repositories configured +
+ {:else} + {#each secondaryPromotion as { repo, idx } (repo.git_repo_resource_path)} +
+ +
+ {/each} + {/if} + + {#if !hasUnsavedSecondaryPromotion} +
+ +
+ {/if}
- {/each} + {/if}
+ {:else} + + {#if !hasUnsavedSecondaryPromotion} +
+ +
+ {/if} {/if} -
- {/if} + {/if} +
From f2dbf6d20d76d25cfa499860366d1ed0360c9205 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 19 Nov 2025 00:03:35 +0100 Subject: [PATCH 46/81] count external jwts for telemetry * feat: count external jwts * nits --- ...9468e01c4bee4cd755322eb2a83353a279a2b.json | 14 +++++++++++++ ...02c8adee995eac05b5051e480f0a20fa6b7bb.json | 20 +++++++++++++++++++ backend/ee-repo-ref.txt | 2 +- .../20251118190643_unique_jwt_token.down.sql | 3 +++ .../20251118190643_unique_jwt_token.up.sql | 9 +++++++++ backend/windmill-api/src/auth.rs | 1 + backend/windmill-api/src/ee_oss.rs | 1 + backend/windmill-common/src/auth.rs | 15 ++++++++++++++ backend/windmill-common/src/utils.rs | 6 ++++++ 9 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json create mode 100644 backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json create mode 100644 backend/migrations/20251118190643_unique_jwt_token.down.sql create mode 100644 backend/migrations/20251118190643_unique_jwt_token.up.sql diff --git a/backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json b/backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json new file mode 100644 index 0000000000..a35e6560e3 --- /dev/null +++ b/backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO unique_ext_jwt_token (jwt_hash, last_used_at)\n VALUES ($1, NOW())\n ON CONFLICT (jwt_hash)\n DO UPDATE SET last_used_at = NOW()", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + }, + "hash": "778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b" +} diff --git a/backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json b/backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json new file mode 100644 index 0000000000..fda5b760b3 --- /dev/null +++ b/backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM unique_ext_jwt_token WHERE last_used_at > NOW() - INTERVAL '30 days'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb" +} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index f885c61364..7dd5e2c77a 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -785ec89a5fb08d62b5ef59d1860089ed1266ae8b \ No newline at end of file +6694dfbc62ff69570743f028aa19c543ae846e6e \ No newline at end of file diff --git a/backend/migrations/20251118190643_unique_jwt_token.down.sql b/backend/migrations/20251118190643_unique_jwt_token.down.sql new file mode 100644 index 0000000000..8e1dca2f24 --- /dev/null +++ b/backend/migrations/20251118190643_unique_jwt_token.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here + +DROP TABLE IF EXISTS unique_ext_jwt_token; diff --git a/backend/migrations/20251118190643_unique_jwt_token.up.sql b/backend/migrations/20251118190643_unique_jwt_token.up.sql new file mode 100644 index 0000000000..bf7613c35d --- /dev/null +++ b/backend/migrations/20251118190643_unique_jwt_token.up.sql @@ -0,0 +1,9 @@ +-- Add up migration script here + +CREATE TABLE IF NOT EXISTS unique_ext_jwt_token ( + jwt_hash BIGINT PRIMARY KEY NOT NULL, + last_used_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_unique_ext_jwt_token_last_used_at ON unique_ext_jwt_token(last_used_at); + diff --git a/backend/windmill-api/src/auth.rs b/backend/windmill-api/src/auth.rs index 0765f701f9..7f2ccc6876 100644 --- a/backend/windmill-api/src/auth.rs +++ b/backend/windmill-api/src/auth.rs @@ -90,6 +90,7 @@ impl AuthCache { w_id.as_ref(), token.trim_start_matches("jwt_ext_"), self.ext_jwks.clone(), + &self.db, ) .await { diff --git a/backend/windmill-api/src/ee_oss.rs b/backend/windmill-api/src/ee_oss.rs index 30756af8ab..24eea5ab85 100644 --- a/backend/windmill-api/src/ee_oss.rs +++ b/backend/windmill-api/src/ee_oss.rs @@ -26,6 +26,7 @@ pub async fn jwt_ext_auth( _w_id: Option<&String>, _token: &str, _external_jwks: Option>>, + _db: &crate::db::DB, ) -> anyhow::Result<(crate::db::ApiAuthed, usize)> { // Implementation is not open source diff --git a/backend/windmill-common/src/auth.rs b/backend/windmill-common/src/auth.rs index e48cf3728e..89d4831e38 100644 --- a/backend/windmill-common/src/auth.rs +++ b/backend/windmill-common/src/auth.rs @@ -158,6 +158,21 @@ impl JWTAuthClaims { .as_ref() .is_some_and(|token_w_ids| token_w_ids.iter().any(|token_w_id| w_id == token_w_id)) } + + pub fn compute_ext_jwt_hash(&self) -> i64 { + let mut hasher = DefaultHasher::new(); + self.email.hash(&mut hasher); + self.username.hash(&mut hasher); + self.is_admin.hash(&mut hasher); + self.is_operator.hash(&mut hasher); + self.groups.hash(&mut hasher); + self.folders.hash(&mut hasher); + self.workspace_id.hash(&mut hasher); + self.workspace_ids.hash(&mut hasher); + self.label.hash(&mut hasher); + self.scopes.hash(&mut hasher); + hasher.finish() as i64 + } } #[derive(Deserialize, Debug)] diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 174d3cd261..623486d19c 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -947,3 +947,9 @@ pub struct ExpiringCacheEntry { pub value: T, pub expiry: std::time::Instant, } + +impl ExpiringCacheEntry { + pub fn is_expired(&self) -> bool { + self.expiry < std::time::Instant::now() + } +} From 8ae266b6a9ced16e1b7416cfc8bea5fe7a7af042 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 19 Nov 2025 00:04:12 +0100 Subject: [PATCH 47/81] feat: disabling/enabling email triggers (#7171) Co-authored-by: Ruben Fiszel --- ...010d4476d5c222184f09e5a189975a37b941.json} | 4 +-- ...8a186fc91f94d4fcc40eeb9914084e4ef60c.json} | 7 ++-- ...d6994d75c6b9e5f61daf54d298a2fdcfd9af.json} | 5 +-- ...bdf5b5feac9c6fa9de38bd24104bac8539d2.json} | 5 +-- ...5ae228697602e3769c479582470dc0b9488b.json} | 4 +-- ...a80eeac12c6ca07a0e8950941ba796b0ba79.json} | 5 +-- backend/ee-repo-ref.txt | 2 +- backend/windmill-api/openapi.yaml | 30 ++++++++++++++++- .../src/triggers/email/handler_oss.rs | 1 - .../src/triggers/gcp/handler_oss.rs | 1 - backend/windmill-api/src/triggers/handler.rs | 18 +++------- .../windmill-api/src/triggers/http/handler.rs | 31 ++++++++--------- .../src/triggers/kafka/handler_oss.rs | 1 - .../windmill-api/src/triggers/mqtt/handler.rs | 1 - .../src/triggers/nats/handler_oss.rs | 1 - .../src/triggers/postgres/handler.rs | 2 -- .../src/triggers/sqs/handler_oss.rs | 1 - .../src/triggers/websocket/handler.rs | 1 - .../email/EmailTriggerEditorInner.svelte | 33 ++++++++++++++----- .../lib/components/triggers/email/utils.ts | 27 +++++++-------- .../triggers/http/RouteEditorInner.svelte | 2 +- .../src/lib/components/triggers/http/utils.ts | 2 +- .../(logged)/email_triggers/+page.svelte | 26 ++++++++++++++- 23 files changed, 133 insertions(+), 77 deletions(-) rename backend/.sqlx/{query-668edc2f84eccf5db6b8daa3ed97e25e46a37c50a9d1a6eca5a277fb58a2e7b4.json => query-628d303956d55d22e5ac64496b66010d4476d5c222184f09e5a189975a37b941.json} (90%) rename backend/.sqlx/{query-d328d00e5393b0e8d2c4b3674221fabbae580fc404e0d4481fd8d7fb51272c94.json => query-6c568509908c1833d9e6f58f739b8a186fc91f94d4fcc40eeb9914084e4ef60c.json} (70%) rename backend/.sqlx/{query-acb094aef60bba9083087264d65034fce38417099f15e8312be72a386f10bc1f.json => query-a704283ff62ac1cd6db489ca3f84d6994d75c6b9e5f61daf54d298a2fdcfd9af.json} (73%) rename backend/.sqlx/{query-4c64bd0e364f536597db83161b5a27ff58b5ec7148bc94807423593bcfa27b90.json => query-ada1a14b4f25e41742df153b07e8bdf5b5feac9c6fa9de38bd24104bac8539d2.json} (67%) rename backend/.sqlx/{query-bbd51f4f0a8bb2db5d6b634f2d32f5f7f7f57390a0e4b6e7cad45c9147fd6356.json => query-df6972fb16a2364f10379fe37e125ae228697602e3769c479582470dc0b9488b.json} (91%) rename backend/.sqlx/{query-6fafc23924eded970689040bd4a94d4d23ebee4f2b7d37bb54c47edb7720be00.json => query-fe12006498b9e7aece6b104fc5d9a80eeac12c6ca07a0e8950941ba796b0ba79.json} (73%) diff --git a/backend/.sqlx/query-668edc2f84eccf5db6b8daa3ed97e25e46a37c50a9d1a6eca5a277fb58a2e7b4.json b/backend/.sqlx/query-628d303956d55d22e5ac64496b66010d4476d5c222184f09e5a189975a37b941.json similarity index 90% rename from backend/.sqlx/query-668edc2f84eccf5db6b8daa3ed97e25e46a37c50a9d1a6eca5a277fb58a2e7b4.json rename to backend/.sqlx/query-628d303956d55d22e5ac64496b66010d4476d5c222184f09e5a189975a37b941.json index 7520bc8879..3f5e733cb1 100644 --- a/backend/.sqlx/query-668edc2f84eccf5db6b8daa3ed97e25e46a37c50a9d1a6eca5a277fb58a2e7b4.json +++ b/backend/.sqlx/query-628d303956d55d22e5ac64496b66010d4476d5c222184f09e5a189975a37b941.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n ", + "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n AND enabled IS TRUE\n ", "describe": { "columns": [ { @@ -66,5 +66,5 @@ true ] }, - "hash": "668edc2f84eccf5db6b8daa3ed97e25e46a37c50a9d1a6eca5a277fb58a2e7b4" + "hash": "628d303956d55d22e5ac64496b66010d4476d5c222184f09e5a189975a37b941" } diff --git a/backend/.sqlx/query-d328d00e5393b0e8d2c4b3674221fabbae580fc404e0d4481fd8d7fb51272c94.json b/backend/.sqlx/query-6c568509908c1833d9e6f58f739b8a186fc91f94d4fcc40eeb9914084e4ef60c.json similarity index 70% rename from backend/.sqlx/query-d328d00e5393b0e8d2c4b3674221fabbae580fc404e0d4481fd8d7fb51272c94.json rename to backend/.sqlx/query-6c568509908c1833d9e6f58f739b8a186fc91f94d4fcc40eeb9914084e4ef60c.json index 474ce7b688..b6e4f96c3c 100644 --- a/backend/.sqlx/query-d328d00e5393b0e8d2c4b3674221fabbae580fc404e0d4481fd8d7fb51272c94.json +++ b/backend/.sqlx/query-6c568509908c1833d9e6f58f739b8a186fc91f94d4fcc40eeb9914084e4ef60c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11\n )\n ", + "query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry,\n enabled\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11, $12\n )\n ", "describe": { "columns": [], "parameters": { @@ -15,10 +15,11 @@ "Varchar", "Varchar", "Jsonb", - "Jsonb" + "Jsonb", + "Bool" ] }, "nullable": [] }, - "hash": "d328d00e5393b0e8d2c4b3674221fabbae580fc404e0d4481fd8d7fb51272c94" + "hash": "6c568509908c1833d9e6f58f739b8a186fc91f94d4fcc40eeb9914084e4ef60c" } diff --git a/backend/.sqlx/query-acb094aef60bba9083087264d65034fce38417099f15e8312be72a386f10bc1f.json b/backend/.sqlx/query-a704283ff62ac1cd6db489ca3f84d6994d75c6b9e5f61daf54d298a2fdcfd9af.json similarity index 73% rename from backend/.sqlx/query-acb094aef60bba9083087264d65034fce38417099f15e8312be72a386f10bc1f.json rename to backend/.sqlx/query-a704283ff62ac1cd6db489ca3f84d6994d75c6b9e5f61daf54d298a2fdcfd9af.json index 805e5f0e79..b59a7f294a 100644 --- a/backend/.sqlx/query-acb094aef60bba9083087264d65034fce38417099f15e8312be72a386f10bc1f.json +++ b/backend/.sqlx/query-a704283ff62ac1cd6db489ca3f84d6994d75c6b9e5f61daf54d298a2fdcfd9af.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8\n WHERE \n workspace_id = $9 AND path = $10\n ", + "query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8,\n enabled = $9\n WHERE \n workspace_id = $10 AND path = $11\n ", "describe": { "columns": [], "parameters": { @@ -13,11 +13,12 @@ "Varchar", "Jsonb", "Jsonb", + "Bool", "Text", "Text" ] }, "nullable": [] }, - "hash": "acb094aef60bba9083087264d65034fce38417099f15e8312be72a386f10bc1f" + "hash": "a704283ff62ac1cd6db489ca3f84d6994d75c6b9e5f61daf54d298a2fdcfd9af" } diff --git a/backend/.sqlx/query-4c64bd0e364f536597db83161b5a27ff58b5ec7148bc94807423593bcfa27b90.json b/backend/.sqlx/query-ada1a14b4f25e41742df153b07e8bdf5b5feac9c6fa9de38bd24104bac8539d2.json similarity index 67% rename from backend/.sqlx/query-4c64bd0e364f536597db83161b5a27ff58b5ec7148bc94807423593bcfa27b90.json rename to backend/.sqlx/query-ada1a14b4f25e41742df153b07e8bdf5b5feac9c6fa9de38bd24104bac8539d2.json index dfe905113e..1a33383b67 100644 --- a/backend/.sqlx/query-4c64bd0e364f536597db83161b5a27ff58b5ec7148bc94807423593bcfa27b90.json +++ b/backend/.sqlx/query-ada1a14b4f25e41742df153b07e8bdf5b5feac9c6fa9de38bd24104bac8539d2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n http_method = $7,\n static_asset_config = $8,\n edited_by = $9,\n email = $10,\n request_type = $11,\n authentication_method = $12,\n summary = $13,\n description = $14,\n edited_at = now(),\n is_static_website = $15,\n error_handler_path = $16,\n error_handler_args = $17,\n retry = $18\n WHERE\n workspace_id = $19 AND\n path = $20\n ", + "query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n enabled = $7,\n http_method = $8,\n static_asset_config = $9,\n edited_by = $10,\n email = $11,\n request_type = $12,\n authentication_method = $13,\n summary = $14,\n description = $15,\n edited_at = now(),\n is_static_website = $16,\n error_handler_path = $17,\n error_handler_args = $18,\n retry = $19\n WHERE\n workspace_id = $20 AND\n path = $21\n ", "describe": { "columns": [], "parameters": { @@ -11,6 +11,7 @@ "Varchar", "Varchar", "Bool", + "Bool", { "Custom": { "name": "http_method", @@ -67,5 +68,5 @@ }, "nullable": [] }, - "hash": "4c64bd0e364f536597db83161b5a27ff58b5ec7148bc94807423593bcfa27b90" + "hash": "ada1a14b4f25e41742df153b07e8bdf5b5feac9c6fa9de38bd24104bac8539d2" } diff --git a/backend/.sqlx/query-bbd51f4f0a8bb2db5d6b634f2d32f5f7f7f57390a0e4b6e7cad45c9147fd6356.json b/backend/.sqlx/query-df6972fb16a2364f10379fe37e125ae228697602e3769c479582470dc0b9488b.json similarity index 91% rename from backend/.sqlx/query-bbd51f4f0a8bb2db5d6b634f2d32f5f7f7f57390a0e4b6e7cad45c9147fd6356.json rename to backend/.sqlx/query-df6972fb16a2364f10379fe37e125ae228697602e3769c479582470dc0b9488b.json index ab4e5e5d36..1e5c26b2e7 100644 --- a/backend/.sqlx/query-bbd51f4f0a8bb2db5d6b634f2d32f5f7f7f57390a0e4b6e7cad45c9147fd6356.json +++ b/backend/.sqlx/query-df6972fb16a2364f10379fe37e125ae228697602e3769c479582470dc0b9488b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n ", + "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n AND enabled IS TRUE\n ", "describe": { "columns": [ { @@ -68,5 +68,5 @@ true ] }, - "hash": "bbd51f4f0a8bb2db5d6b634f2d32f5f7f7f57390a0e4b6e7cad45c9147fd6356" + "hash": "df6972fb16a2364f10379fe37e125ae228697602e3769c479582470dc0b9488b" } diff --git a/backend/.sqlx/query-6fafc23924eded970689040bd4a94d4d23ebee4f2b7d37bb54c47edb7720be00.json b/backend/.sqlx/query-fe12006498b9e7aece6b104fc5d9a80eeac12c6ca07a0e8950941ba796b0ba79.json similarity index 73% rename from backend/.sqlx/query-6fafc23924eded970689040bd4a94d4d23ebee4f2b7d37bb54c47edb7720be00.json rename to backend/.sqlx/query-fe12006498b9e7aece6b104fc5d9a80eeac12c6ca07a0e8950941ba796b0ba79.json index 706bcacdb2..150d2d9504 100644 --- a/backend/.sqlx/query-6fafc23924eded970689040bd4a94d4d23ebee4f2b7d37bb54c47edb7720be00.json +++ b/backend/.sqlx/query-fe12006498b9e7aece6b104fc5d9a80eeac12c6ca07a0e8950941ba796b0ba79.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10\n WHERE \n workspace_id = $11 AND path = $12\n ", + "query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10,\n enabled = $11\n WHERE \n workspace_id = $12 AND path = $13\n ", "describe": { "columns": [], "parameters": { @@ -15,11 +15,12 @@ "Varchar", "Jsonb", "Jsonb", + "Bool", "Text", "Text" ] }, "nullable": [] }, - "hash": "6fafc23924eded970689040bd4a94d4d23ebee4f2b7d37bb54c47edb7720be00" + "hash": "fe12006498b9e7aece6b104fc5d9a80eeac12c6ca07a0e8950941ba796b0ba79" } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 7dd5e2c77a..ada9554f8a 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -6694dfbc62ff69570743f028aa19c543ae846e6e \ No newline at end of file +6009dffc0e9549463e34d748ad54c401cbe46ffd diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 928e5d007a..85740d0d2f 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -11998,6 +11998,33 @@ paths: application/json: schema: type: boolean + /w/{workspace}/email_triggers/setenabled/{path}: + post: + summary: enable/disable email trigger + operationId: setEmailTriggerEnabled + tags: + - email_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + "200": + description: email trigger enable/disable + content: + text/plain: + schema: + type: string /groups/list: get: @@ -18612,7 +18639,8 @@ components: $ref: "#/components/schemas/ScriptArgs" retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" - + enabled: + type: boolean required: - path - script_path diff --git a/backend/windmill-api/src/triggers/email/handler_oss.rs b/backend/windmill-api/src/triggers/email/handler_oss.rs index ca4de1733e..03a377df98 100644 --- a/backend/windmill-api/src/triggers/email/handler_oss.rs +++ b/backend/windmill-api/src/triggers/email/handler_oss.rs @@ -28,7 +28,6 @@ impl TriggerCrud for EmailTrigger { const TABLE_NAME: &'static str = ""; const TRIGGER_TYPE: &'static str = ""; - const SUPPORTS_ENABLED: bool = true; const SUPPORTS_SERVER_STATE: bool = false; const SUPPORTS_TEST_CONNECTION: bool = false; const ROUTE_PREFIX: &'static str = "/email_triggers"; diff --git a/backend/windmill-api/src/triggers/gcp/handler_oss.rs b/backend/windmill-api/src/triggers/gcp/handler_oss.rs index 4b52ff9c68..b75a9721eb 100644 --- a/backend/windmill-api/src/triggers/gcp/handler_oss.rs +++ b/backend/windmill-api/src/triggers/gcp/handler_oss.rs @@ -25,7 +25,6 @@ impl TriggerCrud for GcpTrigger { const TABLE_NAME: &'static str = ""; const TRIGGER_TYPE: &'static str = ""; - const SUPPORTS_ENABLED: bool = false; const SUPPORTS_SERVER_STATE: bool = false; const SUPPORTS_TEST_CONNECTION: bool = false; const ROUTE_PREFIX: &'static str = "/gcp_triggers"; diff --git a/backend/windmill-api/src/triggers/handler.rs b/backend/windmill-api/src/triggers/handler.rs index a6019c5a14..05042bd385 100644 --- a/backend/windmill-api/src/triggers/handler.rs +++ b/backend/windmill-api/src/triggers/handler.rs @@ -50,7 +50,6 @@ pub trait TriggerCrud: Send + Sync + 'static { const TABLE_NAME: &'static str; const TRIGGER_TYPE: &'static str; - const SUPPORTS_ENABLED: bool; const SUPPORTS_SERVER_STATE: bool; const SUPPORTS_TEST_CONNECTION: bool; const ROUTE_PREFIX: &'static str; @@ -144,12 +143,9 @@ pub trait TriggerCrud: Send + Sync + 'static { "email", "edited_at", "extra_perms", + "enabled", ]; - if Self::SUPPORTS_ENABLED { - fields.push("enabled"); - } - if Self::SUPPORTS_SERVER_STATE { fields.extend_from_slice(&["server_id", "last_server_ping", "error"]); } @@ -325,12 +321,9 @@ pub trait TriggerCrud: Send + Sync + 'static { "email", "edited_at", "extra_perms", + "enabled", ]; - if Self::SUPPORTS_ENABLED { - fields.push("enabled"); - } - if Self::SUPPORTS_SERVER_STATE { fields.extend_from_slice(&["server_id", "last_server_ping", "error"]); } @@ -380,11 +373,8 @@ pub fn trigger_routes() -> Router { .route("/get/*path", get(get_trigger::)) .route("/update/*path", post(update_trigger::)) .route("/delete/*path", delete(delete_trigger::)) - .route("/exists/*path", get(exists_trigger::)); - - if T::SUPPORTS_ENABLED { - router = router.route("/setenabled/*path", post(set_enabled_trigger::)); - } + .route("/exists/*path", get(exists_trigger::)) + .route("/setenabled/*path", post(set_enabled_trigger::)); if T::SUPPORTS_TEST_CONNECTION { router = router.route("/test", post(test_connection::)); diff --git a/backend/windmill-api/src/triggers/http/handler.rs b/backend/windmill-api/src/triggers/http/handler.rs index 19fabe40ea..cf1a004c32 100644 --- a/backend/windmill-api/src/triggers/http/handler.rs +++ b/backend/windmill-api/src/triggers/http/handler.rs @@ -358,7 +358,6 @@ impl TriggerCrud for HttpTrigger { const TABLE_NAME: &'static str = "http_trigger"; const TRIGGER_TYPE: &'static str = "http"; - const SUPPORTS_ENABLED: bool = true; const SUPPORTS_SERVER_STATE: bool = false; const SUPPORTS_TEST_CONNECTION: bool = false; const ROUTE_PREFIX: &'static str = "/http_triggers"; @@ -543,22 +542,23 @@ impl TriggerCrud for HttpTrigger { script_path = $4, path = $5, is_flow = $6, - http_method = $7, - static_asset_config = $8, - edited_by = $9, - email = $10, - request_type = $11, - authentication_method = $12, - summary = $13, - description = $14, + enabled = $7, + http_method = $8, + static_asset_config = $9, + edited_by = $10, + email = $11, + request_type = $12, + authentication_method = $13, + summary = $14, + description = $15, edited_at = now(), - is_static_website = $15, - error_handler_path = $16, - error_handler_args = $17, - retry = $18 + is_static_website = $16, + error_handler_path = $17, + error_handler_args = $18, + retry = $19 WHERE - workspace_id = $19 AND - path = $20 + workspace_id = $20 AND + path = $21 "#, trigger.config.wrap_body, trigger.config.raw_string, @@ -566,6 +566,7 @@ impl TriggerCrud for HttpTrigger { trigger.base.script_path, trigger.base.path, trigger.base.is_flow, + trigger.base.enabled.unwrap_or(true), trigger.config.http_method as _, trigger.config.static_asset_config as _, &authed.username, diff --git a/backend/windmill-api/src/triggers/kafka/handler_oss.rs b/backend/windmill-api/src/triggers/kafka/handler_oss.rs index bf5537b941..9fda83916f 100644 --- a/backend/windmill-api/src/triggers/kafka/handler_oss.rs +++ b/backend/windmill-api/src/triggers/kafka/handler_oss.rs @@ -28,7 +28,6 @@ impl TriggerCrud for KafkaTrigger { const TABLE_NAME: &'static str = ""; const TRIGGER_TYPE: &'static str = ""; - const SUPPORTS_ENABLED: bool = false; const SUPPORTS_SERVER_STATE: bool = false; const SUPPORTS_TEST_CONNECTION: bool = false; const ROUTE_PREFIX: &'static str = "/kafka_triggers"; diff --git a/backend/windmill-api/src/triggers/mqtt/handler.rs b/backend/windmill-api/src/triggers/mqtt/handler.rs index d8e314a836..fd6054666c 100644 --- a/backend/windmill-api/src/triggers/mqtt/handler.rs +++ b/backend/windmill-api/src/triggers/mqtt/handler.rs @@ -26,7 +26,6 @@ impl TriggerCrud for MqttTrigger { const TABLE_NAME: &'static str = "mqtt_trigger"; const TRIGGER_TYPE: &'static str = "mqtt"; - const SUPPORTS_ENABLED: bool = true; const SUPPORTS_SERVER_STATE: bool = true; const SUPPORTS_TEST_CONNECTION: bool = true; const ROUTE_PREFIX: &'static str = "/mqtt_triggers"; diff --git a/backend/windmill-api/src/triggers/nats/handler_oss.rs b/backend/windmill-api/src/triggers/nats/handler_oss.rs index 41f060003f..3ab3430e5f 100644 --- a/backend/windmill-api/src/triggers/nats/handler_oss.rs +++ b/backend/windmill-api/src/triggers/nats/handler_oss.rs @@ -25,7 +25,6 @@ impl TriggerCrud for NatsTrigger { const TABLE_NAME: &'static str = ""; const TRIGGER_TYPE: &'static str = ""; - const SUPPORTS_ENABLED: bool = false; const SUPPORTS_SERVER_STATE: bool = false; const SUPPORTS_TEST_CONNECTION: bool = false; const ROUTE_PREFIX: &'static str = "/nats_triggers"; diff --git a/backend/windmill-api/src/triggers/postgres/handler.rs b/backend/windmill-api/src/triggers/postgres/handler.rs index 5dce0345c6..3b0a1b6972 100644 --- a/backend/windmill-api/src/triggers/postgres/handler.rs +++ b/backend/windmill-api/src/triggers/postgres/handler.rs @@ -47,7 +47,6 @@ impl TriggerCrud for PostgresTrigger { const TABLE_NAME: &'static str = "postgres_trigger"; const TRIGGER_TYPE: &'static str = "postgres"; - const SUPPORTS_ENABLED: bool = true; const SUPPORTS_SERVER_STATE: bool = true; const SUPPORTS_TEST_CONNECTION: bool = true; const ROUTE_PREFIX: &'static str = "/postgres_triggers"; @@ -64,7 +63,6 @@ impl TriggerCrud for PostgresTrigger { DeployedObject::PostgresTrigger { path } } - async fn create_trigger( &self, db: &DB, diff --git a/backend/windmill-api/src/triggers/sqs/handler_oss.rs b/backend/windmill-api/src/triggers/sqs/handler_oss.rs index 6b6d18b72e..21200e8e38 100644 --- a/backend/windmill-api/src/triggers/sqs/handler_oss.rs +++ b/backend/windmill-api/src/triggers/sqs/handler_oss.rs @@ -26,7 +26,6 @@ impl TriggerCrud for SqsTrigger { const TABLE_NAME: &'static str = ""; const TRIGGER_TYPE: &'static str = ""; - const SUPPORTS_ENABLED: bool = false; const SUPPORTS_SERVER_STATE: bool = false; const SUPPORTS_TEST_CONNECTION: bool = false; const ROUTE_PREFIX: &'static str = "/sqs_triggers"; diff --git a/backend/windmill-api/src/triggers/websocket/handler.rs b/backend/windmill-api/src/triggers/websocket/handler.rs index 510f6e97ae..2439be8292 100644 --- a/backend/windmill-api/src/triggers/websocket/handler.rs +++ b/backend/windmill-api/src/triggers/websocket/handler.rs @@ -30,7 +30,6 @@ impl TriggerCrud for WebsocketTrigger { const TABLE_NAME: &'static str = "websocket_trigger"; const TRIGGER_TYPE: &'static str = "websocket"; - const SUPPORTS_ENABLED: bool = true; const SUPPORTS_SERVER_STATE: bool = true; const SUPPORTS_TEST_CONNECTION: bool = true; const ROUTE_PREFIX: &'static str = "/websocket_triggers"; diff --git a/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte index 291a3e08ac..4e8a37e9e0 100644 --- a/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte @@ -65,6 +65,7 @@ let error_handler_path: string | undefined = $state() let error_handler_args: Record = $state({}) let retry: Retry | undefined = $state() + let enabled = $state(false) // Component references let drawer = $state(undefined) let initialConfig: NewEmailTrigger | undefined = undefined @@ -72,7 +73,7 @@ let optionTabSelected: 'error_handler' | 'retries' = $state('error_handler') let errorHandlerSelected: ErrorHandler = $state('slack') const isAdmin = $derived($userStore?.is_admin || $userStore?.is_super_admin) - const routeConfig = $derived.by(getEmailTriggerConfig) + const emailConfig = $derived.by(getEmailTriggerConfig) const captureConfig = $derived.by(isEditor ? getCaptureConfig : () => ({})) const saveDisabled = $derived( drawerLoading || !can_write || pathError != '' || !isValid || emptyString(script_path) @@ -141,6 +142,7 @@ error_handler_args = defaultValues?.error_handler_args ?? {} retry = defaultValues?.retry ?? undefined errorHandlerSelected = getHandlerType(error_handler_path ?? '') + enabled = defaultValues?.enabled ?? false } finally { clearTimeout(loader) drawerLoading = false @@ -161,6 +163,7 @@ error_handler_args = cfg?.error_handler_args ?? {} retry = cfg?.retry errorHandlerSelected = getHandlerType(error_handler_path ?? '') + enabled = cfg?.enabled ?? false } async function loadTrigger(defaultConfig?: Partial): Promise { @@ -179,11 +182,11 @@ async function triggerScript(): Promise { if (customSaveBehavior) { - customSaveBehavior(routeConfig) + customSaveBehavior(emailConfig) drawer?.closeDrawer() } else { deploymentLoading = true - const saveCfg = routeConfig + const saveCfg = emailConfig const isSaved = await saveEmailTriggerFromCfg( initialPath, saveCfg, @@ -210,17 +213,30 @@ extra_perms: extraPerms, error_handler_path, error_handler_args, - retry + retry, + enabled } return nCfg } + async function handleToggleEnabled(newEnabled: boolean) { + enabled = newEnabled + if (!trigger?.draftConfig) { + await EmailTriggerService.setEmailTriggerEnabled({ + path: initialPath, + workspace: $workspaceStore ?? '', + requestBody: { enabled: newEnabled } + }) + sendUserToast(`${newEnabled ? 'enabled' : 'disabled'} email trigger ${initialPath}`) + } + } + // Update config for captures function getCaptureConfig() { const newCaptureConfig = { - local_part: routeConfig.local_part, - path: routeConfig.path + local_part: emailConfig.local_part, + path: emailConfig.path } // return newCaptureConfig @@ -233,7 +249,7 @@ $effect(() => { if (!drawerLoading) { - handleConfigChange(routeConfig, initialConfig, saveDisabled, edit, onConfigChange) + handleConfigChange(emailConfig, initialConfig, saveDisabled, edit, onConfigChange) } }) @@ -338,8 +354,9 @@ {trigger} permissions={drawerLoading || !can_write ? 'none' : can_write && isAdmin ? 'create' : 'write'} {saveDisabled} - enabled={undefined} + {enabled} {allowDraft} + onToggleEnabled={handleToggleEnabled} {edit} isLoading={deploymentLoading} onUpdate={triggerScript} diff --git a/frontend/src/lib/components/triggers/email/utils.ts b/frontend/src/lib/components/triggers/email/utils.ts index 82de2a0a0d..49a94093eb 100644 --- a/frontend/src/lib/components/triggers/email/utils.ts +++ b/frontend/src/lib/components/triggers/email/utils.ts @@ -15,21 +15,22 @@ export function getEmailAddress( export async function saveEmailTriggerFromCfg( initialPath: string, - routeCfg: Record, + emailCfg: Record, edit: boolean, workspace: string, isAdmin: boolean, usedTriggerKinds: Writable ): Promise { const requestBody: NewEmailTrigger = { - path: routeCfg.path, - script_path: routeCfg.script_path, - local_part: routeCfg.local_part, - is_flow: routeCfg.is_flow, - workspaced_local_part: routeCfg.workspaced_local_part, - error_handler_path: routeCfg.error_handler_path, - error_handler_args: routeCfg.error_handler_path ? routeCfg.error_handler_args : undefined, - retry: routeCfg.retry + path: emailCfg.path, + script_path: emailCfg.script_path, + local_part: emailCfg.local_part, + is_flow: emailCfg.is_flow, + workspaced_local_part: emailCfg.workspaced_local_part, + error_handler_path: emailCfg.error_handler_path, + error_handler_args: emailCfg.error_handler_path ? emailCfg.error_handler_args : undefined, + retry: emailCfg.retry, + enabled: emailCfg.enabled } try { if (edit) { @@ -38,16 +39,16 @@ export async function saveEmailTriggerFromCfg( path: initialPath, requestBody: { ...requestBody, - local_part: isAdmin || !edit ? routeCfg.local_part : undefined + local_part: isAdmin || !edit ? emailCfg.local_part : undefined } }) - sendUserToast(`Route ${routeCfg.path} updated`) + sendUserToast(`Email trigger ${emailCfg.path} updated`) } else { await EmailTriggerService.createEmailTrigger({ workspace: workspace, - requestBody: requestBody + requestBody: { ...requestBody, enabled: true } }) - sendUserToast(`Route ${routeCfg.path} created`) + sendUserToast(`Email trigger ${emailCfg.path} created`) } if (!get(usedTriggerKinds).includes('email')) { usedTriggerKinds.update((t) => [...t, 'email']) diff --git a/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte b/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte index bb11e620bf..085360df6a 100644 --- a/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte +++ b/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte @@ -236,7 +236,7 @@ s3FileUploadRawMode = defaultValues?.s3FileUploadRawMode ?? false path = defaultValues?.path ?? '' initialPath = '' - enabled = defaultValues?.enabled ?? true + enabled = defaultValues?.enabled ?? false dirtyPath = false is_static_website = defaultValues?.is_static_website ?? false workspaced_route = defaultValues?.workspaced_route ?? false diff --git a/frontend/src/lib/components/triggers/http/utils.ts b/frontend/src/lib/components/triggers/http/utils.ts index 212201f0b6..5cb2cf2b4f 100644 --- a/frontend/src/lib/components/triggers/http/utils.ts +++ b/frontend/src/lib/components/triggers/http/utils.ts @@ -77,7 +77,7 @@ export async function saveHttpRouteFromCfg( } else { await HttpTriggerService.createHttpTrigger({ workspace: workspace, - requestBody: requestBody + requestBody: { ...requestBody, enabled: true } }) sendUserToast(`Route ${routeCfg.path} created`) } diff --git a/frontend/src/routes/(root)/(logged)/email_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/email_triggers/+page.svelte index 8ea6a5069a..386db77449 100644 --- a/frontend/src/routes/(root)/(logged)/email_triggers/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/email_triggers/+page.svelte @@ -193,6 +193,23 @@ } } + async function setTriggerEnabled(path: string, enabled: boolean): Promise { + try { + await EmailTriggerService.setEmailTriggerEnabled({ + path, + workspace: $workspaceStore!, + requestBody: { enabled } + }) + } catch (err) { + sendUserToast( + `Cannot ` + (enabled ? 'enable' : 'disable') + ` email trigger: ${err.body}`, + true + ) + } finally { + loadTriggers() + } + } + onMount(() => { loadQueryFilters() }) @@ -274,7 +291,7 @@
No email triggers
{:else if items?.length}
- {#each items.slice(0, nbDisplayed) as { workspace_id, workspaced_local_part, path, edited_by, edited_at, script_path, is_flow, extra_perms, canWrite, marked, local_part } (path)} + {#each items.slice(0, nbDisplayed) as { workspace_id, workspaced_local_part, path, edited_by, edited_at, script_path, is_flow, extra_perms, canWrite, marked, local_part, enabled } (path)} {@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`} {@const emailAddress = getEmailAddress( local_part, @@ -317,6 +334,13 @@ + { + setTriggerEnabled(path, e.detail) + }} + />
-
+
diff --git a/frontend/src/lib/components/common/button/Button.svelte b/frontend/src/lib/components/common/button/Button.svelte index ee6a3265de..b1372dfd0b 100644 --- a/frontend/src/lib/components/common/button/Button.svelte +++ b/frontend/src/lib/components/common/button/Button.svelte @@ -215,7 +215,7 @@ const horizontalPadding = iconOnly ? ButtonType.UnifiedIconOnlySizingClasses[unifiedSize] : ButtonType.UnifiedSizingClasses[unifiedSize] - const height = ButtonType.UnifiedMinHeightClasses[unifiedSize] + const height = ButtonType.UnifiedHeightClasses[unifiedSize] return `${horizontalPadding} ${height}` } diff --git a/frontend/src/lib/components/common/button/model.ts b/frontend/src/lib/components/common/button/model.ts index 5eb75ba531..e1af83c971 100644 --- a/frontend/src/lib/components/common/button/model.ts +++ b/frontend/src/lib/components/common/button/model.ts @@ -17,7 +17,7 @@ export namespace ButtonType { * @deprecated Use `UnifiedSize` instead */ export type Size = 'xs3' | 'xs2' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' - export type UnifiedSize = 'sm' | 'md' | 'lg' + export type UnifiedSize = 'xs' | 'sm' | 'md' | 'lg' export type ExtendedSize = 'xs2' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' /** * @deprecated Use `Variant` instead @@ -194,7 +194,7 @@ export namespace ButtonType { accent: 'bg-red-500 dark:bg-red-600 hover:bg-red-600 dark:hover:bg-red-700 focus-visible:bg-red-700 text-white focus-visible:ring-red-300', default: - 'border border-border-light bg-transparent hover:bg-red-500 dark:hover:bg-red-600 hover:text-white dark:hover:bg-red-900/20 text-primary focus-visible:bg-red-100 dark:focus-visible:bg-red-900/30 focus-visible:ring-red-300', + 'border border-border-light bg-transparent hover:bg-red-500 dark:hover:bg-red-600 hover:text-white dark:hover:bg-red-600 text-primary focus-visible:bg-red-100 dark:focus-visible:bg-red-900/30 focus-visible:ring-red-300', subtle: 'bg-transparent hover:bg-red-500 hover:text-white dark:hover:bg-red-600 text-primary focus-visible:bg-red-100 dark:focus-visible:bg-red-900/30 focus-visible:ring-red-300' } @@ -221,36 +221,42 @@ export namespace ButtonType { // New unified sizing system export const UnifiedSizingClasses: Record = { + xs: 'px-1', sm: 'px-2', // Regular horizontal padding md: 'px-4', lg: 'px-6' } export const UnifiedIconOnlySizingClasses: Record = { + xs: 'px-1', sm: 'px-2', // Square padding for icon-only (same as width padding) md: 'px-2', lg: 'px-4' } export const UnifiedMinHeightClasses: Record = { + xs: 'min-h-5', sm: 'min-h-7', md: 'min-h-8', lg: 'min-h-10' } export const UnifiedHeightClasses: Record = { + xs: 'h-5', sm: 'h-7', md: 'h-8', lg: 'h-10' } export const UnifiedIconSizes: Record = { + xs: 12, sm: 13, md: 14, lg: 18 } export const UnifiedFontSizes: Record = { + xs: 'font-normal', sm: 'font-normal', md: 'font-medium', lg: 'font-medium' diff --git a/frontend/src/lib/components/common/contextmenu/ContextMenu.svelte b/frontend/src/lib/components/common/contextmenu/ContextMenu.svelte new file mode 100644 index 0000000000..8e0f440bde --- /dev/null +++ b/frontend/src/lib/components/common/contextmenu/ContextMenu.svelte @@ -0,0 +1,126 @@ + + +
+ {@render children?.()} +
+ +{#if $open} +
+ {#each items as menuItem (menuItem.id)} + {#if menuItem.divider} +
+ {:else} +
handleItemClick(menuItem)} + > + {#if menuItem.icon} + + {/if} + {#if menu} + {@render menu({ item: menuItem })} + {:else} + {menuItem.label} + {/if} +
+ {/if} + {/each} +
+{/if} diff --git a/frontend/src/lib/components/common/contextmenu/contextMenuStyles.ts b/frontend/src/lib/components/common/contextmenu/contextMenuStyles.ts new file mode 100644 index 0000000000..ecbe6c6f87 --- /dev/null +++ b/frontend/src/lib/components/common/contextmenu/contextMenuStyles.ts @@ -0,0 +1,45 @@ +/** + * Shared styles for context menu components + * Ensures visual consistency across all context menu implementations + */ + +/** + * Base container styles for context menu + * @param zIndex - Optional z-index override (default: 'z-50') + */ +export function getContextMenuContainerClass(zIndex: string = 'z-50'): string { + return `${zIndex} flex flex-col gap-1 min-w-[12rem] overflow-hidden rounded-md border bg-surface p-1 shadow-md` +} + +/** + * Base styles for context menu items + */ +export const CONTEXT_MENU_ITEM_BASE_CLASS = + 'relative flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-xs outline-none transition-colors' + +/** + * Hover state styles for context menu items (standard CSS hover) + */ +export const CONTEXT_MENU_ITEM_HOVER_CLASS = 'hover:bg-surface-hover' + +/** + * Hover state styles for context menu items (Melt UI data attribute) + */ +export const CONTEXT_MENU_ITEM_HOVER_MELT_CLASS = 'data-[highlighted]:bg-surface-hover' + +/** + * Disabled state styles for context menu items + */ +export const CONTEXT_MENU_ITEM_DISABLED_CLASS = 'pointer-events-none opacity-50' + +/** + * Divider styles for context menu + */ +export const CONTEXT_MENU_DIVIDER_CLASS = 'my-1 h-px bg-border-light' + +/** + * Melt UI animation classes for context menu + */ +export const CONTEXT_MENU_ANIMATION_CLASSES = + 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2' + diff --git a/frontend/src/lib/components/copilot/IteratorGen.svelte b/frontend/src/lib/components/copilot/IteratorGen.svelte index 46eb6d1519..b9015eb2b1 100644 --- a/frontend/src/lib/components/copilot/IteratorGen.svelte +++ b/frontend/src/lib/components/copilot/IteratorGen.svelte @@ -35,7 +35,7 @@ ) let abortController = new AbortController() - const { flowStore, selectedId } = getContext('FlowEditorContext') + const { flowStore, selectionManager } = getContext('FlowEditorContext') async function generateIteratorExpr() { if (generatedContent.length > 0 || loading) { @@ -45,7 +45,7 @@ loading = true const flow: Flow = JSON.parse(JSON.stringify(flowStore.val)) const idOrders = dfs(flow.value.modules, (x) => x.id) - const upToIndex = idOrders.indexOf($selectedId) + const upToIndex = idOrders.indexOf(selectionManager.getSelectedId()) if (upToIndex === -1) { throw new Error('Could not find the selected id in the flow') } @@ -60,7 +60,7 @@ flow_input: pickableProperties?.flow_input } const user = `I'm building a workflow which is a DAG of script steps. -The current step is ${$selectedId} and represents a for-loop. You can find the details of all the steps below: +The current step is ${selectionManager.getSelectedId()} and represents a for-loop. You can find the details of all the steps below: ${flowDetails} Determine the iterator expression to pass either from the previous results or the flow inputs. Here's a summary of the available data: diff --git a/frontend/src/lib/components/copilot/PredicateGen.svelte b/frontend/src/lib/components/copilot/PredicateGen.svelte index 6bfeacd62c..c49edb70a8 100644 --- a/frontend/src/lib/components/copilot/PredicateGen.svelte +++ b/frontend/src/lib/components/copilot/PredicateGen.svelte @@ -29,7 +29,7 @@ }) let abortController = $state(new AbortController()) - const { flowStore, selectedId } = getContext('FlowEditorContext') + const { flowStore, selectionManager } = getContext('FlowEditorContext') const dispatch = createEventDispatcher() @@ -38,7 +38,7 @@ loading = true const flow: Flow = JSON.parse(JSON.stringify(flowStore.val)) const idOrders = dfs(flow.value.modules, (x) => x.id) - const upToIndex = idOrders.indexOf($selectedId) + const upToIndex = idOrders.indexOf(selectionManager.getSelectedId()) if (upToIndex === -1) { throw new Error('Could not find the selected id in the flow') } @@ -53,7 +53,7 @@ flow_input: pickableProperties?.flow_input } const user = `I'm building a workflow which is a DAG of script steps. -The current step is ${$selectedId} and is a branching step (if-else). +The current step is ${selectionManager.getSelectedId()} and is a branching step (if-else). The user wants to generate a predicate for the branching condition. Here's the user's request: ${instructions} You can find the details of all the steps below: diff --git a/frontend/src/lib/components/copilot/StepInputGen.svelte b/frontend/src/lib/components/copilot/StepInputGen.svelte index 9dec547627..d2a29cfa8c 100644 --- a/frontend/src/lib/components/copilot/StepInputGen.svelte +++ b/frontend/src/lib/components/copilot/StepInputGen.svelte @@ -54,7 +54,7 @@ let abortController = new AbortController() let newFlowInput = $state('') - const { flowStore, selectedId } = getContext('FlowEditorContext') + const { flowStore, selectionManager } = getContext('FlowEditorContext') const { stepInputsLoading, generatedExprs } = getContext('FlowCopilotContext') || {} @@ -86,7 +86,7 @@ loading = true const flow: Flow = JSON.parse(JSON.stringify(flowStore.val)) const idOrders = dfs(flow.value.modules, (x) => x.id) - const upToIndex = idOrders.indexOf($selectedId) + const upToIndex = idOrders.indexOf(selectionManager.getSelectedId()) if (upToIndex === -1) { throw new Error('Could not find the selected id in the flow') } @@ -102,7 +102,7 @@ } const isInsideLoop = availableData.flow_input && 'iter' in availableData.flow_input const user = `I'm building a workflow which is a DAG of script steps. -The current step is ${$selectedId}, you can find the details for the step and previous ones below: +The current step is ${selectionManager.getSelectedId()}, you can find the details for the step and previous ones below: ${flowDetails} Determine for the input "${argName}", what to pass either from the previous results or the flow inputs. All possibles inputs either start with results. or flow_input. and are followed by the key of the input. diff --git a/frontend/src/lib/components/copilot/StepInputsGen.svelte b/frontend/src/lib/components/copilot/StepInputsGen.svelte index 2ce8d4b177..c2781153a6 100644 --- a/frontend/src/lib/components/copilot/StepInputsGen.svelte +++ b/frontend/src/lib/components/copilot/StepInputsGen.svelte @@ -30,7 +30,7 @@ let { pickableProperties = undefined, argNames = [], schema = undefined }: Props = $props() - const { flowStore, selectedId } = getContext('FlowEditorContext') + const { flowStore, selectionManager } = getContext('FlowEditorContext') const { exprsToSet, stepInputsLoading, generatedExprs } = getContext('FlowCopilotContext') || {} @@ -49,7 +49,7 @@ stepInputsLoading?.set(true) const flow: Flow = JSON.parse(JSON.stringify(flowStore.val)) const idOrders = dfs(flow.value.modules, (x) => x.id) - const upToIndex = idOrders.indexOf($selectedId) + const upToIndex = idOrders.indexOf(selectionManager.getSelectedId()) if (upToIndex === -1) { throw new Error('Could not find the selected id in the flow') } @@ -65,7 +65,7 @@ } const isInsideLoop = availableData.flow_input && 'iter' in availableData.flow_input const user = `I'm building a workflow which is a DAG of script steps. -The current step is ${$selectedId}, you can find the details for the step and previous ones below: +The current step is ${selectionManager.getSelectedId()}, you can find the details for the step and previous ones below: ${flowDetails} Determine for all the inputs "${argNames.join( diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index edeeed1af9..149f6930d8 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -882,7 +882,7 @@ class AIChatManager { } listenForSelectedIdChanges = ( - selectedId: string, + selectedId: string | undefined, flowStore: ExtendedOpenFlow, flowStateStore: FlowState, currentEditor: CurrentEditor diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index dc48253240..56c7f18f7c 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -25,8 +25,9 @@ flowModuleSchemaMap: FlowModuleSchemaMap | undefined } = $props() - const { flowStore, flowStateStore, selectedId, currentEditor } = + const { flowStore, flowStateStore, selectionManager, currentEditor } = getContext('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) const { exprsToSet } = getContext('FlowCopilotContext') ?? {} @@ -84,7 +85,7 @@ const flow = $state.snapshot(flowStore).val return { flow, - selectedId: $selectedId + selectedId: selectedId } }, // flow apply/reject @@ -382,7 +383,7 @@ value: match[2].trim() })) - if (id === $selectedId) { + if (id === selectedId) { exprsToSet?.set({}) const argsToUpdate = {} for (const { input, value } of parsedInputs) { @@ -421,7 +422,7 @@ setModuleStatus('Input', 'modified') }, selectStep: (id) => { - $selectedId = id + selectionManager.selectId(id) }, getStepCode: (id) => { const module = getModule(id) @@ -611,7 +612,7 @@ $effect(() => { const cleanup = aiChatManager.listenForSelectedIdChanges( - $selectedId, + selectedId, flowStore.val, flowStateStore.val, $currentEditor @@ -628,19 +629,18 @@ $effect(() => { if ( $currentEditor?.type === 'script' && - $selectedId && - affectedModules[$selectedId] && + selectedId && + affectedModules[selectedId] && $currentEditor.editor.getAiChatEditorHandler() ) { - const moduleLastSnapshot = getModule($selectedId, lastSnapshot) + const moduleLastSnapshot = getModule(selectedId, lastSnapshot) const content = moduleLastSnapshot?.value.type === 'rawscript' ? moduleLastSnapshot.value.content : '' if (content.length > 0) { untrack(() => $currentEditor.editor.reviewAppliedCode(content, { onFinishedReview: () => { - const id = $selectedId - flowHelpers.acceptModuleAction(id) + flowHelpers.acceptModuleAction(selectedId) $currentEditor.hideDiffMode() } }) diff --git a/frontend/src/lib/components/flows/FlowEditor.svelte b/frontend/src/lib/components/flows/FlowEditor.svelte index 775bd7cadb..0d7be9c358 100644 --- a/frontend/src/lib/components/flows/FlowEditor.svelte +++ b/frontend/src/lib/components/flows/FlowEditor.svelte @@ -102,6 +102,10 @@ return flowModuleSchemaMap?.isNodeVisible(nodeId) ?? false } + export function enableNotes(): void { + flowModuleSchemaMap?.enableNotes?.() + } + setContext('PropPickerContext', { flowPropPickerConfig: writable(undefined), pickablePropertiesFiltered: writable(undefined) diff --git a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte index 3e6d4e22af..f0288e6533 100644 --- a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte +++ b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte @@ -15,6 +15,7 @@ import { computeMissingInputWarnings } from '../missingInputWarnings' import FlowResult from './FlowResult.svelte' import type { StateStore } from '$lib/utils' + import FlowSelectionPanel from './FlowSelectionPanel.svelte' interface Props { noEditor?: boolean @@ -55,7 +56,7 @@ }: Props = $props() const { - selectedId, + selectionManager, flowStore, flowStateStore, flowInputsStore, @@ -66,6 +67,8 @@ flowInputEditorState } = getContext('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) + const { showCaptureHint, triggersState, triggersCount } = getContext('TriggerContext') function checkDup(modules: FlowModule[]): string | undefined { @@ -84,14 +87,16 @@ }) -{#if $selectedId?.startsWith('settings')} +{#if selectionManager && selectionManager.selectedIds.length > 1} + +{:else if selectedId?.startsWith('settings')} -{:else if $selectedId === 'Input'} +{:else if selectedId === 'Input'} { - $selectedId = 'triggers' + selectionManager.selectId('Trigger') handleSelectTriggerFromKind(triggersState, triggersCount, savedFlow?.path, ev.detail.kind) showCaptureHint.set(true) }} @@ -99,22 +104,22 @@ {onTestFlow} {previewOpen} /> -{:else if $selectedId === 'Result'} +{:else if selectedId === 'Result'} -{:else if $selectedId === 'constants'} +{:else if selectedId === 'constants'} -{:else if $selectedId === 'failure'} +{:else if selectedId === 'failure'} -{:else if $selectedId === 'preprocessor'} +{:else if selectedId === 'preprocessor'} -{:else if $selectedId === 'triggers'} +{:else if selectedId === 'Trigger'} { await insertNewPreprocessorModule(flowStore, flowStateStore, { language: 'bun' }) - $selectedId = 'preprocessor' + selectionManager.selectId('preprocessor') }} on:updateSchema={(e) => { const { payloadData, redirect } = e.detail @@ -122,7 +127,7 @@ previewArgs.val = JSON.parse(JSON.stringify(payloadData)) } if (redirect) { - $selectedId = 'Input' + selectionManager.selectId('Input') $flowInputEditorState.selectedTab = 'captures' $flowInputEditorState.payloadData = payloadData } @@ -141,7 +146,7 @@ schema={flowStore.val.schema} {onDeployTrigger} /> -{:else if $selectedId.startsWith('subflow:')} +{:else if selectedId?.startsWith('subflow:')}
Selected step is witin an expanded subflow and is not directly editable in the flow editor
@@ -150,7 +155,7 @@ {#if dup}
There are duplicate modules in the flow at id: {dup}
{:else} - {#key $selectedId} + {#key selectedId} {#each flowStore.val.value.modules as flowModule, index (flowModule.id ?? index)} ('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) + interface Props { flowModule: FlowModule failureModule?: boolean @@ -215,7 +217,7 @@ let stepHistoryLoader = getStepHistoryLoaderContext() function onSelectedIdChange() { - if (!flowStateStore?.val?.[$selectedId]?.schema && flowModule) { + if (!flowStateStore?.val?.[selectedId]?.schema && flowModule) { reload(flowModule) } } @@ -252,7 +254,7 @@ ) $effect.pre(() => { - $selectedId && untrack(() => onSelectedIdChange()) + selectedId && untrack(() => onSelectedIdChange()) }) let parentLoop = $derived( flowStore.val && flowModule ? checkIfParentLoop(flowStore.val, flowModule.id) : undefined @@ -404,7 +406,7 @@ on:createScriptFromInlineScript={async () => { const [module, state] = await createScriptFromInlineScript( flowModule, - $selectedId, + selectedId, flowStateStore.val[flowModule.id].schema, $pathStore ) @@ -468,7 +470,7 @@ automaticLayout={true} cmdEnterAction={async () => { selected = 'test' - if ($selectedId == flowModule.id) { + if (selectedId == flowModule.id) { if (flowModule.value.type === 'rawscript' && editor) { flowModule.value.content = editor.getCode() } @@ -578,7 +580,7 @@ class="px-2 xl:px-4" bind:this={inputTransformSchemaForm} pickableProperties={stepPropPicker.pickableProperties} - schema={flowStateStore.val[$selectedId]?.schema ?? {}} + schema={flowStateStore.val[selectedId]?.schema ?? {}} previousModuleId={previousModule?.id} bind:args={ () => { @@ -609,7 +611,7 @@ bind:this={modulePreview} mod={flowModule} {noEditor} - schema={flowStateStore.val[$selectedId]?.schema ?? {}} + schema={flowStateStore.val[selectedId]?.schema ?? {}} bind:testJob bind:testIsLoading bind:scriptProgress @@ -623,7 +625,7 @@ active={flowModule.retry !== undefined} label="Retries" /> - {#if !$selectedId.includes('failure')} + {#if !selectedId.includes('failure')} { - $selectedId = 'settings-same-worker' + selectionManager.selectId('settings-same-worker') }} > Set shared directory in the flow settings diff --git a/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte b/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte index e9e15120fc..4a393893bf 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte @@ -20,7 +20,7 @@ let { flowModule = $bindable(), previousModuleId }: Props = $props() - const { selectedId, flowStore, flowStateStore, previewArgs } = + const { selectionManager, flowStore, flowStateStore, previewArgs } = getContext('FlowEditorContext') let schema = $state(emptySchema()) schema.properties['sleep'] = { @@ -41,7 +41,7 @@ ) ) - const result = flowStateStore.val[$selectedId]?.previewResult ?? {} + const result = flowStateStore.val[selectionManager.getSelectedId()]?.previewResult ?? {} let isSleepEnabled = $derived(Boolean(flowModule.sleep)) diff --git a/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte b/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte index fffc702a22..d1d946c5cd 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte @@ -18,8 +18,8 @@ import EditableSchemaDrawer from '$lib/components/schema/EditableSchemaDrawer.svelte' import AddProperty from '$lib/components/schema/AddProperty.svelte' - const { selectedId, flowStateStore } = getContext('FlowEditorContext') - const result = flowStateStore.val[$selectedId]?.previewResult ?? {} + const { selectionManager, flowStateStore } = getContext('FlowEditorContext') + const result = flowStateStore.val[selectionManager.getSelectedId()]?.previewResult ?? {} let editor: SimpleEditor | undefined = $state(undefined) interface Props { diff --git a/frontend/src/lib/components/flows/content/FlowModuleWorkerTagSelect.svelte b/frontend/src/lib/components/flows/content/FlowModuleWorkerTagSelect.svelte index 21b689206c..d724f241e8 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleWorkerTagSelect.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleWorkerTagSelect.svelte @@ -17,7 +17,7 @@ noLabel?: boolean } = $props() - const { flowStore, selectedId } = getContext('FlowEditorContext') + const { flowStore, selectionManager } = getContext('FlowEditorContext') const dispatch = createEventDispatcher() loadWorkerGroups() @@ -44,7 +44,7 @@ diff --git a/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte b/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte index 8af071e7f5..cb50e8a327 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte @@ -23,7 +23,8 @@ import { formatCron } from '$lib/utils' import AgentToolWrapper from './AgentToolWrapper.svelte' - const { selectedId, flowStateStore } = getContext('FlowEditorContext') + const { selectionManager, flowStateStore } = getContext('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) const { triggersState, triggersCount } = getContext('TriggerContext') @@ -113,7 +114,7 @@ } -{#if flowModule.id === $selectedId} +{#if flowModule.id === selectedId} {#if flowModule.value.type === 'forloopflow'} {:else if flowModule.value.type === 'whileloopflow'} @@ -123,13 +124,13 @@ {:else if flowModule.value.type === 'branchall'} {:else if flowModule.value.type === 'identity'} - {#if $selectedId == 'failure'} + {#if selectedId == 'failure'}
If defined, the error handler will take the error as input.
- {:else if $selectedId == 'preprocessor'} + {:else if selectedId == 'preprocessor'}
{ const { path, summary, kind, hash } = detail createModuleFromScript(path, summary, kind, hash) @@ -187,8 +188,8 @@ flowModule = module flowStateStore.val[module.id] = state }} - failureModule={$selectedId === 'failure'} - preprocessorModule={$selectedId === 'preprocessor'} + failureModule={selectedId === 'failure'} + preprocessorModule={selectedId === 'preprocessor'} /> {/if} {:else if flowModule.value.type === 'rawscript' || flowModule.value.type === 'script' || flowModule.value.type === 'flow' || flowModule.value.type === 'aiagent'} @@ -197,8 +198,8 @@ bind:flowModule {parentModule} {previousModule} - failureModule={$selectedId === 'failure'} - preprocessorModule={$selectedId === 'preprocessor'} + failureModule={selectedId === 'failure'} + preprocessorModule={selectedId === 'preprocessor'} {scriptKind} {scriptTemplate} {enableAi} @@ -225,7 +226,7 @@ /> {/each} {:else if flowModule.value.type === 'branchone'} - {#if $selectedId === `${flowModule?.id}-branch-default`} + {#if selectedId === `${flowModule?.id}-branch-default`}

Default branch

Nothing to configure, this is the default branch if none of the predicates are met. @@ -247,7 +248,7 @@ {/each} {/if} {#each flowModule.value.branches as branch, branchIndex (branchIndex)} - {#if $selectedId === `${flowModule?.id}-branch-${branchIndex}`} + {#if selectedId === `${flowModule?.id}-branch-${branchIndex}`} {:else} {#each branch.modules as _, index} @@ -295,7 +296,7 @@ {/each} {:else if flowModule.value.type === 'aiagent'} {#each flowModule.value.tools as tool, toolIndex (toolIndex)} - {#if $selectedId === tool.id} + {#if selectedId === tool.id} + import FlowCard from '../common/FlowCard.svelte' + import type { SelectionManager } from '$lib/components/graph/selectionUtils.svelte' + import { Button } from '$lib/components/common' + import { getNoteEditorContext } from '$lib/components/graph/noteEditor.svelte' + import { StickyNote } from 'lucide-svelte' + + interface Props { + selectionManager: SelectionManager + noEditor: boolean + } + let { selectionManager, noEditor }: Props = $props() + + const noteEditorContext = getNoteEditorContext() + + function addGroupNote() { + if (selectionManager.selectedIds.length > 0 && noteEditorContext?.noteEditor) { + // Create the group note + noteEditorContext.noteEditor.createGroupNote(selectionManager.selectedIds) + } + } + + + + {#snippet action()} + + {/snippet} +
+

{selectionManager.selectedIds.length} nodes selected

+
+ {#each selectionManager.selectedIds as nodeId} +
+ {nodeId} +
+ {/each} +
+
+
diff --git a/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte b/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte index d349b05500..64c4416d45 100644 --- a/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte +++ b/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte @@ -25,7 +25,7 @@ localModuleStates = $bindable({}) }: Props = $props() - const { selectedId } = getContext('FlowEditorContext') + const { selectionManager } = getContext('FlowEditorContext') let flowPreviewContent: FlowPreviewContent | undefined = $state(undefined) let preventEscape = $state(false) @@ -70,7 +70,7 @@ $state('timeline') let upToDisabled = $derived.by(() => { - const upToSelected = upToId ?? $selectedId + const upToSelected = upToId ?? selectionManager.getSelectedId() return ( upToSelected == undefined || [ @@ -92,7 +92,7 @@ 'constants', 'Result', 'Input', - 'triggers' + 'Trigger' ].includes(upToSelected) || upToSelected?.includes('branch') || aiChatManager.flowAiChatHelpers?.getModuleAction(upToSelected) === 'removed' @@ -144,8 +144,8 @@ dropdownItems={!upToDisabled ? [ { - label: 'Test up to ' + $selectedId, - onClick: () => testUpTo($selectedId, true) + label: 'Test up to ' + selectionManager.getSelectedId(), + onClick: () => testUpTo(selectionManager.getSelectedId(), true) } ] : undefined} diff --git a/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte b/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte index 2a80a34f37..c9d681da50 100644 --- a/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte +++ b/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte @@ -29,7 +29,7 @@ generateStep: { moduleId: string; instructions: string; lang: ScriptLang } }>() - const { selectedId, flowStateStore, flowStore } = + const { selectionManager, flowStateStore, flowStore } = getContext('FlowEditorContext') async function insertFailureModule( @@ -50,7 +50,7 @@ }) } - $selectedId = 'failure' + selectionManager.selectId('failure') refreshStateStore(flowStore) } @@ -70,10 +70,10 @@ aiModuleActionToTextColor(action) )} id="flow-editor-error-handler" - selected={$selectedId?.includes('failure')} + selected={selectionManager.getSelectedId()?.includes('failure')} onClick={() => { if (flowStore.val?.value?.failure_module) { - $selectedId = 'failure' + selectionManager.selectId('failure') } }} > @@ -95,7 +95,7 @@ class="ml-1" onclick={() => { flowStore.val.value.failure_module = undefined - $selectedId = 'settings-metadata' + selectionManager.selectId('settings-metadata') }} > diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte index 494715ad29..75a12d1e11 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte @@ -281,7 +281,7 @@ style="width: 275px; height: 34px;" onmouseenter={() => (hover = true)} onmouseleave={() => (hover = false)} - onpointerdown={stopPropagation(preventDefault(() => dispatch('pointerdown')))} + onpointerdown={stopPropagation(preventDefault((e) => dispatch('pointerdown', e)))} > {#if deletable} diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index 0b0bcdb778..1b7af36c41 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -42,6 +42,7 @@ import { ModulesTestStates } from '$lib/components/modulesTest.svelte' import type { StateStore } from '$lib/utils' import { type AgentTool, flowModuleToAgentTool, createMcpTool } from '../agentToolUtils' + import { getNoteEditorContext } from '$lib/components/graph/noteEditor.svelte' interface Props { sidebarSize?: number | undefined @@ -105,12 +106,15 @@ let flowTutorials: FlowTutorials | undefined = $state(undefined) - const { customUi, selectedId, moving, history, flowStateStore, flowStore, pathStore } = + const { customUi, selectionManager, moving, history, flowStateStore, flowStore, pathStore } = getContext('FlowEditorContext') const { triggersCount, triggersState } = getContext('TriggerContext') const { flowPropPickerConfig } = getContext('PropPickerContext') + // Get NoteEditor context for note position updates + const noteEditorContext = getNoteEditorContext() + export async function insertNewModuleAtIndex( modules: FlowModule[] | AgentTool[], index: number, @@ -238,9 +242,9 @@ let allIds = dfs(flowStore.val.value.modules, (mod) => mod.id) if (allIds.length > 1) { const idx = allIds.indexOf(id) - $selectedId = idx == 0 ? allIds[0] : allIds[idx - 1] + selectionManager.selectId(idx == 0 ? allIds[0] : allIds[idx - 1]) } else { - $selectedId = 'settings-metadata' + selectionManager.selectId('settings-metadata') } } } @@ -290,10 +294,19 @@ let dependents: Record = $state({}) let graph: FlowGraphV2 | undefined = $state(undefined) + let noteMode = $state(false) export function isNodeVisible(nodeId: string): boolean { return graph?.isNodeVisible(nodeId) ?? false } + export function enableNotes(): void { + graph?.enableNotes?.() + } + + function toggleNoteMode() { + noteMode = !noteMode + } + function shouldRunTutorial(tutorialName: string, name: string, index: number) { return ( $tutorialsToDo.includes(index) && @@ -400,6 +413,8 @@ on:generateStep {aiChatOpen} {toggleAiChat} + {noteMode} + {toggleNoteMode} />
@@ -418,8 +433,10 @@ moving={$moving?.id} maxHeight={minHeight} modules={flowStore.val.value.modules} + {noteMode} + notes={flowStore.val.value.notes} preprocessorModule={flowStore.val.value?.preprocessor_module} - {selectedId} + {selectionManager} {workspace} editMode {onTestUpTo} @@ -438,7 +455,7 @@ const cb = () => { push(history, flowStore.val) if (id === 'preprocessor') { - $selectedId = 'Input' + selectionManager.selectId('Input') flowStore.val.value.preprocessor_module = undefined } else { selectNextId(id) @@ -497,7 +514,7 @@ let [removedModule] = originalModules.splice(indexToRemove, 1) targetModules.splice(detail.index, 0, removedModule) - $selectedId = removedModule.id + selectionManager.selectId(removedModule.id) $moving = undefined } else { if (detail.isPreprocessor) { @@ -507,7 +524,7 @@ detail.inlineScript, detail.script ) - $selectedId = 'preprocessor' + selectionManager.selectId('preprocessor') if (detail.inlineScript?.instructions) { dispatch('generateStep', { @@ -534,7 +551,7 @@ toolKind ) const id = targetModules[index].id - $selectedId = id + selectionManager.selectId(id) if (detail.inlineScript?.instructions) { dispatch('generateStep', { @@ -619,13 +636,13 @@ flowStateStore.val[newId] = flowStateStore.val[id] delete flowStateStore.val[id] refreshStateStore(flowStore) - $selectedId = newId + selectionManager.selectId(newId) }} onDeleteBranch={async ({ id, index }) => { if (id) { await removeBranch(id, index) refreshStateStore(flowStore) - $selectedId = id + selectionManager.selectId(id) } }} onMove={(id) => { @@ -645,6 +662,14 @@ {onCancelTestFlow} {onOpenPreview} {onHideJobStatus} + exitNoteMode={() => (noteMode = false)} + onNotePositionUpdate={(noteId, position) => { + // Update note position via NoteEditor context in edit mode + if (noteEditorContext?.noteEditor) { + noteEditorContext.noteEditor.updatePosition(noteId, position) + } + }} + multiSelectEnabled />
diff --git a/frontend/src/lib/components/flows/map/FlowStickyNode.svelte b/frontend/src/lib/components/flows/map/FlowStickyNode.svelte index 48e7445593..b1821f98e3 100644 --- a/frontend/src/lib/components/flows/map/FlowStickyNode.svelte +++ b/frontend/src/lib/components/flows/map/FlowStickyNode.svelte @@ -2,7 +2,7 @@ import type { FlowEditorContext } from '../types' import { getContext } from 'svelte' import { Badge } from '$lib/components/common' - import { DollarSign, Settings } from 'lucide-svelte' + import { DollarSign, Settings, StickyNote } from 'lucide-svelte' import FlowErrorHandlerItem from './FlowErrorHandlerItem.svelte' import FlowAIButton from '$lib/components/copilot/chat/flow/FlowAIButton.svelte' import Popover from '$lib/components/Popover.svelte' @@ -15,6 +15,8 @@ aiChatOpen?: boolean showFlowAiButton?: boolean toggleAiChat?: () => void + noteMode?: boolean + toggleNoteMode?: () => void disableAi?: boolean } @@ -25,10 +27,13 @@ aiChatOpen, showFlowAiButton, toggleAiChat, + noteMode, + toggleNoteMode, disableAi }: Props = $props() - const { selectedId, flowStore } = getContext('FlowEditorContext') + const { selectionManager, flowStore } = getContext('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId())
@@ -37,10 +42,10 @@ unifiedSize="sm" wrapperClasses="min-w-36" startIcon={{ icon: Settings }} - selected={$selectedId?.startsWith('settings')} + selected={selectedId?.startsWith('settings')} variant="default" title="Settings" - onClick={() => ($selectedId = 'settings')} + onClick={() => selectionManager.selectId('settings')} > Settings {#if flowStore.val.value.same_worker} @@ -60,10 +65,10 @@ wrapperClasses="h-full" unifiedSize="sm" startIcon={{ icon: DollarSign }} - selected={$selectedId === 'constants'} + selected={selectedId === 'constants'} variant="default" iconOnly - onClick={() => ($selectedId = 'constants')} + onClick={() => selectionManager.selectId('constants')} /> {#snippet text()} Environment Variables @@ -83,4 +88,17 @@ {/snippet} {/if} + + + {#snippet text()} + {noteMode ? 'Exit note mode' : 'Add sticky notes'} + {/snippet} +
diff --git a/frontend/src/lib/components/flows/map/MapItem.svelte b/frontend/src/lib/components/flows/map/MapItem.svelte index 2021b10e91..08d88f26aa 100644 --- a/frontend/src/lib/components/flows/map/MapItem.svelte +++ b/frontend/src/lib/components/flows/map/MapItem.svelte @@ -2,7 +2,6 @@ import { Button } from '$lib/components/common' import type { FlowModule, Job } from '$lib/gen' import { createEventDispatcher, getContext } from 'svelte' - import type { Writable } from 'svelte/store' import FlowModuleSchemaItem from './FlowModuleSchemaItem.svelte' import FlowModuleIcon from '../FlowModuleIcon.svelte' import { prettyLanguage } from '$lib/common' @@ -17,6 +16,7 @@ import { twMerge } from 'tailwind-merge' import type { FlowNodeState } from '$lib/components/graph' import type { AIModuleAction } from '$lib/components/copilot/chat/flow/core' + import { getGraphContext } from '$lib/components/graph/graphContext' interface Props { moduleId: string @@ -74,9 +74,7 @@ maximizeSubflow }: Props = $props() - const { selectedId } = getContext<{ - selectedId: Writable - }>('FlowGraphContext') + const { selectionManager } = getGraphContext() const { flowStore } = getContext('FlowEditorContext') || {} @@ -88,7 +86,7 @@ }>() let itemProps = $derived({ - selected: $selectedId === mod.id, + selected: selectionManager && selectionManager.isNodeSelected(mod.id), retry: mod.retry?.constant != undefined || mod.retry?.exponential != undefined, earlyStop: mod.stop_after_if != undefined || mod.stop_after_all_iters_if != undefined, skip: Boolean(mod.skip_if), @@ -102,6 +100,13 @@ let parentLoop = $derived( flowStore?.val && mod ? checkIfParentLoop(flowStore.val, mod.id) : undefined ) + + function handlePointerDown(e: CustomEvent) { + // Only handle left clicks (button 0) + if (e.detail.button === 0) { + onSelect(mod.id) + } + } {#if mod} @@ -164,7 +169,7 @@ on:changeId on:move on:delete - on:pointerdown={() => onSelect(mod.id)} + on:pointerdown={handlePointerDown} onUpdateMock={(mock) => { mod.mock = mock onUpdateMock?.({ id: mod.id, mock }) @@ -193,7 +198,7 @@ on:changeId on:delete on:move - on:pointerdown={() => onSelect(mod.id)} + on:pointerdown={handlePointerDown} {...itemProps} id={mod.id} label={mod.summary || 'Run one branch'} @@ -213,7 +218,7 @@ on:changeId on:delete on:move - on:pointerdown={() => onSelect(mod.id)} + on:pointerdown={handlePointerDown} id={mod.id} {...itemProps} label={mod.summary || `Run all branches${mod.value.parallel ? ' (parallel)' : ''}`} @@ -231,7 +236,7 @@ {moduleAction} {onShowModuleDiff} on:changeId - on:pointerdown={() => onSelect(mod.id)} + on:pointerdown={handlePointerDown} on:delete on:move onUpdateMock={(mock) => { diff --git a/frontend/src/lib/components/flows/types.ts b/frontend/src/lib/components/flows/types.ts index 733cd304aa..6ed09a1436 100644 --- a/frontend/src/lib/components/flows/types.ts +++ b/frontend/src/lib/components/flows/types.ts @@ -15,6 +15,8 @@ import type ResourceEditorDrawer from '../ResourceEditorDrawer.svelte' import type { ModulesTestStates } from '../modulesTest.svelte' import type { ButtonProp } from '$lib/components/DiffEditor.svelte' +import type { SelectionManager } from '../graph/selectionUtils.svelte' + export type FlowInput = Record< string, { @@ -28,6 +30,7 @@ export type FlowInput = Record< } > +// Extended OpenFlow with additional properties not in the core spec export type ExtendedOpenFlow = OpenFlow & { tag?: string ws_error_handler_muted?: boolean @@ -68,7 +71,7 @@ export type CurrentEditor = | undefined export type FlowEditorContext = { - selectedId: Writable + selectionManager: SelectionManager currentEditor: Writable moving: Writable<{ id: string } | undefined> previewArgs: StateStore> diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 3b41cf2035..26a3ed2aed 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -1,7 +1,7 @@ {#if insertable} @@ -589,8 +815,9 @@ {/if}
{#if graph?.error}
@@ -615,14 +842,29 @@ bind:this={viewportSynchronizer} /> {/if} + { + onpaneclick={() => { document.dispatchEvent(new Event('focus')) + selectionManager.clearSelection() + }} + onpanecontextmenu={({ event }) => { + paneContextMenu?.onPaneContextMenu(event) + }} + onnodedragstop={(event) => { + const node = event.targetNode + if (node && node.type === 'note') { + const positionWithOffset = { + x: node.position.x, + y: node.position.y - yOffset + } + onNotePositionUpdate?.(node.id, positionWithOffset) + } }} onmove={(event, viewport) => { viewportSynchronizer?.handleLocalViewportChange(event, viewport) }} - {nodes} + nodes={nodesWithOffset} {edges} {edgeTypes} {nodeTypes} @@ -633,26 +875,85 @@ connectionLineType={ConnectionLineType.SmoothStep} defaultEdgeOptions={{ type: 'smoothstep' }} preventScrolling={scroll} + selectionOnDrag={selectionManager.mode === 'rect-select'} + elementsSelectable={true} + selectionMode={SelectionMode.Partial} + selectionKey={selectionManager.mode === 'rect-select' || !editMode ? null : modifierKey} + panActivationKey={selectionManager.mode === 'rect-select' ? modifierKey : null} + panOnDrag={selectionManager.mode === 'rect-select' ? [1] : true} zoomOnDoubleClick={false} - elementsSelectable={false} + elevateNodesOnSelect={false} {proOptions} + multiSelectionKey={'Shift'} nodesDraggable={false} --background-color={false} >
+ + {#if noteMode} + + {/if} + + {#if multiSelectEnabled} + + {/if} + + + + {#if leftHeader}
{@render leftHeader()}
{:else} + {#if multiSelectEnabled} +
+ + { + selectionManager.mode = + selectionManager.mode === 'normal' ? 'rect-select' : 'normal' + }} + > + {#if selectionManager.mode === 'rect-select'} + + {:else} + + {/if} + + {#snippet text()} +
+
+ + Grab: Click and drag to pan. Hold + {getModifierKey()} to box select. +
+
+ + Select Click and drag to box + select. Hold + {getModifierKey()} to pan. +
+
+ {/snippet} +
+
+ {/if} {#if download} { try { localStorage.setItem( 'svelvet', - encodeState({ modules, failureModule, preprocessorModule }) + encodeState({ modules, failureModule, preprocessorModule, notes }) ) } catch (e) { console.error('error interacting with local storage', e) @@ -678,6 +979,9 @@ {#if !hideAssetsToggle} {/if} + {#if !hideNotesToggle} + + {/if} {#if showDataflow} {/if} @@ -703,4 +1007,9 @@ :global(.svelte-flow__edgelabel-renderer) { @apply z-50; } + + :global(.svelte-flow__selection) { + display: none; + pointer-events: none; + } diff --git a/frontend/src/lib/components/graph/NodeContextMenu.svelte b/frontend/src/lib/components/graph/NodeContextMenu.svelte new file mode 100644 index 0000000000..034fc2d7da --- /dev/null +++ b/frontend/src/lib/components/graph/NodeContextMenu.svelte @@ -0,0 +1,47 @@ + + +{#if noteEditorContext?.noteEditor && selectedNodeIds.length > 1} + + {@render children()} + +{/if} diff --git a/frontend/src/lib/components/graph/NoteColorPicker.svelte b/frontend/src/lib/components/graph/NoteColorPicker.svelte new file mode 100644 index 0000000000..d17adb9317 --- /dev/null +++ b/frontend/src/lib/components/graph/NoteColorPicker.svelte @@ -0,0 +1,50 @@ + + + + {#snippet trigger()} + + {/each} +
+ {/snippet} + diff --git a/frontend/src/lib/components/graph/NoteTool.svelte b/frontend/src/lib/components/graph/NoteTool.svelte new file mode 100644 index 0000000000..ad41bd974a --- /dev/null +++ b/frontend/src/lib/components/graph/NoteTool.svelte @@ -0,0 +1,216 @@ + + + +
{ + // Capture the position when context menu is triggered + const flowPosition = screenToFlowPosition({ + x: e.clientX, + y: e.clientY + }) + contextMenuPosition = { + x: flowPosition.x, + y: flowPosition.y - yOffset + } + }} + role="button" + tabindex="0" + aria-label="Click and drag to create a note, or right-click to add a sticky note" + onkeydown={(e) => { + if (e.key === 'Escape') { + if (isDrawing) { + // Cancel current drawing + isDrawing = false + startPosition = null + } else { + // Exit note mode + exitNoteMode?.() + } + } + }} + > + + {#if previewNote} +
+
+ {/if} +
+
+ + diff --git a/frontend/src/lib/components/graph/PaneContextMenu.svelte b/frontend/src/lib/components/graph/PaneContextMenu.svelte new file mode 100644 index 0000000000..b6b6b49ac9 --- /dev/null +++ b/frontend/src/lib/components/graph/PaneContextMenu.svelte @@ -0,0 +1,115 @@ + + +{#if contextMenuVisible} + + + + + +{/if} diff --git a/frontend/src/lib/components/graph/SelectionBoundingBox.svelte b/frontend/src/lib/components/graph/SelectionBoundingBox.svelte new file mode 100644 index 0000000000..5faff92d28 --- /dev/null +++ b/frontend/src/lib/components/graph/SelectionBoundingBox.svelte @@ -0,0 +1,82 @@ + + +{#if bounds() && selectedNodes.length > 1} + {@const currentBounds = bounds()!} + +
+ + {#if noteEditorContext?.noteEditor} +
+ +
+ {/if} +
+
+{/if} diff --git a/frontend/src/lib/components/graph/SelectionTool.svelte b/frontend/src/lib/components/graph/SelectionTool.svelte new file mode 100644 index 0000000000..e7af55bc24 --- /dev/null +++ b/frontend/src/lib/components/graph/SelectionTool.svelte @@ -0,0 +1,43 @@ + + + +{#if store.selectionRect} + {@const bounds = store.selectionRect!} +
+
+{/if} diff --git a/frontend/src/lib/components/graph/graphBuilder.svelte.ts b/frontend/src/lib/components/graph/graphBuilder.svelte.ts index a6f0ebdd6a..6dded81b9c 100644 --- a/frontend/src/lib/components/graph/graphBuilder.svelte.ts +++ b/frontend/src/lib/components/graph/graphBuilder.svelte.ts @@ -88,6 +88,7 @@ export type NodeLayout = { data: { offset?: number } + selectable?: boolean } & FlowNode export type FlowNode = @@ -447,7 +448,8 @@ export function graphBuilder( moduleAction: extra.moduleActions?.[module.id], onShowModuleDiff: extra.onShowModuleDiff }, - type: 'module' + type: 'module', + selectable: true }) return module.id @@ -540,7 +542,8 @@ export function graphBuilder( ...extra, insertable: extra.insertable && !options?.disableInsert && prefix == undefined, shouldOffsetInsertBtnDueToAssetNode: nodeIdsWithOutputAssets.has(sourceId) - } + }, + selectable: false }) } @@ -596,7 +599,7 @@ export function graphBuilder( } const resultNode: NodeLayout = { - id: 'result', + id: 'Result', data: { eventHandlers: eventHandlers, success: success, @@ -1085,14 +1088,14 @@ export function graphBuilder( let pid = x[0] if (input?.startsWith('flow_input.iter')) { - const parent = dfsByModule(selectedId!, modules ?? [])?.pop() + const parent = dfsByModule(selectedId, modules ?? [])?.pop() if (parent?.id) { pid = parent.id } } - addEdge(pid, selectedId!, undefined, undefined, { + addEdge(pid, selectedId, undefined, undefined, { customId: `dep-${pid}-${selectedId}-${input}-${index}`, type: 'dataflowedge' }) @@ -1102,7 +1105,7 @@ export function graphBuilder( Object.entries(deps.dependents).forEach((x, i) => { let pid = x[0] - addEdge(selectedId!, pid, undefined, undefined, { + addEdge(selectedId, pid, undefined, undefined, { customId: `dep-${selectedId}-${pid}-${i}`, type: 'dataflowedge' }) diff --git a/frontend/src/lib/components/graph/graphContext.ts b/frontend/src/lib/components/graph/graphContext.ts new file mode 100644 index 0000000000..176fc5d3bf --- /dev/null +++ b/frontend/src/lib/components/graph/graphContext.ts @@ -0,0 +1,19 @@ +import { getContext, setContext } from 'svelte' +import type { SelectionManager } from './selectionUtils.svelte' +import type { NoteManager } from './noteManager.svelte' +import type { Writable } from 'svelte/store' + +export type GraphContext = { + selectionManager: SelectionManager + useDataflow: Writable + showAssets: Writable + noteManager?: NoteManager + clearFlowSelection?: () => void + yOffset?: number +} + +const graphContextKey = 'FlowGraphContext' + +//TODO: use https://svelte.dev/docs/svelte/context#Type-safe-context after migrating svelte 5 to latest version +export const getGraphContext = () => getContext(graphContextKey) +export const setGraphContext = (context: GraphContext) => setContext(graphContextKey, context) diff --git a/frontend/src/lib/components/graph/groupDetectionUtils.ts b/frontend/src/lib/components/graph/groupDetectionUtils.ts new file mode 100644 index 0000000000..e2c81c4d98 --- /dev/null +++ b/frontend/src/lib/components/graph/groupDetectionUtils.ts @@ -0,0 +1,86 @@ +type FlowNode = { id: string; parentIds?: string[] } + +/** + * Use a simple algorithm to complete a group and split it into connected components + */ +export function completeAndSplitGroup(groupNodes: string[], flowNodes: FlowNode[]): string[][] { + if (groupNodes.length <= 1) { + return groupNodes.length === 1 ? [groupNodes] : [] + } + + // Build parent map for upward traversal only + const parents = new Map() + for (const node of flowNodes) { + parents.set(node.id, node.parentIds || []) + } + + const groupSet = new Set(groupNodes) + const assignedComponent = new Map() + const components: Array> = [] + + const mergeComponents = (fromIdx: number, toIdx: number): void => { + if (fromIdx === toIdx) return + const target = components[toIdx] + const source = components[fromIdx] + + source.forEach((node) => target.add(node)) + source.clear() + + for (const [nodeId, idx] of assignedComponent.entries()) { + if (idx === fromIdx) { + assignedComponent.set(nodeId, toIdx) + } + } + } + + for (const startNode of groupNodes) { + if (assignedComponent.has(startNode)) continue + + const componentIdx = components.length + components.push(new Set([startNode])) + assignedComponent.set(startNode, componentIdx) + + const stack: { nodeId: string; path: string[]; seen: Set }[] = [ + { nodeId: startNode, path: [startNode], seen: new Set([startNode]) } + ] + + while (stack.length > 0) { + const { nodeId, path, seen } = stack.pop()! + const parentIds = parents.get(nodeId) || [] + + for (const parentId of parentIds) { + if (seen.has(parentId)) continue + + const newPath = [...path, parentId] + const newSeen = new Set(seen) + newSeen.add(parentId) + + if (groupSet.has(parentId)) { + const existingIdx = assignedComponent.get(parentId) + if (existingIdx === undefined) { + assignedComponent.set(parentId, componentIdx) + components[componentIdx].add(parentId) + stack.push({ nodeId: parentId, path: [parentId], seen: new Set([parentId]) }) + } else if (existingIdx !== componentIdx) { + mergeComponents(existingIdx, componentIdx) + } + + for (const node of newPath) { + components[componentIdx].add(node) + } + } else { + stack.push({ nodeId: parentId, path: newPath, seen: newSeen }) + } + } + } + } + + return components + .filter((component) => component.size > 0) + .map((component) => + Array.from(component) + .filter((nodeId) => !nodeId.startsWith('subflow:')) + .sort() + ) + .filter((component) => component.length > 0) +} diff --git a/frontend/src/lib/components/graph/noteColors.ts b/frontend/src/lib/components/graph/noteColors.ts new file mode 100644 index 0000000000..f9024adf7e --- /dev/null +++ b/frontend/src/lib/components/graph/noteColors.ts @@ -0,0 +1,135 @@ +// Note color definitions with Tailwind classes for light and dark mode +export enum NoteColor { + YELLOW = 'yellow', + BLUE = 'blue', + GREEN = 'green', + PURPLE = 'purple', + PINK = 'pink', + ORANGE = 'orange', + RED = 'red', + CYAN = 'cyan', + LIME = 'lime', + GRAY = 'gray' +} + +export interface NoteColorConfig { + background: string + outline: string + outlineHover: string + text: string + hover: string +} + +// Color configurations for each note color with dark mode support +export const NOTE_COLORS: Record = { + [NoteColor.YELLOW]: { + background: 'bg-yellow-200 dark:bg-yellow-900', + outline: 'outline-yellow-300 dark:outline-yellow-600', + outlineHover: 'outline-yellow-300/60 dark:outline-yellow-600/60', + text: 'text-yellow-900 dark:text-yellow-100', + hover: 'hover:bg-yellow-200 dark:hover:bg-yellow-800' + }, + [NoteColor.BLUE]: { + background: 'bg-blue-100 dark:bg-blue-950', + outline: 'outline-blue-300 dark:outline-blue-600', + outlineHover: 'outline-blue-300/60 dark:outline-blue-600/60', + text: 'text-blue-900 dark:text-blue-100', + hover: 'hover:bg-blue-200 dark:hover:bg-blue-800' + }, + [NoteColor.GREEN]: { + background: 'bg-green-200 dark:bg-green-900', + outline: 'outline-green-300 dark:outline-green-600', + outlineHover: 'outline-green-300/60 dark:outline-green-600/60', + text: 'text-green-900 dark:text-green-100', + hover: 'hover:bg-green-200 dark:hover:bg-green-800' + }, + [NoteColor.PURPLE]: { + background: 'bg-purple-200 dark:bg-purple-900', + outline: 'outline-purple-300 dark:outline-purple-600', + outlineHover: 'outline-purple-300/60 dark:outline-purple-600/60', + text: 'text-purple-900 dark:text-purple-100', + hover: 'hover:bg-purple-200 dark:hover:bg-purple-800' + }, + [NoteColor.PINK]: { + background: 'bg-pink-200 dark:bg-pink-900', + outline: 'outline-pink-300 dark:outline-pink-600', + outlineHover: 'outline-pink-300/60 dark:outline-pink-600/60', + text: 'text-pink-900 dark:text-pink-100', + hover: 'hover:bg-pink-200 dark:hover:bg-pink-800' + }, + [NoteColor.ORANGE]: { + background: 'bg-orange-200 dark:bg-orange-900', + outline: 'outline-orange-300 dark:outline-orange-600', + outlineHover: 'outline-orange-300/60 dark:outline-orange-600/60', + text: 'text-orange-900 dark:text-orange-100', + hover: 'hover:bg-orange-200 dark:hover:bg-orange-800' + }, + [NoteColor.RED]: { + background: 'bg-red-200 dark:bg-red-900', + outline: 'outline-red-300 dark:outline-red-600', + outlineHover: 'outline-red-300/60 dark:outline-red-600/60', + text: 'text-red-900 dark:text-red-100', + hover: 'hover:bg-red-200 dark:hover:bg-red-800' + }, + [NoteColor.CYAN]: { + background: 'bg-cyan-200 dark:bg-cyan-900', + outline: 'outline-cyan-300 dark:outline-cyan-600', + outlineHover: 'outline-cyan-300/60 dark:outline-cyan-600/60', + text: 'text-cyan-900 dark:text-cyan-100', + hover: 'hover:bg-cyan-200 dark:hover:bg-cyan-800' + }, + [NoteColor.LIME]: { + background: 'bg-lime-200 dark:bg-lime-900', + outline: 'outline-lime-300 dark:outline-lime-600', + outlineHover: 'outline-lime-300/60 dark:outline-lime-600/60', + text: 'text-lime-900 dark:text-lime-100', + hover: 'hover:bg-lime-200 dark:hover:bg-lime-800' + }, + [NoteColor.GRAY]: { + background: 'bg-gray-200 dark:bg-gray-800', + outline: 'outline-gray-300 dark:outline-gray-600', + outlineHover: 'outline-gray-300/60 dark:outline-gray-600/60', + text: 'text-gray-900 dark:text-gray-100', + hover: 'hover:bg-gray-200 dark:hover:bg-gray-700' + } +} + +// Color swatch colors for the picker (solid colors for the palette dots) +export const NOTE_COLOR_SWATCHES: Record = { + [NoteColor.YELLOW]: 'bg-yellow-400', + [NoteColor.BLUE]: 'bg-blue-400', + [NoteColor.GREEN]: 'bg-green-400', + [NoteColor.PURPLE]: 'bg-purple-400', + [NoteColor.PINK]: 'bg-pink-400', + [NoteColor.ORANGE]: 'bg-orange-400', + [NoteColor.RED]: 'bg-red-400', + [NoteColor.CYAN]: 'bg-cyan-400', + [NoteColor.LIME]: 'bg-lime-400', + [NoteColor.GRAY]: 'bg-gray-400' +} + +// Default note color +export const DEFAULT_NOTE_COLOR = NoteColor.GREEN +export const DEFAULT_GROUP_NOTE_COLOR = NoteColor.BLUE + +/** + * Get the next available color that's not in the used colors set + * Cycles through all available colors in order + */ +export function getNextAvailableColor(usedColors: Set): NoteColor { + const allColors = Object.values(NoteColor) + + // Find first unused color + for (const color of allColors) { + if (!usedColors.has(color)) { + return color + } + } + + // If all colors are used, return the default + return DEFAULT_GROUP_NOTE_COLOR +} + +// Minimum note size constraints +export const MIN_NOTE_WIDTH = 275 +export const MIN_NOTE_HEIGHT = 60 diff --git a/frontend/src/lib/components/graph/noteEditor.svelte.ts b/frontend/src/lib/components/graph/noteEditor.svelte.ts new file mode 100644 index 0000000000..4d59cfa480 --- /dev/null +++ b/frontend/src/lib/components/graph/noteEditor.svelte.ts @@ -0,0 +1,322 @@ +import type { FlowNote } from '$lib/gen' +import type { StateStore } from '$lib/utils' +import type { ExtendedOpenFlow } from '../flows/types' +import type { NoteColor } from './noteColors' +import { DEFAULT_GROUP_NOTE_COLOR, getNextAvailableColor } from './noteColors' +import { generateId } from './util' +import { getContext, setContext } from 'svelte' +import { completeAndSplitGroup } from './groupDetectionUtils' + +/** + * Utility class for editing flow notes via direct flowStore mutations + * This class is designed to be used in editor contexts via Svelte context + */ +export class NoteEditor { + private flowStore: StateStore + private onNoteAdded?: () => void + + constructor(flowStore: StateStore, onNoteAdded?: () => void) { + this.flowStore = flowStore + this.onNoteAdded = onNoteAdded + } + + /** + * Get the current notes array from the flow store + */ + private getNotes(): FlowNote[] { + return this.flowStore.val.value?.notes || [] + } + + /** + * Set the notes array in the flow store + */ + private setNotes(notes: FlowNote[]): void { + if (this.flowStore.val.value) { + this.flowStore.val.value.notes = notes + } + } + + /** + * Add a new note to the flow + */ + addNote(note: Omit): string { + const notes = this.getNotes() + const newNote: FlowNote = { + id: generateId(), + ...note + } + this.setNotes([...notes, newNote]) + + // Call callback to enable notes display when a note is created + this.onNoteAdded?.() + + return newNote.id + } + + /** + * Update the text content of a note + */ + updateText(noteId: string, text: string): void { + const notes = this.getNotes() + const updatedNotes = notes.map((note) => (note.id === noteId ? { ...note, text } : note)) + this.setNotes(updatedNotes) + } + + /** + * Update the color of a note + */ + updateColor(noteId: string, color: NoteColor): void { + const notes = this.getNotes() + const updatedNotes = notes.map((note) => (note.id === noteId ? { ...note, color } : note)) + this.setNotes(updatedNotes) + } + + /** + * Update the position of a note + */ + updatePosition(noteId: string, position: { x: number; y: number }): void { + const notes = this.getNotes() + const updatedNotes = notes.map((note) => (note.id === noteId ? { ...note, position } : note)) + this.setNotes(updatedNotes) + } + + /** + * Update the size of a note + */ + updateSize(noteId: string, size: { width: number; height: number }): void { + const notes = this.getNotes() + const updatedNotes = notes.map((note) => (note.id === noteId ? { ...note, size } : note)) + this.setNotes(updatedNotes) + } + + /** + * Toggle the locked state of a note + */ + updateLock(noteId: string, locked: boolean): void { + const notes = this.getNotes() + const updatedNotes = notes.map((note) => (note.id === noteId ? { ...note, locked } : note)) + this.setNotes(updatedNotes) + } + + /** + * Delete a note from the flow + */ + deleteNote(noteId: string): void { + const notes = this.getNotes() + const updatedNotes = notes.filter((note) => note.id !== noteId) + this.setNotes(updatedNotes) + } + + /** + * Find which nodes from the given list are already in existing group notes + */ + private findNodesInExistingGroups(nodeIds: string[]): { + overlappingGroups: FlowNote[] + nodesInGroups: Set + } { + const notes = this.getNotes() + const groupNotes = notes.filter((note) => note.type === 'group') + const overlappingGroups: FlowNote[] = [] + const nodesInGroups = new Set() + + for (const groupNote of groupNotes) { + const containedNodeIds = groupNote.contained_node_ids || [] + const hasOverlap = nodeIds.some((nodeId) => containedNodeIds.includes(nodeId)) + + if (hasOverlap) { + overlappingGroups.push(groupNote) + containedNodeIds.forEach((nodeId) => nodesInGroups.add(nodeId)) + } + } + + return { overlappingGroups, nodesInGroups } + } + + /** + * Get smart color for group note based on existing groups + */ + private getSmartGroupNoteColor(nodeIds: string[]): NoteColor { + const { overlappingGroups } = this.findNodesInExistingGroups(nodeIds) + + // If no overlapping groups, use default color + if (overlappingGroups.length === 0) { + return DEFAULT_GROUP_NOTE_COLOR + } + + // Get colors used by overlapping groups + const usedColors = new Set() + overlappingGroups.forEach((group) => { + if (group.color) { + usedColors.add(group.color as NoteColor) + } + }) + + // Return next available color + return getNextAvailableColor(usedColors) + } + + /** + * Create a group note containing the specified node IDs + */ + createGroupNote( + nodeIds: string[], + text: string = '### Group note\nDouble click to edit me' + ): string { + // Filter ids in case they contain subflow nodes + let filteredNodeIds: string[] = nodeIds + let subflowIds: string[] = [] + for (const id of nodeIds) { + if (id.startsWith('subflow:')) { + const match = id.match(/^subflow:([^:]+)/) + if (match) { + subflowIds.push(match[1]) + } + } + } + if (subflowIds.length > 0) { + filteredNodeIds = filteredNodeIds.filter((id) => !subflowIds.includes(id)) + filteredNodeIds = [...filteredNodeIds, ...subflowIds] + } + + // Position and size will be calculated dynamically by layout + const smartColor = this.getSmartGroupNoteColor(filteredNodeIds) + + const groupNote: Omit = { + text, + color: smartColor, + type: 'group', + contained_node_ids: filteredNodeIds, + locked: false + } + + return this.addNote(groupNote) + } + + /** + * Check if a node is the only member of an existing group note + */ + isNodeOnlyMemberOfGroupNote(nodeId: string): boolean { + const notes = this.getNotes() + const groupNotes = notes.filter((note) => note.type === 'group') + + for (const groupNote of groupNotes) { + const containedNodeIds = groupNote.contained_node_ids || [] + if (containedNodeIds.length === 1 && containedNodeIds.includes(nodeId)) { + return true + } + } + + return false + } + + /** + * Check if editing is available (flowStore is properly initialized) + */ + isAvailable(): boolean { + return !!this.flowStore.val.value + } + + /** + * Clean up group notes using DAG path completion + */ + cleanupGroupNotes(flowNodes: { id: string; parentIds?: string[]; offset?: number }[]): void { + if (!this.isAvailable()) { + return + } + + const allNotes = this.getNotes() + const groupNotes = allNotes.filter((note) => note.type === 'group') + if (groupNotes.length === 0) return + + let hasChanges = false + const nodeSet = new Set(flowNodes.map((n) => n.id)) + + // Step 1: Clean invalid nodes from existing group notes + for (const note of groupNotes) { + const originalIds = note.contained_node_ids || [] + const validIds = originalIds.filter((id) => nodeSet.has(id)) + + if (validIds.length !== originalIds.length) { + note.contained_node_ids = validIds + hasChanges = true + } + } + + // Step 2: Complete paths for each group using the DAG algorithm + const splitGroups: FlowNote[] = [] + + for (const note of groupNotes) { + const originalNodes = note.contained_node_ids || [] + if (originalNodes.length === 0) continue + + // Use the DAG path completion and splitting algorithm + const completedGroups = completeAndSplitGroup(originalNodes, flowNodes) + + if (completedGroups.length <= 1) { + // Single group or no change needed + const completeNodes = completedGroups.length > 0 ? completedGroups[0] : [] + const sortedComplete = completeNodes.sort() + const sortedOriginal = originalNodes.sort() + + if ( + sortedComplete.length !== sortedOriginal.length || + !sortedComplete.every((id, i) => id === sortedOriginal[i]) + ) { + note.contained_node_ids = completeNodes + hasChanges = true + } + } else { + // Multiple groups - split into separate notes + hasChanges = true + // Mark original note for removal + note.contained_node_ids = [] + + // Create new notes for each completed group + for (const completedGroup of completedGroups) { + splitGroups.push({ + ...note, + id: generateId(), + contained_node_ids: completedGroup + }) + } + } + } + + // Remove empty group notes and add split component notes + const nonEmptyGroupNotes = groupNotes.filter( + (note) => (note.contained_node_ids?.length || 0) > 0 + ) + + if (hasChanges || splitGroups.length > 0) { + const updatedNotes = [ + ...allNotes.filter((note) => note.type !== 'group'), + ...nonEmptyGroupNotes, + ...splitGroups + ] + this.setNotes(updatedNotes) + } + } +} + +/** + * Context type for NoteEditor + */ +export type NoteEditorContext = { + noteEditor: NoteEditor +} + +const CONTEXT_KEY = 'NoteEditorContext' + +/** + * Set the NoteEditor context (used in FlowBuilder) + */ +export function setNoteEditorContext(noteEditor: NoteEditor): void { + setContext(CONTEXT_KEY, { noteEditor }) +} + +/** + * Get the NoteEditor context (used in components that need editing capabilities) + */ +export function getNoteEditorContext(): NoteEditorContext | undefined { + return getContext(CONTEXT_KEY) +} diff --git a/frontend/src/lib/components/graph/noteManager.svelte.ts b/frontend/src/lib/components/graph/noteManager.svelte.ts new file mode 100644 index 0000000000..6e605327a8 --- /dev/null +++ b/frontend/src/lib/components/graph/noteManager.svelte.ts @@ -0,0 +1,150 @@ +import type { FlowNote } from '$lib/gen' +import type { Node } from '@xyflow/svelte' +import { getLayoutSignature, getPropertySignature } from './noteUtils.svelte' +import { deepEqual } from 'fast-equals' +import { untrack } from 'svelte' + +/** + * Utility class for managing flow note text height caching, selection, and fine-grained reactivity + * Handles both fast visual updates and structural changes + */ +export class NoteManager { + renderCount = $state(0) + + // Track notes for layout change detection + #notes: () => FlowNote[] + #previousLayoutSignature: ReturnType = $state({ + notesCount: 0, + noteIds: [], + groupMemberships: [] + }) + #previousPropertySignature: ReturnType = $state([]) + + // Function to update nodes array with reactivity + #setNodes: (nodes: Node[]) => void + #getNodes: () => Node[] + + // Selection state + #selectedNoteId = $state(undefined) + + constructor(notes: () => FlowNote[], setNodes: (nodes: Node[]) => void, getNodes: () => Node[]) { + this.#notes = notes + this.#setNodes = setNodes + this.#getNodes = getNodes + + // Effect to monitor note changes with dual signature tracking + $effect(() => { + const currentNotes = this.#notes() + const currentLayoutSignature = getLayoutSignature(currentNotes) + const currentPropertySignature = getPropertySignature(currentNotes) + + untrack(() => { + const hasLayoutChanges = !deepEqual(currentLayoutSignature, this.#previousLayoutSignature) + const hasPropertyChanges = !deepEqual( + currentPropertySignature, + this.#previousPropertySignature + ) + + if (hasLayoutChanges) { + // Structural changes require full re-render + this.#previousLayoutSignature = currentLayoutSignature + this.#previousPropertySignature = currentPropertySignature + this.render() + } else if (hasPropertyChanges) { + // Property changes can be handled with fast updates + this.#updateNodesProperties(currentNotes) + this.#previousPropertySignature = currentPropertySignature + } + }) + }) + } + + /** + * Triggers a re-render of the graph by incrementing the render count + */ + render(): void { + this.renderCount++ + } + + /** + * Update node properties using setter function for proper reactivity + * Only updates visual properties that don't affect layout + */ + #updateNodesProperties(currentNotes: FlowNote[]): void { + const currentNodes = this.#getNodes() + if (currentNodes.length === 0) return + + // Create a new array with updated nodes to trigger reactivity + const updatedNodes = currentNodes.map((node) => { + const note = currentNotes.find((n) => n.id === node.id) + if (!note || node.type !== 'note') return node + + // Clone the node to avoid mutation + const updatedNode = { ...node, data: { ...node.data } } + + // Update properties that don't affect layout + if (updatedNode.data) { + updatedNode.data.text = note.text + updatedNode.data.color = note.color + updatedNode.data.locked = note.locked || false + } + + // Update draggable property based on lock state + const isGroupNote = note.type === 'group' + updatedNode.draggable = isGroupNote ? false : !note.locked + + // Update free note size and position (group notes are calculated differently) + if (!isGroupNote && note.size && note.position) { + updatedNode.width = note.size.width + updatedNode.height = note.size.height + updatedNode.position = { ...note.position } + } + + return updatedNode + }) + + // Use setter function to trigger reactivity + this.#setNodes(updatedNodes) + } + + /** + * Select a note by ID (single selection only) + */ + selectNote(noteId: string): void { + if (this.#selectedNoteId === noteId) { + return + } + this.#selectedNoteId = noteId + } + + /** + * Clear note selection + */ + clearNoteSelection(): void { + this.#selectedNoteId = undefined + } + + /** + * Deselect a note by ID (single selection only) + */ + deselectNote(noteId?: string): void { + if (this.#selectedNoteId === noteId) { + this.#selectedNoteId = undefined + } + } + + /** + * Check if a note is currently selected + */ + isNoteSelected(noteId: string): boolean { + return this.#selectedNoteId === noteId + } + + // Handle keyboard shortcuts + handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape') { + // Escape key clears selection regardless of mode + this.clearNoteSelection() + } + } +} diff --git a/frontend/src/lib/components/graph/noteUtils.svelte.ts b/frontend/src/lib/components/graph/noteUtils.svelte.ts new file mode 100644 index 0000000000..d84817a693 --- /dev/null +++ b/frontend/src/lib/components/graph/noteUtils.svelte.ts @@ -0,0 +1,420 @@ +import type { FlowNote } from '$lib/gen' +import type { Node } from '@xyflow/svelte' +import { deepEqual } from 'fast-equals' +import { calculateNodesBoundsWithOffset } from './util' +import { MIN_NOTE_WIDTH, MIN_NOTE_HEIGHT } from './noteColors' +import type { NodeLayout } from './graphBuilder.svelte' +import { topologicalSort } from './graphBuilder.svelte' +import type { AssetWithAltAccessType } from '../assets/lib' +import type { NoteEditorContext } from './noteEditor.svelte' +import { StickyNote } from 'lucide-svelte' + +export type NodeDep = { + id: string + position: { x: number; y: number } + data?: { assets?: AssetWithAltAccessType[] } + parentIds?: string[] + offset?: number + type?: string +} + +export type NoteComputeResult = { + noteNodes: (Node & NodeLayout)[] + newNodePositions: Record +} + +export type AIToolSpacingInfo = { + toolNodes: (Node & NodeLayout)[] + toolEdges: any[] + newNodePositions: Record +} + +export interface GroupNoteBounds { + x: number + y: number + width: number + height: number +} + +let computeNoteNodesCache: + | [NodeDep[], FlowNote[], Record, NoteComputeResult] + | undefined + +/** + * Extracts layout-affecting signature for change detection + * Only includes properties that affect graph layout (structure, grouping) + */ +export function getLayoutSignature(notes: FlowNote[]) { + return { + notesCount: notes.length, + noteIds: notes.map((n) => n.id).sort(), + // Group memberships affect layout spacing + groupMemberships: notes + .filter((note) => note.type === 'group') + .map((note) => ({ + id: note.id, + containedIds: note.contained_node_ids?.slice().sort() || [] + })) + .sort((a, b) => a.id.localeCompare(b.id)) + } +} + +/** + * Extracts property-only signature for change detection + * Only includes visual/content properties that don't affect layout + */ +export function getPropertySignature(notes: FlowNote[]) { + return notes + .map((note) => ({ + id: note.id, + text: note.text, + color: note.color, + locked: note.locked || false, + position: { ...note.position }, + size: { ...note.size } + })) + .sort((a, b) => a.id.localeCompare(b.id)) +} + +/** + * Calculates z-index values for all notes + * Group notes are ordered by their topmost node's hierarchy position + * Free notes get undefined z-index to use SvelteFlow's native behavior + */ +export function calculateAllNoteZIndexes( + notes: FlowNote[], + nodes: NodeDep[] +): Record { + const zIndexMap: Record = {} + + // Use topological sort to get proper hierarchy order based on parentIds relationships + const sortedNodes = topologicalSort(nodes).reverse() + + // Create a mapping from node ID to its hierarchy position (topological order) + const nodeHierarchyMap: Record = {} + sortedNodes.forEach((node, index) => { + nodeHierarchyMap[node.id] = index + }) + + // Process each note + for (const note of notes) { + if (note.type === 'free') { + // Free notes use SvelteFlow's native z-index behavior (last selected on top) + zIndexMap[note.id] = undefined + } else if (note.type === 'group') { + // Group notes get z-index based on topmost contained node's hierarchy + // Since sortedNodes is in topological order, the first matching node is the topmost + const topmostNode = sortedNodes.find((node) => note.contained_node_ids?.includes(node.id)) + + if (topmostNode) { + const hierarchyPosition = nodeHierarchyMap[topmostNode.id] ?? 0 + // Higher hierarchy position = lower z-index (appears behind) + // Use negative values starting from -2000 to stay below other elements + zIndexMap[note.id] = hierarchyPosition - 2000 + } else { + // Fallback for group notes without valid contained nodes + zIndexMap[note.id] = -2000 + } + } + } + + return zIndexMap +} + +/** + * Calculate extra spacing needed for asset nodes of the topmost node + */ +function calculateExtraAssetSpacing(topmostNodeId: string, nodes: NodeDep[]): number { + // Find the topmost node position + const topmostNode = nodes.find((n) => n.id === topmostNodeId) + if (!topmostNode) { + return 0 + } + + // Find actual asset nodes for the topmost node: {topmostNodeId}-asset-in, type 'asset' + const assetNodes = nodes.filter((n) => n.id.startsWith(`${topmostNodeId}-asset-in-`)) + + if (assetNodes.length === 0) { + return 0 + } + + // Calculate the spacing based on actual asset node positions + const assetSpacing = Math.max( + ...assetNodes.map((assetNode) => { + // Calculate how much space the asset node takes above the main node + return Math.max(0, -assetNode.position.y) + }) + ) + + return assetSpacing +} + +/** + * Calculate extra spacing needed for AI tool nodes of the topmost node + */ +function calculateExtraAIToolSpacing(topmostNodeId: string, nodes: NodeDep[]): number { + // Find the topmost node position + const topmostNode = nodes.find((n) => n.id === topmostNodeId) + if (!topmostNode) { + return 0 + } + + // Find actual AI tool nodes for the topmost node: {topmostNodeId}-tool-, type 'aiTool' + const toolNodes = nodes.filter((n) => n.id.startsWith(`${topmostNodeId}-tool-`)) + + if (toolNodes.length === 0) { + return 0 + } + + // Calculate the spacing based on actual AI tool node positions + const toolSpacing = Math.max( + ...toolNodes.map((toolNode) => { + // Calculate how much space the tool node takes above/below the main node + return Math.max(0, -toolNode.position.y) + }) + ) + + return toolSpacing +} + +/** + * Calculate position and size for group notes based on contained nodes + */ +function calculateGroupNoteLayout( + note: FlowNote, + nodes: NodeDep[], + textHeight: number = 60, + topMostNodeId: string +): { position: { x: number; y: number }; size: { width: number; height: number } } { + if (note.type !== 'group' || !note.contained_node_ids?.length) { + return { + position: note.position ?? { x: 0, y: 0 }, + size: note.size ?? { width: MIN_NOTE_WIDTH, height: MIN_NOTE_HEIGHT } + } + } + + const containedNodes = nodes.filter((node) => note.contained_node_ids?.includes(node.id)) + + if (containedNodes.length === 0) { + return { + position: note.position ?? { x: 0, y: 0 }, + size: note.size ?? { width: MIN_NOTE_WIDTH, height: MIN_NOTE_HEIGHT } + } + } + + const bounds = calculateNodesBoundsWithOffset( + note.contained_node_ids || [], + nodes.map((n) => ({ + id: n.id, + position: n.position, + data: { offset: n.offset ?? 0 }, + type: n.type ?? '' + })) + ) + + const padding = 16 + + // Calculate extra spacing for asset nodes and AI tool nodes of the topmost node + const extraAssetSpacing = topMostNodeId ? calculateExtraAssetSpacing(topMostNodeId, nodes) : 0 + + const extraAIToolSpacing = topMostNodeId ? calculateExtraAIToolSpacing(topMostNodeId, nodes) : 0 + + const totalTextHeight = textHeight + extraAssetSpacing + extraAIToolSpacing + + return { + position: { + x: bounds.minX - padding, + y: bounds.minY - totalTextHeight - padding + }, + size: { + width: bounds.maxX - bounds.minX + 2 * padding, + height: bounds.maxY - bounds.minY + totalTextHeight + 2 * padding + } + } +} + +/** + * Create common data object for note nodes + */ +function createNoteData( + note: FlowNote, + onTextHeightChange: (noteId: string, height: number) => void, + isGroupNote: boolean, + editMode: boolean +) { + return { + noteId: note.id, + text: note.text, + color: note.color, + locked: note.locked || false, + isGroupNote, + editMode, + ...(isGroupNote && { containedNodeIds: note.contained_node_ids || [] }), + onTextHeightChange: (textHeight: number) => { + onTextHeightChange(note.id, textHeight) + } + } +} + +/** + * Main function to compute note nodes and adjust nodes position based on group notes + */ +export function computeNoteNodes( + nodes: NodeDep[], + notes: FlowNote[], + noteTextHeights: Record, + onTextHeightChange: (noteId: string, height: number) => void, + editMode: boolean = false, + noteEditorContext: NoteEditorContext | undefined +): NoteComputeResult { + // Check cache first + if ( + computeNoteNodesCache && + deepEqual(nodes, computeNoteNodesCache[0]) && + deepEqual(notes, computeNoteNodesCache[1]) && + deepEqual(noteTextHeights, computeNoteNodesCache[2]) + ) { + return computeNoteNodesCache[3] + } + + if (editMode) { + if (noteEditorContext?.noteEditor?.isAvailable()) { + noteEditorContext.noteEditor.cleanupGroupNotes(nodes) + } + } + + const allNoteNodes: (Node & NodeLayout)[] = [] + + // Build a map of Y positions that need extra spacing for group notes + const yPosMap: Record = {} // Y position -> spacing needed + + // Group notes that need spacing + const groupNotes = notes.filter((n) => n.type === 'group') + + const topMostNodesMap: Record = {} + + const sortedNodes = topologicalSort(nodes).reverse() + + for (const groupNote of groupNotes) { + if (groupNote.contained_node_ids?.length) { + const topmostNodeId = sortedNodes.find((node) => + groupNote.contained_node_ids?.includes(node.id) + )?.id + const topmostNode = nodes.find((node) => node.id === topmostNodeId) + if (topmostNode) { + const textHeight = noteTextHeights[groupNote.id] || 60 + const spacing = textHeight + 16 // padding + // Mark this Y position as needing spacing + yPosMap[topmostNode.position.y] = Math.max(yPosMap[topmostNode.position.y] || 0, spacing) + topMostNodesMap[groupNote.id] = topmostNode.id + } + } + } + + // Calculate new positions for nodes (offset by group notes) + const sortedNewNodes = nodes + .map((n) => ({ position: { ...n.position }, id: n.id })) + .sort((a, b) => a.position.y - b.position.y) + + let currentYOffset = 0 + let prevYPos = NaN + + for (const node of sortedNewNodes) { + if (node.position.y !== prevYPos) { + // Add spacing for group notes at this Y level + if (yPosMap[node.position.y]) { + currentYOffset += yPosMap[node.position.y] + } + prevYPos = node.position.y + } + node.position.y += currentYOffset + } + + // Create note nodes AFTER calculating adjusted node positions + // For group notes, we need to use the adjusted node positions + const adjustedNodes = sortedNewNodes.map((n) => { + const origNode = nodes.find((orig) => orig.id === n.id) + return { + ...n, + data: origNode?.data, + offset: origNode?.offset, + type: origNode?.type + } + }) + + // Calculate all z-indexes at once using hierarchy information + const noteZIndexes = calculateAllNoteZIndexes(notes, nodes) + + for (const note of notes) { + const isGroupNote = note.type === 'group' + const zIndex = noteZIndexes[note.id] + + // Calculate position and size using adjusted node positions for group notes + const { position, size } = isGroupNote + ? calculateGroupNoteLayout( + note, + adjustedNodes, + noteTextHeights[note.id] || 60, + topMostNodesMap[note.id] + ) + : { + position: note.position ?? { x: 0, y: 0 }, + size: note.size ?? { width: MIN_NOTE_WIDTH, height: MIN_NOTE_HEIGHT } + } + + // Create the note node + const noteNode: Node & NodeLayout = { + id: note.id, + type: 'note' as any, // Note nodes are handled specially + position, + width: size.width, + height: size.height, + zIndex, + draggable: isGroupNote ? false : editMode && !note.locked, + selectable: false, + data: createNoteData(note, onTextHeightChange, isGroupNote, editMode) as any + } + + allNoteNodes.push(noteNode) + } + + const newNodePositions: Record = Object.fromEntries( + sortedNewNodes.map((n) => [n.id, n.position]) + ) + + const result: NoteComputeResult = { + noteNodes: allNoteNodes, + newNodePositions + } + + // Cache the result + computeNoteNodesCache = [ + structuredClone($state.snapshot(nodes)), + structuredClone($state.snapshot(notes)), + structuredClone($state.snapshot(noteTextHeights)), + result + ] + + return result +} + +export function addGroupNoteContextMenuItem( + nodeId: string, + noteEditorContext: NoteEditorContext | undefined +) { + const isDisabled = + !noteEditorContext?.noteEditor || + (noteEditorContext?.noteEditor?.isNodeOnlyMemberOfGroupNote(nodeId) ?? false) + + return { + id: 'add-group-note', + label: 'Add note', + icon: StickyNote, + disabled: isDisabled, + onClick: () => { + if (noteEditorContext?.noteEditor && !isDisabled) { + noteEditorContext.noteEditor.createGroupNote([nodeId]) + } + } + } +} diff --git a/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte b/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte index 568f16c0a0..3010a94325 100644 --- a/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte +++ b/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte @@ -2,8 +2,6 @@ import InsertModulePopover from '$lib/components/flows/map/InsertModulePopover.svelte' import { getBezierPath, BaseEdge, type EdgeProps, EdgeLabel } from '@xyflow/svelte' import { ClipboardCopy, Hourglass } from 'lucide-svelte' - import { getContext } from 'svelte' - import type { Writable } from 'svelte/store' import type { GraphEventHandlers } from '../../graphBuilder.svelte' import { getStraightLinePath } from '../utils' import { twMerge } from 'tailwind-merge' @@ -13,11 +11,9 @@ import type { Job } from '$lib/gen' import type { GraphModuleState } from '../../model' import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte' + import { getGraphContext } from '../../graphContext' - const { useDataflow, showAssets } = getContext<{ - useDataflow: Writable - showAssets?: Writable - }>('FlowGraphContext') + const { useDataflow, showAssets } = getGraphContext() let { // id, diff --git a/frontend/src/lib/components/graph/renderers/edges/EmptyEdge.svelte b/frontend/src/lib/components/graph/renderers/edges/EmptyEdge.svelte index 47a099b7c8..0f622bc65c 100644 --- a/frontend/src/lib/components/graph/renderers/edges/EmptyEdge.svelte +++ b/frontend/src/lib/components/graph/renderers/edges/EmptyEdge.svelte @@ -1,8 +1,7 @@ @@ -322,7 +322,7 @@
+{/snippet} + +
{ + dragging = false + }} + ondragstart={() => { + dragging = true + }} + ondragend={() => { + dragging = false + }} + onmouseenter={handleMouseEnter} + onmouseleave={handleMouseLeave} + role="button" + tabindex={editMode ? -1 : 0} + ondblclick={handleDoubleClick} + use:clickOutside={{ + onClickOutside: () => { + noteManager?.deselectNote(data.noteId) + } + }} +> + + {#if hovering || selected} + {#if !editMode && isEditModeAvailable} +
+ {locked + ? 'Note is locked' + : isEditModeAvailable + ? 'Double click to edit' + : 'View only mode'} +
+ {:else if !locked && isEditModeAvailable} +
GH Markdown
+ {/if} + {/if} + + +
+ {#if editMode} + + + {:else} + +
containerHeight, + (v) => { + if (v > 0 && v !== containerHeight) { + data.onTextHeightChange?.(v) + } + containerHeight = v + } + } + > + {#if textForDisplay} +
+ +
+ {:else} +
+ Double click to edit me +
+ {/if} +
+ {/if} +
+ + + {#if !locked && isEditModeAvailable} + { + // Update note size when resizing ends + if (params.width !== undefined && params.height !== undefined) { + const size = { width: params.width, height: params.height } + if (isEditModeAvailable && noteEditorContext?.noteEditor) { + // Use NoteEditor context in edit mode + noteEditorContext.noteEditor.updateSize(data.noteId, size) + } + } + }} + /> + {/if} + + + {#if isEditModeAvailable} + {#if data.isGroupNote && currentNode?.position} + + +
+ {@render actionButtons()} +
+
+ {:else} + +
+ {@render actionButtons()} +
+ {/if} + {/if} +
+ + diff --git a/frontend/src/lib/components/graph/renderers/nodes/ResultNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/ResultNode.svelte index 3917206edb..13ede87991 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/ResultNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/ResultNode.svelte @@ -1,19 +1,17 @@ @@ -22,7 +20,7 @@ id={'Result'} label={'Result'} selectable={true} - selected={$selectedId === 'Result'} + selected={selectionManager && selectionManager.isNodeSelected(id)} hideId={true} on:select={(e) => { setTimeout(() => data?.eventHandlers?.select(e.detail)) diff --git a/frontend/src/lib/components/graph/renderers/nodes/SubflowBound.svelte b/frontend/src/lib/components/graph/renderers/nodes/SubflowBound.svelte index bb23d4566a..7e5438dda0 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/SubflowBound.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/SubflowBound.svelte @@ -5,12 +5,16 @@ import NodeWrapper from './NodeWrapper.svelte' import { Minimize2 } from 'lucide-svelte' import type { SubflowBoundN } from '../../graphBuilder.svelte' + import { getGraphContext } from '../../graphContext' interface Props { data: SubflowBoundN['data'] + id: string } - let { data }: Props = $props() + let { data, id }: Props = $props() + + const { selectionManager } = getGraphContext() @@ -19,7 +23,7 @@ label={data.label} preLabel={data.preLabel} selectable - selected={data.selected} + selected={selectionManager && selectionManager.isNodeSelected(id)} on:select={() => { setTimeout(() => data.eventHandlers?.select(data.id)) }} diff --git a/frontend/src/lib/components/graph/renderers/nodes/TriggersNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/TriggersNode.svelte index 7283739b5f..58f95d4b33 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/TriggersNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/TriggersNode.svelte @@ -1,9 +1,10 @@ @@ -76,26 +82,26 @@ const primarySchedule = triggersState.triggers.findIndex((t) => t.isPrimary && !t.isDraft) triggersState.selectedTriggerIndex = primarySchedule }} - on:select={() => data?.eventHandlers?.select('triggers')} + on:select={() => data?.eventHandlers?.select('Trigger')} onSelect={async (triggerIndex: number) => { - data?.eventHandlers?.select('triggers') + data?.eventHandlers?.select('Trigger') await tick() triggersState.selectedTriggerIndex = triggerIndex }} onAddDraftTrigger={async (type: TriggerType) => { const newTrigger = triggersState.addDraftTrigger(triggersCount, type) - data?.eventHandlers?.select('triggers') + data?.eventHandlers?.select('Trigger') await tick() triggersState.selectedTriggerIndex = newTrigger }} - selected={$selectedId == 'triggers'} + selected={selectionManager?.getSelectedId() === 'Trigger'} newItem={data.newFlow} /> {:else} { data?.eventHandlers?.select(e.detail) }} @@ -116,7 +122,7 @@ {:else}
+ +
+

Context Menus

+

Right-click triggered menus with contextual actions.

+
+ + +
+

Context menu tests

+ +
+
+
+

Basic Context Menu

+

Right-click the area below

+
+ +
+ Right-click me for context menu +
+
+
+ +
+
+

Text Context Menu

+

Right-click the text below

+
+ +
+

+ Right-click this text to open the context menu. You can test various interactions + here. +

+
+
+
+ +
+
+

Button Context Menu

+

Right-click the button below

+
+ + + +
+
+
+

Input Components

diff --git a/frontend/src/routes/view_graph/+page.svelte b/frontend/src/routes/view_graph/+page.svelte index 18b49846a8..86bbba17cf 100644 --- a/frontend/src/routes/view_graph/+page.svelte +++ b/frontend/src/routes/view_graph/+page.svelte @@ -4,7 +4,7 @@ import { decodeState } from '$lib/utils' let content = localStorage.getItem('svelvet') - const { modules, failureModule, preprocessorModule } = content + const { modules, failureModule, preprocessorModule, notes } = content ? decodeState(content) : { modules: [], failureModule: undefined, preprocessorModule: undefined } @@ -15,6 +15,7 @@ {modules} {failureModule} {preprocessorModule} + {notes} /> Date: Wed, 19 Nov 2025 21:27:02 +0000 Subject: [PATCH 61/81] nits --- ...1f7f387f5055c47f493271d26731336257384.json | 10 ++++---- ...a90d9206c45d92a0423c0bc2396d0d66a0b0d.json | 4 +-- ...ba524a2bc1e9047bdde5f568af4d993dbb74c.json | 25 +++++++++++++++++++ ...8d1e383aeda9a5a71183b7fbaa41deca4e333.json | 16 ------------ ...6dc628e2ba56eab5e1a50c99481da9793759e.json | 16 ++++++++++++ ...0cb549a34b96554ae1872355b90304f5dcb76.json | 4 +-- ...f4a5b962f7803def9ecd6e33d2aec6abce772.json | 25 ------------------- .../windmill-worker/src/worker_lockfiles.rs | 1 - 8 files changed, 49 insertions(+), 52 deletions(-) create mode 100644 backend/.sqlx/query-207a0721b6f0b8b6ddd4120343eba524a2bc1e9047bdde5f568af4d993dbb74c.json delete mode 100644 backend/.sqlx/query-544a00afb5c72d2aa24ab4ccfe68d1e383aeda9a5a71183b7fbaa41deca4e333.json create mode 100644 backend/.sqlx/query-676c758d9c4492dada50edd3ad06dc628e2ba56eab5e1a50c99481da9793759e.json delete mode 100644 backend/.sqlx/query-d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772.json diff --git a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json index d29a18c691..e7ed0aee65 100644 --- a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json +++ b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json @@ -46,11 +46,11 @@ ] }, "nullable": [ - true, - true, - true, - true, - true, + false, + false, + false, + false, + false, true, true ] diff --git a/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json b/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json index bc15928e38..ecaf828737 100644 --- a/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json +++ b/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json @@ -59,9 +59,7 @@ "failure", "command", "approval", - "preprocessor", - "schedule_handler_old", - "dynamic_skip" + "preprocessor" ] } } diff --git a/backend/.sqlx/query-207a0721b6f0b8b6ddd4120343eba524a2bc1e9047bdde5f568af4d993dbb74c.json b/backend/.sqlx/query-207a0721b6f0b8b6ddd4120343eba524a2bc1e9047bdde5f568af4d993dbb74c.json new file mode 100644 index 0000000000..b0cf45284a --- /dev/null +++ b/backend/.sqlx/query-207a0721b6f0b8b6ddd4120343eba524a2bc1e9047bdde5f568af4d993dbb74c.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE\n flow\n SET\n path = $1,\n summary = $2,\n description = $3,\n dependency_job = NULL,\n lock_error_logs = '',\n draft_only = NULL,\n tag = $4,\n dedicated_worker = $5,\n visible_to_runner_only = $6,\n on_behalf_of_email = $7,\n value = $8,\n schema = $9::text::json,\n edited_by = $10,\n edited_at = now()\n WHERE\n path = $11 AND workspace_id = $12", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text", + "Varchar", + "Bool", + "Bool", + "Text", + "Jsonb", + "Text", + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "207a0721b6f0b8b6ddd4120343eba524a2bc1e9047bdde5f568af4d993dbb74c" +} diff --git a/backend/.sqlx/query-544a00afb5c72d2aa24ab4ccfe68d1e383aeda9a5a71183b7fbaa41deca4e333.json b/backend/.sqlx/query-544a00afb5c72d2aa24ab4ccfe68d1e383aeda9a5a71183b7fbaa41deca4e333.json deleted file mode 100644 index 8c0d13fbd1..0000000000 --- a/backend/.sqlx/query-544a00afb5c72d2aa24ab4ccfe68d1e383aeda9a5a71183b7fbaa41deca4e333.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow \n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) \n SELECT workspace_id, $1, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow\n WHERE path = $2 AND workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "544a00afb5c72d2aa24ab4ccfe68d1e383aeda9a5a71183b7fbaa41deca4e333" -} \ No newline at end of file diff --git a/backend/.sqlx/query-676c758d9c4492dada50edd3ad06dc628e2ba56eab5e1a50c99481da9793759e.json b/backend/.sqlx/query-676c758d9c4492dada50edd3ad06dc628e2ba56eab5e1a50c99481da9793759e.json new file mode 100644 index 0000000000..11339abea1 --- /dev/null +++ b/backend/.sqlx/query-676c758d9c4492dada50edd3ad06dc628e2ba56eab5e1a50c99481da9793759e.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at)\n SELECT workspace_id, $1, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow\n WHERE path = $2 AND workspace_id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "676c758d9c4492dada50edd3ad06dc628e2ba56eab5e1a50c99481da9793759e" +} diff --git a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json index 99269c9851..54e94cfb8f 100644 --- a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json +++ b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json @@ -18,8 +18,8 @@ "Left": [] }, "nullable": [ - true, - false + false, + true ] }, "hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76" diff --git a/backend/.sqlx/query-d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772.json b/backend/.sqlx/query-d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772.json deleted file mode 100644 index a13d62402a..0000000000 --- a/backend/.sqlx/query-d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE \n flow \n SET\n path = $1,\n summary = $2,\n description = $3,\n dependency_job = NULL,\n lock_error_logs = '',\n draft_only = NULL,\n tag = $4,\n dedicated_worker = $5,\n visible_to_runner_only = $6,\n on_behalf_of_email = $7,\n value = $8,\n schema = $9::text::json,\n edited_by = $10,\n edited_at = now()\n WHERE \n path = $11 AND workspace_id = $12", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text", - "Text", - "Varchar", - "Bool", - "Bool", - "Text", - "Jsonb", - "Text", - "Varchar", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772" -} \ No newline at end of file diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index cc92955569..824a45c661 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -17,7 +17,6 @@ use sqlx::types::Json; use tokio::time::timeout; use uuid::Uuid; use windmill_common::assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind}; -use windmill_common::cache::FlowNotes; use windmill_common::error::Error; use windmill_common::error::Result; use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeId}; From cd5827e802680fd0e37e7f201dc584731bbe0223 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 19 Nov 2025 22:29:46 +0100 Subject: [PATCH 62/81] chore(main): release 1.581.0 (#7181) * chore(main): release 1.581.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 62 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 55 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee09e12696..f97e2859a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.581.0](https://github.com/windmill-labs/windmill/compare/v1.580.0...v1.581.0) (2025-11-19) + + +### Features + +* **frontend:** add notes to flow ([#6628](https://github.com/windmill-labs/windmill/issues/6628)) ([cfeb294](https://github.com/windmill-labs/windmill/commit/cfeb294308ba85763025f3628cbb85144d7f0778)) + ## [1.580.0](https://github.com/windmill-labs/windmill/compare/v1.579.2...v1.580.0) (2025-11-18) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index ff91f7063b..dada36c231 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2094,9 +2094,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.52" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -2104,9 +2104,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.52" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -15188,7 +15188,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15250,7 +15250,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "argon2", @@ -15371,7 +15371,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.580.0" +version = "1.581.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15386,7 +15386,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.580.0" +version = "1.581.0" dependencies = [ "chrono", "lazy_static", @@ -15400,7 +15400,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "axum", @@ -15419,7 +15419,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "async-recursion", @@ -15507,7 +15507,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.580.0" +version = "1.581.0" dependencies = [ "regex", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "bytes", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.580.0" +version = "1.581.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.580.0" +version = "1.581.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15567,7 +15567,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "lazy_static", @@ -15579,7 +15579,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "serde_json", @@ -15591,7 +15591,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "gosyn", @@ -15603,7 +15603,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "lazy_static", @@ -15615,7 +15615,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "serde_json", @@ -15627,7 +15627,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "nu-parser", @@ -15638,7 +15638,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15661,7 +15661,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "async-recursion", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "lazy_static", @@ -15698,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15715,7 +15715,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "lazy_static", @@ -15729,7 +15729,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "lazy_static", @@ -15747,7 +15747,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "serde", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "async-recursion", @@ -15795,7 +15795,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.580.0" +version = "1.581.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15805,7 +15805,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.580.0" +version = "1.581.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 20470e42b0..8f3c13c3a2 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.580.0" +version = "1.581.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.580.0" +version = "1.581.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 39e23a74db..9254136f72 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.580.0 + version: 1.581.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 14d0b8ce73..97524c888d 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.580.0"; +export const VERSION = "v1.581.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index c98ad8a997..b74c3e0c0a 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.580.0"; +export const VERSION = "1.581.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 047e89119d..90a8bf7d6e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.580.0", + "version": "1.581.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.580.0", + "version": "1.581.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 344c843dcd..706235f31b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.580.0", + "version": "1.581.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index f4c670f6e5..ae5a2377c8 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.580.0" -wmill_pg = ">=1.580.0" +wmill = ">=1.581.0" +wmill_pg = ">=1.581.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index ae7e4d3496..195666f665 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.580.0 + version: 1.581.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index f993af076d..7a388645c7 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.580.0' + ModuleVersion = '1.581.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 056ef57769..d890a51b05 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.580.0" +version = "1.581.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 411f60ca58..ced5c27427 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.580.0" +version = "1.581.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index f2e92959b3..3fb45e9001 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.580.0", + "version": "1.581.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index e4892ec746..a122e5ddc2 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.580.0", + "version": "1.581.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 98470cba67..629e9b1913 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.580.0 +1.581.0 From 054aeb33271288dc9458b012881164c3c4597280 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Thu, 20 Nov 2025 11:33:10 +0000 Subject: [PATCH 63/81] fix(frontend): missing node Result id migration (#7182) * fix missing id changes * fix ai tool selection --- frontend/src/lib/components/Dev.svelte | 2 +- frontend/src/lib/components/FlowBuilder.svelte | 2 +- .../src/lib/components/flows/map/FlowModuleSchemaMap.svelte | 2 +- frontend/src/lib/components/graph/FlowGraphV2.svelte | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index 1b6df2a06f..505002cd54 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -650,7 +650,7 @@ job.success && flowPreviewButtons?.getPreviewMode() === 'whole' ) { - if (flowModuleSchemaMap?.isNodeVisible('result') && selectedId !== 'Result') { + if (flowModuleSchemaMap?.isNodeVisible('Result') && selectedId !== 'Result') { outputPickerOpenFns['Result']?.() } } else { diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index daaa4d5238..6367779894 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -949,7 +949,7 @@ job.success && flowPreviewButtons?.getPreviewMode() === 'whole' ) { - if (flowEditor?.isNodeVisible('result') && selectedIdStore !== 'Result') { + if (flowEditor?.isNodeVisible('Result') && selectedIdStore !== 'Result') { outputPickerOpenFns['Result']?.() } } else { diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index 1b7af36c41..eab09789b6 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -484,7 +484,7 @@ let targetModules if ( detail.sourceId == 'Input' || - detail.targetId == 'result' || + detail.targetId == 'Result' || detail.kind == 'trigger' ) { targetModules = flowStore.val.value.modules diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 26a3ed2aed..9be2534b9f 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -396,6 +396,10 @@ onInsert?.(detail) }, select: (modId) => { + // AI tools are not selectable by the flow. Selection has to be refactored to be simplier. + if (nodes.find((n) => n.data?.moduleId === modId)?.type === 'aiTool') { + selectionManager.selectId(modId) + } if (!notSelectable) { onSelect?.(modId) } From 3e2935b4ee0dee06486c3e0026b9ff50eba59eb1 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 20 Nov 2025 12:39:41 +0100 Subject: [PATCH 64/81] chore(main): release 1.581.1 (#7183) * chore(main): release 1.581.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 62 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 55 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f97e2859a6..9dc919ee16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.581.1](https://github.com/windmill-labs/windmill/compare/v1.581.0...v1.581.1) (2025-11-20) + + +### Bug Fixes + +* **frontend:** missing node Result id migration ([#7182](https://github.com/windmill-labs/windmill/issues/7182)) ([054aeb3](https://github.com/windmill-labs/windmill/commit/054aeb33271288dc9458b012881164c3c4597280)) + ## [1.581.0](https://github.com/windmill-labs/windmill/compare/v1.580.0...v1.581.0) (2025-11-19) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index dada36c231..717b025e28 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -788,9 +788,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.9" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86590e57ea40121d47d3f2e131bfd873dea15d78dc2f4604f4734537ad9e56c4" +checksum = "b01c9521fa01558f750d183c8c68c81b0155b9d193a4ba7f84c36bd1b6d04a06" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -823,9 +823,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.14" +version = "1.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe0fd441565b0b318c76e7206c8d1d0b0166b3e986cf30e890b61feb6192045" +checksum = "7ce527fb7e53ba9626fc47824f25e256250556c40d8f81d27dd92aa38239d632" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -15188,7 +15188,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -15250,7 +15250,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "argon2", @@ -15371,7 +15371,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.581.0" +version = "1.581.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15386,7 +15386,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.581.0" +version = "1.581.1" dependencies = [ "chrono", "lazy_static", @@ -15400,7 +15400,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "axum", @@ -15419,7 +15419,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "async-recursion", @@ -15507,7 +15507,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.581.0" +version = "1.581.1" dependencies = [ "regex", "serde", @@ -15522,7 +15522,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "bytes", @@ -15546,7 +15546,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.581.0" +version = "1.581.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.581.0" +version = "1.581.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15567,7 +15567,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "lazy_static", @@ -15579,7 +15579,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "serde_json", @@ -15591,7 +15591,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "gosyn", @@ -15603,7 +15603,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "lazy_static", @@ -15615,7 +15615,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "serde_json", @@ -15627,7 +15627,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "nu-parser", @@ -15638,7 +15638,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15661,7 +15661,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "async-recursion", @@ -15684,7 +15684,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "lazy_static", @@ -15698,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15715,7 +15715,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "lazy_static", @@ -15729,7 +15729,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "lazy_static", @@ -15747,7 +15747,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "serde", @@ -15758,7 +15758,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "async-recursion", @@ -15795,7 +15795,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.581.0" +version = "1.581.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15805,7 +15805,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.581.0" +version = "1.581.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8f3c13c3a2..666727bf9c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.581.0" +version = "1.581.1" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.581.0" +version = "1.581.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 9254136f72..9dfcbb25ec 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.581.0 + version: 1.581.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 97524c888d..70f3b3d53b 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.581.0"; +export const VERSION = "v1.581.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index b74c3e0c0a..de51bb1164 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.581.0"; +export const VERSION = "1.581.1"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 90a8bf7d6e..38cbcfb409 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.581.0", + "version": "1.581.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.581.0", + "version": "1.581.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 706235f31b..1c7f547522 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.581.0", + "version": "1.581.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index ae5a2377c8..493c7c1ca9 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.581.0" -wmill_pg = ">=1.581.0" +wmill = ">=1.581.1" +wmill_pg = ">=1.581.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 195666f665..d3e9e0ad99 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.581.0 + version: 1.581.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 7a388645c7..aba6ec750b 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.581.0' + ModuleVersion = '1.581.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index d890a51b05..9604c22fbf 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.581.0" +version = "1.581.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index ced5c27427..236f748be7 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.581.0" +version = "1.581.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 3fb45e9001..6c9570044c 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.581.0", + "version": "1.581.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index a122e5ddc2..b76c958a06 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.581.0", + "version": "1.581.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 629e9b1913..902f26d0c0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.581.0 +1.581.1 From d6da4a32bf707ecea1a6397906e916d782bc7dff Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:35:44 +0100 Subject: [PATCH 65/81] Fix alignment issues and TextArea min-height taller (#7184) --- frontend/src/lib/components/ArgInput.svelte | 61 +++++++--------- frontend/src/lib/components/Password.svelte | 63 ++++++++-------- frontend/src/lib/components/SchemaForm.svelte | 4 +- .../components/text_input/TextInput.svelte | 71 ++++++++++++------- 4 files changed, 100 insertions(+), 99 deletions(-) diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index b63d3798e5..f912fad49f 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -28,7 +28,6 @@ import DateTimeInput from './DateTimeInput.svelte' import DateInput from './DateInput.svelte' import CurrencyInput from './apps/components/inputs/currency/CurrencyInput.svelte' - import autosize from '$lib/autosize' import PasswordArgInput from './PasswordArgInput.svelte' import Password from './Password.svelte' import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte' @@ -46,11 +45,7 @@ import { workspaceStore } from '$lib/stores' import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte' import AIProviderPicker from './AIProviderPicker.svelte' - import TextInput, { - inputBaseClass, - inputBorderClass, - inputSizeClasses - } from './text_input/TextInput.svelte' + import TextInput from './text_input/TextInput.svelte' import FileInput from './common/fileInput/FileInput.svelte' interface Props { @@ -1412,45 +1407,39 @@ {/if} {:else} {#key extra?.['minRows']} - + {error} + unifiedHeight={false} + underlyingInputEl="textarea" + /> {/key} {/if} {#if !disabled && itemPicker && extra?.['disableVariablePicker'] != true} - - + /> {/if}
{@render variableInput()} diff --git a/frontend/src/lib/components/Password.svelte b/frontend/src/lib/components/Password.svelte index f6fd20eb09..c14a654ccb 100644 --- a/frontend/src/lib/components/Password.svelte +++ b/frontend/src/lib/components/Password.svelte @@ -1,5 +1,8 @@
-
- - +
+
- {#if hideValue} - - {:else} - - {/if} + onBlur?.(e), + onkeydown: (e) => { + onKeyDown?.(e) + bubble('keydown')(e) + }, + type: hideValue ? 'password' : 'text' + }} + />
{#if red}
This field is required
diff --git a/frontend/src/lib/components/SchemaForm.svelte b/frontend/src/lib/components/SchemaForm.svelte index a0fb8b56b5..16494af9b9 100644 --- a/frontend/src/lib/components/SchemaForm.svelte +++ b/frontend/src/lib/components/SchemaForm.svelte @@ -415,7 +415,7 @@ {#snippet actions()} {@render actions_render?.({ item })} {#if linkedSecretCandidates?.includes(argName)} -
+
{ @@ -429,14 +429,12 @@ {#snippet children({ item })} - - { - e.stopImmediatePropagation() - }} - bind:this={inputEl} - bind:value -/> +{#if underlyingInputEl === 'textarea'} + + +{:else if underlyingInputEl === 'input'} + e.stopImmediatePropagation()} + bind:this={inputEl} + bind:value + /> +{/if} From 1b77e2eaaade943620a0aabb137c3776b62870da Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:37:34 +0100 Subject: [PATCH 66/81] fix monaco height (#7186) --- frontend/src/lib/components/TemplateEditor.svelte | 2 +- frontend/src/lib/components/vscode.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/TemplateEditor.svelte b/frontend/src/lib/components/TemplateEditor.svelte index 9c01537eea..c959773bf5 100644 --- a/frontend/src/lib/components/TemplateEditor.svelte +++ b/frontend/src/lib/components/TemplateEditor.svelte @@ -494,7 +494,7 @@ const updateHeight = () => { const contentHeight = Math.min(1000, editor.getContentHeight()) if (divEl) { - divEl.style.height = `${contentHeight + 2}px` + divEl.style.height = `${contentHeight}px` } try { editor.layout({ width, height: contentHeight }) diff --git a/frontend/src/lib/components/vscode.ts b/frontend/src/lib/components/vscode.ts index 14438d1ad1..22e955dc5c 100644 --- a/frontend/src/lib/components/vscode.ts +++ b/frontend/src/lib/components/vscode.ts @@ -291,4 +291,4 @@ export function keepModelAroundToAvoidDisposalOfWorkers() { } } -export let MONACO_Y_PADDING = 7 +export let MONACO_Y_PADDING = 6.5 From ce5a31865cf6965ec28c449c2a832b93572a8eb6 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:01:54 +0100 Subject: [PATCH 67/81] feat(aichat): handle duckdb scripts (#7187) * handle duckdb in aichat * better * add in gen edit fix * fix missing entry in yaml * fix --- .../components/copilot/chat/script/core.ts | 5 ++- .../lib/components/copilot/prompts/edit.yaml | 34 +++++++++++++++ .../components/copilot/prompts/editPrompt.ts | 15 ++++--- .../lib/components/copilot/prompts/fix.yaml | 41 ++++++++++++++++++- .../components/copilot/prompts/fixPrompt.ts | 7 +++- .../lib/components/copilot/prompts/gen.yaml | 26 +++++++++++- .../components/copilot/prompts/genPrompt.ts | 15 ++++--- 7 files changed, 126 insertions(+), 17 deletions(-) diff --git a/frontend/src/lib/components/copilot/chat/script/core.ts b/frontend/src/lib/components/copilot/chat/script/core.ts index 438fdf45ce..5c14610c45 100644 --- a/frontend/src/lib/components/copilot/chat/script/core.ts +++ b/frontend/src/lib/components/copilot/chat/script/core.ts @@ -204,7 +204,8 @@ export const SUPPORTED_CHAT_SCRIPT_LANGUAGES = [ 'graphql', 'powershell', 'csharp', - 'java' + 'java', + 'duckdb' ] export function getLangContext( @@ -310,6 +311,8 @@ export function getLangContext( return 'The user is coding in C#. On Windmill, it is expected the script contains a public static Main method inside a class. The class name is irrelevant. NuGet packages can be added using the format: #r "nuget: PackageName, Version" at the top of the script. The Main method signature should be: public static ReturnType Main(parameter types...)' case 'java': return 'The user is coding in Java. On Windmill, it is expected the script contains a Main public class and a public static main() method. The return type can be Object or void. Dependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script. The method signature should be: public static Object main(parameter types...)' + case 'duckdb': + return "The user is coding in DuckDB. On Windmill, arguments are defined with comments like `-- $name (text) = default` or `-- $name (text)` (one per line) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes, then perform CRUD operations. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);` and query with `SELECT * FROM db.schema.table;`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage" default: return '' } diff --git a/frontend/src/lib/components/copilot/prompts/edit.yaml b/frontend/src/lib/components/copilot/prompts/edit.yaml index e17e8139f1..b3c5d70ba9 100644 --- a/frontend/src/lib/components/copilot/prompts/edit.yaml +++ b/frontend/src/lib/components/copilot/prompts/edit.yaml @@ -213,6 +213,30 @@ prompts: No need to require autoload, it is already done. My instructions: {description} + csharp: + prompt: |- + Here's my C# code: + ```csharp + {code} + ``` + + You have to write C# code with a public static Main method inside a class. Specify the parameter types. Do not call the main function. You should generally return the result. + NuGet packages can be added using the format: #r "nuget: PackageName, Version" at the top of the script. + The Main method signature should be: public static ReturnType Main(parameter types...) + + My instructions: {description} + java: + prompt: |- + Here's my Java code: + ```java + {code} + ``` + + You have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result. + Dependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script. + The method signature should be: public static Object main(parameter types...) + + My instructions: {description} frontend: prompt: |- Here's my client-side javascript code: @@ -262,3 +286,13 @@ prompts: My instructions: {description} + duckdb: + prompt: |- + Here's my DuckDB code: + ```sql + {code} + ``` + + Arguments are defined with comments like `-- $age (text) = 20` or `-- $name (text)` (one per row) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage. + + My instructions: {description} diff --git a/frontend/src/lib/components/copilot/prompts/editPrompt.ts b/frontend/src/lib/components/copilot/prompts/editPrompt.ts index 6aec87dd35..dcfdd1d07b 100644 --- a/frontend/src/lib/components/copilot/prompts/editPrompt.ts +++ b/frontend/src/lib/components/copilot/prompts/editPrompt.ts @@ -46,17 +46,20 @@ export const EDIT_PROMPT = { "php": { "prompt": "Here's my php code: \n```php\n{code}\n```\n\nYou have to write a function in php called \"main\". Specify the parameter types. Do not call the main function. You should generally return the result. The script must start with `\n{resourceTypes}\n\nYou need to define the type of the resources that are needed before the main function, but only include them if they are actually needed to achieve the function purpose.\nBefore defining each type, check if the class already exists using class_exists.\nThe resource type name has to be exactly as specified.\nIf you need to import libraries, you need to specify them as comments in the following manner before the main function:\n```\n// require:\n// mylibrary/mylibrary\n// myotherlibrary/myotherlibrary@optionalversion\n```\nNo need to require autoload, it is already done.\n\nMy instructions: {description}" }, + "csharp": { + "prompt": "Here's my C# code:\n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\n\nMy instructions: {description}" + }, + "java": { + "prompt": "Here's my Java code:\n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\n\nMy instructions: {description}" + }, "frontend": { "prompt": "Here's my client-side javascript code: \n```javascript\n{code}\n```\n\nYou can access the context object with the ctx global variable. \nThe app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar'\nYou can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean)\nYou can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string)\nYou can use the setTab function to manually set the tab of a Tab component: setTab(id: string, index: string)\nYou can use the recompute function to recompute a component: recompute(id: string)\nYou can use the getAgGrid function to get the ag-grid instance of a table: getAgGrid(id: string)\nYou can use the setSelectedIndex function to select a row in a table or an AG Grid table: setSelectedIndex(id: string, index: number)\nThe setValue function is meant to set or force the value of a component: setValue(id: string, value: any)\nYou can validate a specific field of a form: validate(id: string, key: string)\nYou can validate all fields of a form: validateAll(id: string, key: string)\nYou can invalidate a specific field of a form: invalidate(id: string, key: string, error: string)\n\nMy instructions: {description}" }, - "csharp": { - "prompt": "Here's my C# code: \n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nMy instructions: {description}" - }, - "java": { - "prompt": "Here's my Java code: \n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nMy instructions: {description}" - }, "transformer": { "prompt": "Here's my client-side javascript code: \n```javascript\n{code}\n```\n\n\nThe code should process the variable `result` according to my instructions.\nThe variable `result` is available globally.\nAt the end of the code, the processed result has to be returned.\n\n\nYou can access the context object with the ctx global variable. \nThe app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar'\nYou can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean)\nYou can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string)\nYou can use the setTab function to manually set the tab of a Tab component: setTab(id: string, index: string)\nYou can use the recompute function to recompute a component: recompute(id: string)\nYou can use the getAgGrid function to get the ag-grid instance of a table: getAgGrid(id: string)\nYou can use the setSelectedIndex function to select a row in a table or an AG Grid table: setSelectedIndex(id: string, index: number)\nThe setValue function is meant to set or force the value of a component: setValue(id: string, value: any)\nYou can validate a specific field of a form: validate(id: string, key: string)\nYou can validate all fields of a form: validateAll(id: string, key: string)\nYou can invalidate a specific field of a form: invalidate(id: string, key: string, error: string)\n\n\nMy instructions: {description}" + }, + "duckdb": { + "prompt": "Here's my DuckDB code:\n```sql\n{code}\n```\n\nArguments are defined with comments like `-- $age (text) = 20` or `-- $name (text)` (one per row) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage.\n\nMy instructions: {description}" } } }; \ No newline at end of file diff --git a/frontend/src/lib/components/copilot/prompts/fix.yaml b/frontend/src/lib/components/copilot/prompts/fix.yaml index 78921857f2..b15e30a9ca 100644 --- a/frontend/src/lib/components/copilot/prompts/fix.yaml +++ b/frontend/src/lib/components/copilot/prompts/fix.yaml @@ -61,7 +61,7 @@ prompts: pub fn main(...) -> Result> ``` but do not call it. Favor idiomatic Rust patterns, ensuring safe handling of ownership and borrowing, robust error handling with `Result`, and concurrency if needed (`async`/`tokio` or std threading). - + - Include necessary imports and modules only when needed. - Add comments explaining important operations and any unsafe usage (if absolutely required). - The generated code should be easily executable and testable in an integrated terminal. @@ -229,3 +229,42 @@ prompts: I get the following error: {error} Fix my code. + csharp: + prompt: |- + Here's my C# code: + ```csharp + {code} + ``` + + You have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result. + NuGet packages can be added using the format: #r "nuget: PackageName, Version" at the top of the script. + The Main method signature should be: public static ReturnType Main(parameter types...) + Arguments are used to generate the input specification and create the frontend UI for the script. + + I get the following error: {error} + Fix my code. + java: + prompt: |- + Here's my Java code: + ```java + {code} + ``` + + You have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result. + Dependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script. + The method signature should be: public static Object main(parameter types...) + Arguments are used to generate the input specification and create the frontend UI for the script. + + I get the following error: {error} + Fix my code. + duckdb: + prompt: |- + Here's my DuckDB code: + ```sql + {code} + ``` + + Arguments are defined with comments like `-- $age (text) = 20` or `-- $name (text)` (one per row) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage. + + I get the following error: {error} + Fix my code. diff --git a/frontend/src/lib/components/copilot/prompts/fixPrompt.ts b/frontend/src/lib/components/copilot/prompts/fixPrompt.ts index 4fc6ed3227..9f3d48309c 100644 --- a/frontend/src/lib/components/copilot/prompts/fixPrompt.ts +++ b/frontend/src/lib/components/copilot/prompts/fixPrompt.ts @@ -47,10 +47,13 @@ export const FIX_PROMPT = { "prompt": "Here's my php code: \n```php\n{code}\n```\n\nYou have to write a function in php called \"main\". Specify the parameter types. Do not call the main function. You should generally return the result. The script must start with `\n{resourceTypes}\n\nYou need to define the type of the resources that are needed before the main function, but only include them if they are actually needed to achieve the function purpose.\nBefore defining each type, check if the class already exists using class_exists.\nThe resource type name has to be exactly as specified.\nIf you need to import libraries, you need to specify them as comments in the following manner before the main function:\n```\n// require:\n// mylibrary/mylibrary\n// myotherlibrary/myotherlibrary@optionalversion\n```\nNo need to require autoload, it is already done.\n\nI get the following error: {error}\nFix my code." }, "csharp": { - "prompt": "Here's my C# code: \n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nI get the following error: {error}\nFix my code." + "prompt": "Here's my C# code:\n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\n\nI get the following error: {error}\nFix my code." }, "java": { - "prompt": "Here's my Java code: \n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nI get the following error: {error}\nFix my code." + "prompt": "Here's my Java code:\n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\n\nI get the following error: {error}\nFix my code." + }, + "duckdb": { + "prompt": "Here's my DuckDB code:\n```sql\n{code}\n```\n\nArguments are defined with comments like `-- $age (text) = 20` or `-- $name (text)` (one per row) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage.\n\nI get the following error: {error}\nFix my code." } } }; \ No newline at end of file diff --git a/frontend/src/lib/components/copilot/prompts/gen.yaml b/frontend/src/lib/components/copilot/prompts/gen.yaml index e7cf420654..d22a6e1473 100644 --- a/frontend/src/lib/components/copilot/prompts/gen.yaml +++ b/frontend/src/lib/components/copilot/prompts/gen.yaml @@ -151,6 +151,24 @@ prompts: No need to require autoload, it is already done. My instructions: {description} + csharp: + prompt: |- + + You have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result. + NuGet packages can be added using the format: #r "nuget: PackageName, Version" at the top of the script. + The Main method signature should be: public static ReturnType Main(parameter types...) + Arguments are used to generate the input specification and create the frontend UI for the script. + + My instructions: {description} + java: + prompt: |- + + You have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result. + Dependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script. + The method signature should be: public static Object main(parameter types...) + Arguments are used to generate the input specification and create the frontend UI for the script. + + My instructions: {description} frontend: prompt: |- Write client-side javascript code that should {description}. @@ -176,7 +194,7 @@ prompts: At the end of the code, the processed result has to be returned. - You can access the context object with the ctx global variable. + You can access the context object with the ctx global variable. The app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar' You can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean) You can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string) @@ -189,3 +207,9 @@ prompts: You can validate all fields of a form: validateAll(id: string, key: string) You can invalidate a specific field of a form: invalidate(id: string, key: string, error: string) + duckdb: + prompt: |- + + You have to write a statement for DuckDB. Arguments are defined with comments like `-- $age (text) = 20` or `-- $name (text)` (one per row) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage. + + My instructions: {description} diff --git a/frontend/src/lib/components/copilot/prompts/genPrompt.ts b/frontend/src/lib/components/copilot/prompts/genPrompt.ts index a318c55e12..478ef99f92 100644 --- a/frontend/src/lib/components/copilot/prompts/genPrompt.ts +++ b/frontend/src/lib/components/copilot/prompts/genPrompt.ts @@ -46,17 +46,20 @@ export const GEN_PROMPT = { "php": { "prompt": "\nYou have to write a function in php called \"main\". Specify the parameter types. Do not call the main function. You should generally return the result. The script must start with `\n{resourceTypes}\n\nYou need to define the type of the resources that are needed before the main function, but only include them if they are actually needed to achieve the function purpose.\nBefore defining each type, check if the class already exists using class_exists.\nThe resource type name has to be exactly as specified.\nIf you need to import libraries, you need to specify them as comments in the following manner before the main function:\n```\n// require:\n// mylibrary/mylibrary\n// myotherlibrary/myotherlibrary@optionalversion\n```\nNo need to require autoload, it is already done.\n\nMy instructions: {description}" }, - "frontend": { - "prompt": "Write client-side javascript code that should {description}. \n\n\nYou can access the context object with the ctx global variable. \nThe app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar'\nYou can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean)\nYou can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string)\nYou can use the setTab function to manually set the tab of a Tab component: setTab(id: string, index: string)\nYou can use the recompute function to recompute a component: recompute(id: string)\nYou can use the getAgGrid function to get the ag-grid instance of a table: getAgGrid(id: string)\nYou can use the setSelectedIndex function to select a row in a table or an AG Grid table: setSelectedIndex(id: string, index: number)\nThe setValue function is meant to set or force the value of a component: setValue(id: string, value: any)\nYou can validate a specific field of a form: validate(id: string, key: string)\nYou can validate all fields of a form: validateAll(id: string, key: string)\nYou can invalidate a specific field of a form: invalidate(id: string, key: string, error: string)\n" - }, - "transformer": { - "prompt": "Write client-side javascript code that should process the variable `result` according to the following instructions: {description}.\nThe variable `result` is available globally.\nAt the end of the code, the processed result has to be returned.\n\n\nYou can access the context object with the ctx global variable. \nThe app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar'\nYou can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean)\nYou can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string)\nYou can use the setTab function to manually set the tab of a Tab component: setTab(id: string, index: string)\nYou can use the recompute function to recompute a component: recompute(id: string)\nYou can use the getAgGrid function to get the ag-grid instance of a table: getAgGrid(id: string)\nYou can use the setSelectedIndex function to select a row in a table or an AG Grid table: setSelectedIndex(id: string, index: number)\nThe setValue function is meant to set or force the value of a component: setValue(id: string, value: any)\nYou can validate a specific field of a form: validate(id: string, key: string)\nYou can validate all fields of a form: validateAll(id: string, key: string)\nYou can invalidate a specific field of a form: invalidate(id: string, key: string, error: string)\n" - }, "csharp": { "prompt": "\nYou have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nMy instructions: {description}" }, "java": { "prompt": "\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nMy instructions: {description}" + }, + "frontend": { + "prompt": "Write client-side javascript code that should {description}. \n\n\nYou can access the context object with the ctx global variable. \nThe app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar'\nYou can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean)\nYou can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string)\nYou can use the setTab function to manually set the tab of a Tab component: setTab(id: string, index: string)\nYou can use the recompute function to recompute a component: recompute(id: string)\nYou can use the getAgGrid function to get the ag-grid instance of a table: getAgGrid(id: string)\nYou can use the setSelectedIndex function to select a row in a table or an AG Grid table: setSelectedIndex(id: string, index: number)\nThe setValue function is meant to set or force the value of a component: setValue(id: string, value: any)\nYou can validate a specific field of a form: validate(id: string, key: string)\nYou can validate all fields of a form: validateAll(id: string, key: string)\nYou can invalidate a specific field of a form: invalidate(id: string, key: string, error: string)\n" + }, + "transformer": { + "prompt": "Write client-side javascript code that should process the variable `result` according to the following instructions: {description}.\nThe variable `result` is available globally.\nAt the end of the code, the processed result has to be returned.\n\n\nYou can access the context object with the ctx global variable.\nThe app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar'\nYou can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean)\nYou can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string)\nYou can use the setTab function to manually set the tab of a Tab component: setTab(id: string, index: string)\nYou can use the recompute function to recompute a component: recompute(id: string)\nYou can use the getAgGrid function to get the ag-grid instance of a table: getAgGrid(id: string)\nYou can use the setSelectedIndex function to select a row in a table or an AG Grid table: setSelectedIndex(id: string, index: number)\nThe setValue function is meant to set or force the value of a component: setValue(id: string, value: any)\nYou can validate a specific field of a form: validate(id: string, key: string)\nYou can validate all fields of a form: validateAll(id: string, key: string)\nYou can invalidate a specific field of a form: invalidate(id: string, key: string, error: string)\n" + }, + "duckdb": { + "prompt": "\nYou have to write a statement for DuckDB. Arguments are defined with comments like `-- $age (text) = 20` or `-- $name (text)` (one per row) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage.\n\nMy instructions: {description}" } } }; \ No newline at end of file From c8aef6a44fa77087e61a732298dc4be52321e3ab Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:06:00 +0100 Subject: [PATCH 68/81] fix (#7188) --- frontend/src/lib/components/copilot/prompts/edit.yaml | 8 +++++--- frontend/src/lib/components/copilot/prompts/editPrompt.ts | 6 +++--- frontend/src/lib/components/copilot/prompts/fixPrompt.ts | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/copilot/prompts/edit.yaml b/frontend/src/lib/components/copilot/prompts/edit.yaml index b3c5d70ba9..22c2d54267 100644 --- a/frontend/src/lib/components/copilot/prompts/edit.yaml +++ b/frontend/src/lib/components/copilot/prompts/edit.yaml @@ -57,13 +57,13 @@ prompts: pub fn main(...) -> Result> ``` but do not call it. Favor idiomatic Rust patterns, ensuring safe handling of ownership and borrowing, robust error handling with `Result`, and concurrency if needed (`async`/`tokio` or std threading). - + Follow these guidelines: - Include necessary imports and modules only when needed. - Add comments explaining important operations and any unsafe usage (if absolutely required). - The generated code should be easily executable and testable in an integrated terminal. - My instructions: {description} + My instructions: {description} go: prompt: |- Here's my go code: @@ -220,9 +220,10 @@ prompts: {code} ``` - You have to write C# code with a public static Main method inside a class. Specify the parameter types. Do not call the main function. You should generally return the result. + You have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result. NuGet packages can be added using the format: #r "nuget: PackageName, Version" at the top of the script. The Main method signature should be: public static ReturnType Main(parameter types...) + Arguments are used to generate the input specification and create the frontend UI for the script. My instructions: {description} java: @@ -235,6 +236,7 @@ prompts: You have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result. Dependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script. The method signature should be: public static Object main(parameter types...) + Arguments are used to generate the input specification and create the frontend UI for the script. My instructions: {description} frontend: diff --git a/frontend/src/lib/components/copilot/prompts/editPrompt.ts b/frontend/src/lib/components/copilot/prompts/editPrompt.ts index dcfdd1d07b..8a5ac5ff4b 100644 --- a/frontend/src/lib/components/copilot/prompts/editPrompt.ts +++ b/frontend/src/lib/components/copilot/prompts/editPrompt.ts @@ -8,7 +8,7 @@ export const EDIT_PROMPT = { "prompt": "Here's my TypeScript code in a deno running environment:\n```typescript\n{code}\n```\n\nWe have to export a \"main\" function like this: \"export async function main(...)\" and specify the parameter types but do not call it. You should generally return the result.\nIf needed, the standard fetch method is available globally, do not import it.\nYou can take as parameters resources which are dictionaries containing credentials or configuration information. Name the resource parameters like this: \"{resource_type}Resource\". \nThe resource type name has to be exactly as specified.\n\n{resourceTypes}\n\nOnly define the type for resources that are actually needed to achieve the function purpose. If the type name conflicts with the imported object, rename the imported object NOT THE TYPE.\nNaming conventions are guidelines, but follow the user's naming choices in the existing code.\n\nMy instructions: {description}" }, "rust": { - "prompt": "Here's my Rust code:\n```rust\n{code}\n```\n\nPlease define a `main` function in Rust with this signature:\n```rust\npub fn main(...) -> Result>\n```\nbut do not call it. Favor idiomatic Rust patterns, ensuring safe handling of ownership and borrowing, robust error handling with `Result`, and concurrency if needed (`async`/`tokio` or std threading). \n\nFollow these guidelines:\n- Include necessary imports and modules only when needed.\n- Add comments explaining important operations and any unsafe usage (if absolutely required).\n- The generated code should be easily executable and testable in an integrated terminal.\n\nMy instructions: {description} " + "prompt": "Here's my Rust code:\n```rust\n{code}\n```\n\nPlease define a `main` function in Rust with this signature:\n```rust\npub fn main(...) -> Result>\n```\nbut do not call it. Favor idiomatic Rust patterns, ensuring safe handling of ownership and borrowing, robust error handling with `Result`, and concurrency if needed (`async`/`tokio` or std threading). \n\nFollow these guidelines:\n- Include necessary imports and modules only when needed.\n- Add comments explaining important operations and any unsafe usage (if absolutely required).\n- The generated code should be easily executable and testable in an integrated terminal.\n\nMy instructions: {description}" }, "go": { "prompt": "Here's my go code: \n```go\n{code}\n```\n\nWe have to export a \"main\" function. Import the packages you need. The return type of the function has to be ({return_type}, error). The file package has to be \"inner\"\n\nMy instructions: {description}" @@ -47,10 +47,10 @@ export const EDIT_PROMPT = { "prompt": "Here's my php code: \n```php\n{code}\n```\n\nYou have to write a function in php called \"main\". Specify the parameter types. Do not call the main function. You should generally return the result. The script must start with `\n{resourceTypes}\n\nYou need to define the type of the resources that are needed before the main function, but only include them if they are actually needed to achieve the function purpose.\nBefore defining each type, check if the class already exists using class_exists.\nThe resource type name has to be exactly as specified.\nIf you need to import libraries, you need to specify them as comments in the following manner before the main function:\n```\n// require:\n// mylibrary/mylibrary\n// myotherlibrary/myotherlibrary@optionalversion\n```\nNo need to require autoload, it is already done.\n\nMy instructions: {description}" }, "csharp": { - "prompt": "Here's my C# code:\n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\n\nMy instructions: {description}" + "prompt": "Here's my C# code:\n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nMy instructions: {description}" }, "java": { - "prompt": "Here's my Java code:\n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\n\nMy instructions: {description}" + "prompt": "Here's my Java code:\n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nMy instructions: {description}" }, "frontend": { "prompt": "Here's my client-side javascript code: \n```javascript\n{code}\n```\n\nYou can access the context object with the ctx global variable. \nThe app state is a store that can be used to store data. You can access and update the state object with the state global variable like this: state.foo = 'bar'\nYou can use the goto function to navigate to a specific URL: goto(path: string, newTab?: boolean)\nYou can use the openModal and closeModal functions to open and close a modal: openModal(id: string), closeModal(id: string)\nYou can use the setTab function to manually set the tab of a Tab component: setTab(id: string, index: string)\nYou can use the recompute function to recompute a component: recompute(id: string)\nYou can use the getAgGrid function to get the ag-grid instance of a table: getAgGrid(id: string)\nYou can use the setSelectedIndex function to select a row in a table or an AG Grid table: setSelectedIndex(id: string, index: number)\nThe setValue function is meant to set or force the value of a component: setValue(id: string, value: any)\nYou can validate a specific field of a form: validate(id: string, key: string)\nYou can validate all fields of a form: validateAll(id: string, key: string)\nYou can invalidate a specific field of a form: invalidate(id: string, key: string, error: string)\n\nMy instructions: {description}" diff --git a/frontend/src/lib/components/copilot/prompts/fixPrompt.ts b/frontend/src/lib/components/copilot/prompts/fixPrompt.ts index 9f3d48309c..c66f74dc31 100644 --- a/frontend/src/lib/components/copilot/prompts/fixPrompt.ts +++ b/frontend/src/lib/components/copilot/prompts/fixPrompt.ts @@ -47,10 +47,10 @@ export const FIX_PROMPT = { "prompt": "Here's my php code: \n```php\n{code}\n```\n\nYou have to write a function in php called \"main\". Specify the parameter types. Do not call the main function. You should generally return the result. The script must start with `\n{resourceTypes}\n\nYou need to define the type of the resources that are needed before the main function, but only include them if they are actually needed to achieve the function purpose.\nBefore defining each type, check if the class already exists using class_exists.\nThe resource type name has to be exactly as specified.\nIf you need to import libraries, you need to specify them as comments in the following manner before the main function:\n```\n// require:\n// mylibrary/mylibrary\n// myotherlibrary/myotherlibrary@optionalversion\n```\nNo need to require autoload, it is already done.\n\nI get the following error: {error}\nFix my code." }, "csharp": { - "prompt": "Here's my C# code:\n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\n\nI get the following error: {error}\nFix my code." + "prompt": "Here's my C# code:\n```csharp\n{code}\n```\n\nYou have to write C# code with a public static Main method inside a class. The class name is irrelevant. Specify the parameter types. Do not call the main function. You should generally return the result.\nNuGet packages can be added using the format: #r \"nuget: PackageName, Version\" at the top of the script.\nThe Main method signature should be: public static ReturnType Main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nI get the following error: {error}\nFix my code." }, "java": { - "prompt": "Here's my Java code:\n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\n\nI get the following error: {error}\nFix my code." + "prompt": "Here's my Java code:\n```java\n{code}\n```\n\nYou have to write Java code with a Main public class and a public static main() method. The return type can be Object or void. Do not call the main function. You should generally return the result.\nDependencies can be added using the format: //requirements://groupId:artifactId:version at the top of the script.\nThe method signature should be: public static Object main(parameter types...)\nArguments are used to generate the input specification and create the frontend UI for the script.\n\nI get the following error: {error}\nFix my code." }, "duckdb": { "prompt": "Here's my DuckDB code:\n```sql\n{code}\n```\n\nArguments are defined with comments like `-- $age (text) = 20` or `-- $name (text)` (one per row) and used in the statement with $age, $name, etc. To use Ducklake, attach it with `ATTACH 'ducklake' AS dl;` (for main ducklake) or `ATTACH 'ducklake://name' AS dl;` for named ducklakes. To connect to external databases, use `ATTACH '$res:path/to/resource' AS db (TYPE postgres);`. To read S3 files, use `SELECT * FROM read_csv('s3:///path/to/file.csv');` for default storage or `SELECT * FROM read_csv('s3://secondary_storage_name/path/to/file.csv');` for named storage.\n\nI get the following error: {error}\nFix my code." From b56e611700f06844dda4f30d02a1119e714d73a4 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:23:37 +0100 Subject: [PATCH 69/81] fix(aichat): fallback to completion if responses fails (#7190) * fallback to completion if responses fails * add missing fallbacks * remove test errors --- .../copilot/chat/AIChatManager.svelte.ts | 52 +++++++++++++------ frontend/src/lib/components/copilot/lib.ts | 25 +++++++-- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index 149f6930d8..5645792971 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -408,24 +408,42 @@ class AIChatManager { } const model = getCurrentModel() - const completionFn = - model.provider === 'anthropic' - ? getAnthropicCompletion - : model.provider === 'openai' || model.provider === 'azure_openai' - ? getOpenAIResponsesCompletion - : getCompletion - const parseFn = - model.provider === 'anthropic' - ? parseAnthropicCompletion - : model.provider === 'openai' || model.provider === 'azure_openai' - ? parseOpenAIResponsesCompletion - : parseOpenAICompletion + const isOpenAI = model.provider === 'openai' || model.provider === 'azure_openai' + const isAnthropic = model.provider === 'anthropic' - const completion = await completionFn( - [systemMessage, ...messages, ...(pendingUserMessage ? [pendingUserMessage] : [])], - abortController, - tools.map((t) => t.def) - ) + let completion: any + let parseFn: any + + const messageParams = [ + systemMessage, + ...messages, + ...(pendingUserMessage ? [pendingUserMessage] : []) + ] + const toolDefs = tools.map((t) => t.def) + + // For OpenAI/Azure, try Responses API first, fallback to Completions API + if (isOpenAI) { + try { + completion = await getOpenAIResponsesCompletion( + messageParams, + abortController, + toolDefs + ) + parseFn = parseOpenAIResponsesCompletion + } catch (err) { + console.warn('OpenAI Responses API failed, falling back to Completions API:', err) + completion = await getCompletion(messageParams, abortController, toolDefs, { + forceCompletions: true + }) + parseFn = parseOpenAICompletion + } + } else if (isAnthropic) { + completion = await getAnthropicCompletion(messageParams, abortController, toolDefs) + parseFn = parseAnthropicCompletion + } else { + completion = await getCompletion(messageParams, abortController, toolDefs) + parseFn = parseOpenAICompletion + } if (completion) { const continueCompletion = await parseFn( diff --git a/frontend/src/lib/components/copilot/lib.ts b/frontend/src/lib/components/copilot/lib.ts index 3f2686765b..1fa2a0d07c 100644 --- a/frontend/src/lib/components/copilot/lib.ts +++ b/frontend/src/lib/components/copilot/lib.ts @@ -675,7 +675,16 @@ export async function getNonStreamingCompletion( // Use Responses API for OpenAI and Azure OpenAI if (provider === 'openai' || provider === 'azure_openai') { - return getNonStreamingOpenAIResponsesCompletion(messages, abortController, testOptions) + try { + const response = await getNonStreamingOpenAIResponsesCompletion( + messages, + abortController, + testOptions + ) + return response + } catch (error) { + console.error('Error using Responses API:', error) + } } const fetchOptions: { @@ -793,13 +802,21 @@ export async function getFimCompletion( export async function getCompletion( messages: ChatCompletionMessageParam[], abortController: AbortController, - tools?: OpenAI.Chat.Completions.ChatCompletionTool[] + tools?: OpenAI.Chat.Completions.ChatCompletionTool[], + options?: { + forceCompletions?: boolean + } ): Promise> { const { provider, config } = getProviderAndCompletionConfig({ messages, stream: true, tools }) // Use Responses API for OpenAI and Azure OpenAI - if (provider === 'openai' || provider === 'azure_openai') { - return getOpenAIResponsesCompletionStream(messages, abortController, tools) as any + if ((provider === 'openai' || provider === 'azure_openai') && !options?.forceCompletions) { + try { + const stream = getOpenAIResponsesCompletionStream(messages, abortController, tools) as any + return stream + } catch (error) { + console.error('Error using Responses API:', error) + } } // Use Completions API for other providers From 338fd8a38cb035de298006ed1b96b6513eab9769 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Thu, 20 Nov 2025 19:01:39 +0100 Subject: [PATCH 70/81] fix(frontend): show code/lock in flow steps on runs page (#7191) --- frontend/src/lib/components/runs/JobRunsPreview.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/runs/JobRunsPreview.svelte b/frontend/src/lib/components/runs/JobRunsPreview.svelte index 8b0a9954fd..5b224ed5f0 100644 --- a/frontend/src/lib/components/runs/JobRunsPreview.svelte +++ b/frontend/src/lib/components/runs/JobRunsPreview.svelte @@ -222,7 +222,11 @@ {#if job?.type === 'CompletedJob'} {#if job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)}
-
{:else} @@ -301,7 +305,7 @@ {#if job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)}
- +
{:else}
Job is still running
From e9691c9eb080236849850a1ea6f3237ae39a2c4c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 20 Nov 2025 18:58:35 +0000 Subject: [PATCH 71/81] feat(ee): support iamrds --- backend/Cargo.lock | 26 ++++ backend/Cargo.toml | 1 + backend/ee-repo-ref.txt | 2 +- backend/src/main.rs | 18 ++- backend/windmill-api/src/resources.rs | 2 +- backend/windmill-api/src/settings.rs | 3 +- backend/windmill-common/Cargo.toml | 9 +- backend/windmill-common/src/lib.rs | 182 +++++++++++++++++++--- backend/windmill-common/src/workspaces.rs | 2 +- 9 files changed, 212 insertions(+), 33 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 717b025e28..0d06a86718 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -894,6 +894,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "aws-sdk-rds" +version = "1.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fdc73bba9b0b20e7f3eb35ea692b8f2eb3e3b4ed0e80909a7c4ae81da59dc0" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", + "url", +] + [[package]] name = "aws-sdk-sqs" version = "1.77.0" @@ -15427,6 +15452,7 @@ dependencies = [ "async-trait", "aws-config", "aws-credential-types", + "aws-sdk-rds", "aws-sdk-sts", "aws-smithy-types", "aws-smithy-types-convert", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 666727bf9c..702d762140 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -268,6 +268,7 @@ regex = "^1" semver = "^1" aws-sigv4 = "^1.3.4" aws-sdk-config = "=1.68.0" +aws-sdk-rds = "^1" async-trait = "0.1.88" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index eeaa99bcfe..0f8e46b3e7 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -5dd7ab7ce9e78e1b96ea78fd297da95d9b69e7a3 \ No newline at end of file +6fedd4b83c41fdbf93d2c3fea4d89720aeccdcda \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 26e1755fea..4c4a848e4d 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -211,6 +211,7 @@ lazy_static::lazy_static! { } pub fn main() -> anyhow::Result<()> { + // On Windows with enterprise feature, check if running as a service #[cfg(all(windows, feature = "enterprise", feature = "private"))] { @@ -317,6 +318,10 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> { } async fn windmill_main() -> anyhow::Result<()> { + // windmill_common::db_iam::main().await?; + + // return Ok(()); + let (killpill_tx, mut killpill_rx) = KillpillSender::new(2); let mut monitor_killpill_rx = killpill_tx.subscribe(); let (killpill_phase2_tx, _killpill_phase2_rx) = tokio::sync::broadcast::channel::<()>(2); @@ -511,7 +516,9 @@ async fn windmill_main() -> anyhow::Result<()> { conn } else { // This time we use a pool of connections - let db = windmill_common::connect_db(server_mode, indexer_mode, worker_mode).await?; + let db = windmill_common::connect_db(server_mode, indexer_mode, worker_mode, + #[cfg(feature = "private")] + killpill_rx.resubscribe()).await?; // NOTE: Variable/resource cache initialization moved to API server in windmill-api @@ -832,10 +839,10 @@ Windmill Community Edition {GIT_VERSION} match conn { Connection::Sql(ref db) => { let base_internal_url = base_internal_url.to_string(); - let db_url: String = get_database_url().await?; + let db_url = get_database_url().await?; let db = db.clone(); let h = tokio::spawn(async move { - let mut listener = retry_listen_pg(&db_url).await; + let mut listener = retry_listen_pg(&db_url.as_str().await).await; let mut last_listener_refresh = Instant::now(); let mut monitor_iteration: u64 = 0; let rd_shift: u8 = rand::rng().random_range(0..200); @@ -1169,13 +1176,14 @@ Windmill Community Edition {GIT_VERSION} }, Err(e) => { tracing::error!(error = %e, "Could not receive notification, attempting to reconnect listener"); + let db_url = db_url.clone(); tokio::select! { biased; _ = monitor_killpill_rx.recv() => { tracing::info!("received killpill for monitor job"); break; }, - new_listener = retry_listen_pg(&db_url) => { + new_listener = async move { retry_listen_pg(&db_url.as_str().await).await } => { listener = new_listener; continue; } @@ -1189,7 +1197,7 @@ Windmill Community Edition {GIT_VERSION} if let Err(e) = listener.unlisten_all().await { tracing::error!(error = %e, "Could not unlisten to database"); } - listener = retry_listen_pg(&db_url).await; + listener = retry_listen_pg(&db_url.as_str().await).await; initial_load( &conn, tx.clone(), diff --git a/backend/windmill-api/src/resources.rs b/backend/windmill-api/src/resources.rs index 4aafcb0207..503c60e1f8 100644 --- a/backend/windmill-api/src/resources.rs +++ b/backend/windmill-api/src/resources.rs @@ -472,7 +472,7 @@ pub async fn get_resource_value_interpolated_internal( // This is a special syntax to help debugging ducklake catalogs stored in the instance if let Some(dbname) = path.strip_prefix("INSTANCE_DUCKLAKE_CATALOG/") { require_super_admin(db, &authed.email).await?; - let pg_creds = parse_postgres_url(&get_database_url().await?)?; + let pg_creds = parse_postgres_url(&get_database_url().await?.as_str().await)?; return Ok(Some(serde_json::json!({ "dbname": dbname, "host": pg_creds.host, diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index 8fa6a0138b..bff2b282b6 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -647,8 +647,7 @@ async fn setup_ducklake_catalog_db_inner( ) -> Result<()> { require_super_admin(db, &authed.email).await?; logs.super_admin = "OK".to_string(); - let pg_creds = &get_database_url().await?; - let pg_creds = parse_postgres_url(pg_creds)?; + let pg_creds = parse_postgres_url(&get_database_url().await?.as_str().await)?; logs.database_credentials = "OK".to_string(); // Validate name to ensure it only contains alphanumeric characters diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index 123d1a5ea4..c0a52c6677 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -7,14 +7,14 @@ edition.workspace = true [features] default = [] enterprise = [] -private = [] +private = ["dep:aws-sdk-rds"] jemalloc = ["dep:tikv-jemalloc-ctl"] tantivy = [] prometheus = ["dep:prometheus"] loki = ["dep:tracing-loki"] benchmark = [] -parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts", "dep:aws-smithy-types-convert", "dep:datafusion"] -aws_auth = ["dep:aws-sdk-sts", "dep:aws-config"] +parquet = ["dep:object_store", "dep:aws-sdk-sts", "dep:aws-smithy-types-convert", "dep:datafusion"] +aws_auth = ["dep:aws-sdk-sts"] otel = ["dep:opentelemetry-semantic-conventions", "dep:opentelemetry-otlp", "dep:opentelemetry_sdk", "dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-appender-tracing", "dep:tonic"] smtp = ["dep:mail-send"] @@ -62,7 +62,7 @@ tracing-loki = { version = "^0", optional = true } magic-crypt.workspace = true object_store = { workspace = true, optional = true } prometheus = { workspace = true, optional = true } -aws-config = { workspace = true, optional = true } +aws-config.workspace = true aws-sdk-sts = { workspace = true, optional = true } aws-credential-types.workspace = true aws-smithy-types.workspace = true @@ -70,6 +70,7 @@ base64.workspace = true bitflags.workspace = true aws-smithy-types-convert = { workspace = true, optional = true } +aws-sdk-rds = { workspace = true, optional = true } indexmap.workspace = true bytes.workspace = true mail-send = { workspace = true, optional = true } diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index ada171a230..30c6411305 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -35,6 +35,8 @@ pub mod bench; pub mod cache; pub mod client; pub mod db; +#[cfg(all(feature = "enterprise", feature = "private"))] +mod db_iam_ee; #[cfg(feature = "private")] pub mod ee; pub mod ee_oss; @@ -375,27 +377,120 @@ pub fn parse_postgres_url(url: &str) -> Result { }) } -pub async fn get_database_url() -> Result { - use std::env::var; - use tokio::fs::File; - use tokio::io::AsyncReadExt; - match var("DATABASE_URL_FILE") { - Ok(file_path) => { - let mut file = File::open(file_path).await?; - let mut contents = String::new(); - file.read_to_string(&mut contents).await?; - Ok(contents.trim().to_string()) +#[derive(Clone)] +pub enum DatabaseUrl { + #[cfg(all(feature = "enterprise", feature = "private"))] + IamRds(std::sync::Arc>), + Static(String), +} + +impl DatabaseUrl { + pub async fn as_str(&self) -> String { + match self { + #[cfg(all(feature = "enterprise", feature = "private"))] + DatabaseUrl::IamRds(rds_url) => { + let guard = rds_url.read().await; + guard.as_str().to_string() + } + DatabaseUrl::Static(url) => url.clone(), } - Err(_) => var("DATABASE_URL").map_err(|_| { - Error::BadConfig( - "Either DATABASE_URL_FILE or DATABASE_URL env var is missing".to_string(), - ) - }), } + + pub async fn refresh(&self) -> anyhow::Result<()> { + match self { + #[cfg(all(feature = "enterprise", feature = "private"))] + DatabaseUrl::IamRds(rds_url) => rds_url.write().await.refresh().await, + DatabaseUrl::Static(_) => Ok(()), + } + } + + +} + +static DATABASE_URL_CACHE: tokio::sync::OnceCell = + tokio::sync::OnceCell::const_new(); + +pub async fn get_database_url() -> Result { + let database_url = DATABASE_URL_CACHE + .get_or_try_init(|| async { + use std::env::var; + use tokio::fs::File; + use tokio::io::AsyncReadExt; + + let url = match var("DATABASE_URL_FILE") { + Ok(file_path) => { + let mut file = File::open(file_path).await?; + let mut contents = String::new(); + file.read_to_string(&mut contents).await?; + Ok(contents.trim().to_string()) + } + Err(_) => var("DATABASE_URL").map_err(|_| { + Error::BadConfig( + "Either DATABASE_URL_FILE or DATABASE_URL env var is missing".to_string(), + ) + }), + }?; + + let parsed_url = url::Url::parse(&url)?; + + if parsed_url.password().is_some_and(|x| x == "iamrds") { + let region = var("AWS_REGION").map_err(|_| { + Error::BadConfig( + "AWS_REGION env var is required for IAM RDS authentication".to_string(), + ) + })?; + + tracing::info!("iamrds mode detected, generating IAM RDS URL for region: {region}"); + #[cfg(all(feature = "enterprise", feature = "private"))] + { + let rds_url = db_iam_ee::generate_database_url(&url, ®ion) + .await + .map_err(|e| { + Error::InternalErr(format!("Failed to generate IAM database URL: {}", e)) + })?; + tracing::info!("IAM RDS URL generated successfully"); + Ok::(DatabaseUrl::IamRds( + std::sync::Arc::new(tokio::sync::RwLock::new(rds_url)) + )) + } + + #[cfg(not(all(feature = "enterprise", feature = "private")))] + { + return Err(Error::BadConfig("IAM RDS authentication is not enabled in OSS mode".to_string())); + } + } else { + Ok::(DatabaseUrl::Static(url.to_string())) + } + }) + .await?; + + // Check if we need to refresh and do so if necessary + #[cfg(feature = "enterprise")] + if let DatabaseUrl::IamRds(ref rds_url_lock) = database_url { + // Check if refresh is needed + let needs_refresh = { + let read_guard = rds_url_lock.read().await; + read_guard.needs_refresh() + }; + + // If refresh is needed, acquire write lock and refresh + if needs_refresh { + let mut write_guard = rds_url_lock.write().await; + // Double-check after acquiring write lock (another task might have refreshed) + if write_guard.needs_refresh() { + write_guard.refresh().await.map_err(|e| { + Error::InternalErr(format!("Failed to refresh IAM token: {}", e)) + })?; + } + } + } + + // Return the URL string + Ok(database_url.clone()) } pub async fn initial_connection() -> Result, error::Error> { - let database_url = get_database_url().await?; + let database_url = get_database_url().await?.as_str().await; sqlx::postgres::PgPoolOptions::new() .max_connections(2) .connect_with(sqlx::postgres::PgConnectOptions::from_str(&database_url)?) @@ -407,6 +502,8 @@ pub async fn connect_db( server_mode: bool, indexer_mode: bool, worker_mode: bool, + #[cfg(feature = "private")] + mut killpill_rx: tokio::sync::broadcast::Receiver<()>, ) -> anyhow::Result> { use anyhow::Context; @@ -431,11 +528,58 @@ pub async fn connect_db( } }; - Ok(connect(&database_url, max_connections, worker_mode).await?) + + let pool = connect(database_url.clone(), max_connections, worker_mode).await?; + #[cfg(all(feature = "enterprise", feature = "private"))] + let pool2 = pool.clone(); + #[cfg(all(feature = "enterprise", feature = "private"))] + if let DatabaseUrl::IamRds(database_url) = database_url { + tokio::spawn(async move { + loop { + tokio::select! { + _ = killpill_rx.recv() => { + break; + } + _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => { + let needs_refresh = { + let read_guard = database_url.read().await; + read_guard.needs_refresh() + }; + if needs_refresh { + let new_url = tokio::time::timeout(std::time::Duration::from_secs(10), get_database_url()).await; + match new_url { + Ok(Ok(new_url)) => { + let new_url = new_url.as_str().await; + let connect_options = sqlx::postgres::PgConnectOptions::from_str(&new_url); + if let Err(e) = connect_options { + tracing::error!("Error parsing IAM RDS URL as connect options, retrying in 10s: {}", e); + continue; + } + pool2.set_connect_options(connect_options.unwrap()); + tracing::info!("Refreshed IAM RDS URL successfully"); + } + Ok(Err(e)) => { + tracing::error!("Error refreshing IAM RDS URL, trying again in 10s: {}", e); + continue; + } + Err(e) => { + tracing::error!("Timeout after 10s refreshing IAM RDS URL, trying again in 10 seconds: {}", e); + continue; + } + } + } + } + } + + } + }); + } + + Ok(pool) } pub async fn connect( - database_url: &str, + database_url: DatabaseUrl, max_connections: u32, worker_mode: bool, ) -> Result, error::Error> { @@ -484,7 +628,7 @@ pub async fn connect( } }) .connect_with( - sqlx::postgres::PgConnectOptions::from_str(database_url)?.statement_cache_capacity(400), + sqlx::postgres::PgConnectOptions::from_str(&database_url.as_str().await)?.statement_cache_capacity(400), ) .await .map_err(|err| Error::ConnectingToDatabase(err.to_string())) diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index edd9c8abe5..0fa8769ffd 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -208,7 +208,7 @@ pub async fn get_ducklake_from_db_unchecked( let catalog_resource = if ducklake.catalog.resource_type == DucklakeCatalogResourceType::Instance { - let pg_creds = parse_postgres_url(&get_database_url().await?)?; + let pg_creds = parse_postgres_url(&get_database_url().await?.as_str().await)?; json!({ "dbname": ducklake.catalog.resource_path, "host": pg_creds.host, From f51771183604f7759c57983f00ca2b7c93001122 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 20 Nov 2025 19:04:22 +0000 Subject: [PATCH 72/81] nit oss full --- backend/windmill-common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 30c6411305..04e59cfac9 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -465,7 +465,7 @@ pub async fn get_database_url() -> Result { .await?; // Check if we need to refresh and do so if necessary - #[cfg(feature = "enterprise")] + #[cfg(all(feature = "enterprise", feature = "private"))] if let DatabaseUrl::IamRds(ref rds_url_lock) = database_url { // Check if refresh is needed let needs_refresh = { From ec81696828676319b6904cab0fe8be9621dd04da Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 20 Nov 2025 20:04:48 +0100 Subject: [PATCH 73/81] chore(main): release 1.582.0 (#7189) * chore(main): release 1.582.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 14 ++++ backend/Cargo.lock | 68 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 65 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dc919ee16..13ff519ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.582.0](https://github.com/windmill-labs/windmill/compare/v1.581.1...v1.582.0) (2025-11-20) + + +### Features + +* **aichat:** handle duckdb scripts ([#7187](https://github.com/windmill-labs/windmill/issues/7187)) ([ce5a318](https://github.com/windmill-labs/windmill/commit/ce5a31865cf6965ec28c449c2a832b93572a8eb6)) +* **ee:** support iamrds ([e9691c9](https://github.com/windmill-labs/windmill/commit/e9691c9eb080236849850a1ea6f3237ae39a2c4c)) + + +### Bug Fixes + +* **aichat:** fallback to completion if responses fails ([#7190](https://github.com/windmill-labs/windmill/issues/7190)) ([b56e611](https://github.com/windmill-labs/windmill/commit/b56e611700f06844dda4f30d02a1119e714d73a4)) +* **frontend:** show code/lock in flow steps on runs page ([#7191](https://github.com/windmill-labs/windmill/issues/7191)) ([338fd8a](https://github.com/windmill-labs/windmill/commit/338fd8a38cb035de298006ed1b96b6513eab9769)) + ## [1.581.1](https://github.com/windmill-labs/windmill/compare/v1.581.0...v1.581.1) (2025-11-20) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 0d06a86718..20e6087422 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -6327,9 +6327,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hashify" @@ -10311,7 +10311,7 @@ checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" dependencies = [ "ahash 0.8.12", "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "parking_lot 0.12.5", ] @@ -15213,7 +15213,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15275,7 +15275,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "argon2", @@ -15396,7 +15396,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.581.1" +version = "1.582.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15411,7 +15411,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.581.1" +version = "1.582.0" dependencies = [ "chrono", "lazy_static", @@ -15425,7 +15425,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "axum", @@ -15444,7 +15444,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "async-recursion", @@ -15533,7 +15533,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.581.1" +version = "1.582.0" dependencies = [ "regex", "serde", @@ -15548,7 +15548,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "bytes", @@ -15572,7 +15572,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.581.1" +version = "1.582.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15584,7 +15584,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.581.1" +version = "1.582.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15593,7 +15593,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "lazy_static", @@ -15605,7 +15605,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "serde_json", @@ -15617,7 +15617,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "gosyn", @@ -15629,7 +15629,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "lazy_static", @@ -15641,7 +15641,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "serde_json", @@ -15653,7 +15653,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "nu-parser", @@ -15664,7 +15664,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15675,7 +15675,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15687,7 +15687,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "async-recursion", @@ -15710,7 +15710,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "lazy_static", @@ -15724,7 +15724,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15741,7 +15741,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "lazy_static", @@ -15755,7 +15755,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "lazy_static", @@ -15773,7 +15773,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "serde", @@ -15784,7 +15784,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "async-recursion", @@ -15821,7 +15821,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.581.1" +version = "1.582.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15831,7 +15831,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.581.1" +version = "1.582.0" dependencies = [ "anyhow", "async-once-cell", @@ -16628,18 +16628,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" dependencies = [ "proc-macro2", "quote", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 702d762140..226d33cdbb 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.581.1" +version = "1.582.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.581.1" +version = "1.582.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 9dfcbb25ec..1674a9c6b4 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.581.1 + version: 1.582.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 70f3b3d53b..7d2c420419 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.581.1"; +export const VERSION = "v1.582.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index de51bb1164..b86cb08287 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.581.1"; +export const VERSION = "1.582.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 38cbcfb409..95a147695c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.581.1", + "version": "1.582.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.581.1", + "version": "1.582.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 1c7f547522..ea48cee3e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.581.1", + "version": "1.582.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 493c7c1ca9..e5a9cd32d7 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.581.1" -wmill_pg = ">=1.581.1" +wmill = ">=1.582.0" +wmill_pg = ">=1.582.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index d3e9e0ad99..4f70ab8b32 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.581.1 + version: 1.582.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index aba6ec750b..738b11fb3f 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.581.1' + ModuleVersion = '1.582.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 9604c22fbf..7d0f606c67 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.581.1" +version = "1.582.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 236f748be7..2ab6ca2ef5 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.581.1" +version = "1.582.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 6c9570044c..401fcd22cf 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.581.1", + "version": "1.582.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index b76c958a06..0418d7d989 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.581.1", + "version": "1.582.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 902f26d0c0..13ee58993d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.581.1 +1.582.0 From a3b4cfcb8f11db326b0ebf1777ad7e6479425125 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 21 Nov 2025 07:04:28 +0000 Subject: [PATCH 74/81] fix: fix aws oidc refresh --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 0f8e46b3e7..9f6c5197e8 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -6fedd4b83c41fdbf93d2c3fea4d89720aeccdcda \ No newline at end of file +1a15bf2491f33836056595cacd0f2eb59b50bb25 \ No newline at end of file From 67b3fd6a84a0e5e5c3399df18be8f9f0c1a0cedf Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 21 Nov 2025 08:09:51 +0100 Subject: [PATCH 75/81] chore(main): release 1.582.1 (#7192) * chore(main): release 1.582.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 58 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 53 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13ff519ec2..40ca0f1d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.582.1](https://github.com/windmill-labs/windmill/compare/v1.582.0...v1.582.1) (2025-11-21) + + +### Bug Fixes + +* fix aws oidc refresh ([a3b4cfc](https://github.com/windmill-labs/windmill/commit/a3b4cfcb8f11db326b0ebf1777ad7e6479425125)) + ## [1.582.0](https://github.com/windmill-labs/windmill/compare/v1.581.1...v1.582.0) (2025-11-20) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 20e6087422..9e7882df42 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -896,9 +896,9 @@ dependencies = [ [[package]] name = "aws-sdk-rds" -version = "1.116.0" +version = "1.117.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fdc73bba9b0b20e7f3eb35ea692b8f2eb3e3b4ed0e80909a7c4ae81da59dc0" +checksum = "56b116d0229c41d5b72050dda07aa2cc7d1c571de8f7870c8b87fa297a724982" dependencies = [ "aws-credential-types", "aws-runtime", @@ -15213,7 +15213,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -15275,7 +15275,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "argon2", @@ -15396,7 +15396,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.582.0" +version = "1.582.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15411,7 +15411,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.582.0" +version = "1.582.1" dependencies = [ "chrono", "lazy_static", @@ -15425,7 +15425,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "axum", @@ -15444,7 +15444,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "async-recursion", @@ -15533,7 +15533,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.582.0" +version = "1.582.1" dependencies = [ "regex", "serde", @@ -15548,7 +15548,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "bytes", @@ -15572,7 +15572,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.582.0" +version = "1.582.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15584,7 +15584,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.582.0" +version = "1.582.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15593,7 +15593,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "lazy_static", @@ -15605,7 +15605,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "serde_json", @@ -15617,7 +15617,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "gosyn", @@ -15629,7 +15629,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "lazy_static", @@ -15641,7 +15641,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "serde_json", @@ -15653,7 +15653,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "nu-parser", @@ -15664,7 +15664,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15675,7 +15675,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15687,7 +15687,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "async-recursion", @@ -15710,7 +15710,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "lazy_static", @@ -15724,7 +15724,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15741,7 +15741,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "lazy_static", @@ -15755,7 +15755,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "lazy_static", @@ -15773,7 +15773,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "serde", @@ -15784,7 +15784,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "async-recursion", @@ -15821,7 +15821,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.582.0" +version = "1.582.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15831,7 +15831,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.582.0" +version = "1.582.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 226d33cdbb..f83c5b6818 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.582.0" +version = "1.582.1" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.582.0" +version = "1.582.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 1674a9c6b4..801e1d10c2 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.582.0 + version: 1.582.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 7d2c420419..aa8a3b96bb 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.582.0"; +export const VERSION = "v1.582.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index b86cb08287..7a7798dbc2 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.582.0"; +export const VERSION = "1.582.1"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 95a147695c..40bb7b2ea8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.582.0", + "version": "1.582.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.582.0", + "version": "1.582.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index ea48cee3e4..13889acaf7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.582.0", + "version": "1.582.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index e5a9cd32d7..b80e6df023 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.582.0" -wmill_pg = ">=1.582.0" +wmill = ">=1.582.1" +wmill_pg = ">=1.582.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 4f70ab8b32..bc865ff1a2 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.582.0 + version: 1.582.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 738b11fb3f..c4e3665c76 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.582.0' + ModuleVersion = '1.582.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 7d0f606c67..5e290ed418 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.582.0" +version = "1.582.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 2ab6ca2ef5..b2ffb6f13a 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.582.0" +version = "1.582.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 401fcd22cf..06ed5254c0 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.582.0", + "version": "1.582.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 0418d7d989..c9c8e5bdc6 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.582.0", + "version": "1.582.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 13ee58993d..69ea07ac25 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.582.0 +1.582.1 From 98bdb6825a1b85c973ddec3e6b933e4a3d6d6972 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 21 Nov 2025 07:12:36 +0000 Subject: [PATCH 76/81] fix: fix aws oidc refresh --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 9f6c5197e8..c8c41644d5 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -1a15bf2491f33836056595cacd0f2eb59b50bb25 \ No newline at end of file +7f27da56373dde0c37081f38227abc629436819e \ No newline at end of file From dd320a6883a9666504fb501f343b993074fa3cc0 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 21 Nov 2025 08:17:41 +0100 Subject: [PATCH 77/81] chore(main): release 1.582.2 (#7193) * chore(main): release 1.582.2 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 54 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ca0f1d95..c42c850475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.582.2](https://github.com/windmill-labs/windmill/compare/v1.582.1...v1.582.2) (2025-11-21) + + +### Bug Fixes + +* fix aws oidc refresh ([98bdb68](https://github.com/windmill-labs/windmill/commit/98bdb6825a1b85c973ddec3e6b933e4a3d6d6972)) + ## [1.582.1](https://github.com/windmill-labs/windmill/compare/v1.582.0...v1.582.1) (2025-11-21) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9e7882df42..7c9a4f373c 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15213,7 +15213,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "aws-sdk-config", @@ -15275,7 +15275,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "argon2", @@ -15396,7 +15396,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.582.1" +version = "1.582.2" dependencies = [ "base64 0.22.1", "chrono", @@ -15411,7 +15411,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.582.1" +version = "1.582.2" dependencies = [ "chrono", "lazy_static", @@ -15425,7 +15425,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "axum", @@ -15444,7 +15444,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "async-recursion", @@ -15533,7 +15533,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.582.1" +version = "1.582.2" dependencies = [ "regex", "serde", @@ -15548,7 +15548,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "bytes", @@ -15572,7 +15572,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.582.1" +version = "1.582.2" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15584,7 +15584,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.582.1" +version = "1.582.2" dependencies = [ "convert_case 0.6.0", "serde", @@ -15593,7 +15593,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "lazy_static", @@ -15605,7 +15605,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "serde_json", @@ -15617,7 +15617,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "gosyn", @@ -15629,7 +15629,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "lazy_static", @@ -15641,7 +15641,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "serde_json", @@ -15653,7 +15653,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "nu-parser", @@ -15664,7 +15664,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15675,7 +15675,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15687,7 +15687,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "async-recursion", @@ -15710,7 +15710,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "lazy_static", @@ -15724,7 +15724,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15741,7 +15741,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "lazy_static", @@ -15755,7 +15755,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "lazy_static", @@ -15773,7 +15773,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "serde", @@ -15784,7 +15784,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "async-recursion", @@ -15821,7 +15821,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.582.1" +version = "1.582.2" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15831,7 +15831,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.582.1" +version = "1.582.2" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f83c5b6818..45ff97c15e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.582.1" +version = "1.582.2" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.582.1" +version = "1.582.2" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 801e1d10c2..10acdee52f 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.582.1 + version: 1.582.2 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index aa8a3b96bb..3592faccdd 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.582.1"; +export const VERSION = "v1.582.2"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 7a7798dbc2..157eda767a 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.582.1"; +export const VERSION = "1.582.2"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 40bb7b2ea8..50a0547dfa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.582.1", + "version": "1.582.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.582.1", + "version": "1.582.2", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 13889acaf7..72ec938dab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.582.1", + "version": "1.582.2", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index b80e6df023..108c9edfb9 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.582.1" -wmill_pg = ">=1.582.1" +wmill = ">=1.582.2" +wmill_pg = ">=1.582.2" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index bc865ff1a2..d8889ac2e1 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.582.1 + version: 1.582.2 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index c4e3665c76..b90734f2ba 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.582.1' + ModuleVersion = '1.582.2' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 5e290ed418..d6b7b52fda 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.582.1" +version = "1.582.2" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index b2ffb6f13a..c0cef5f1e0 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.582.1" +version = "1.582.2" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 06ed5254c0..780fd79cd1 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.582.1", + "version": "1.582.2", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index c9c8e5bdc6..1fb003024e 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.582.1", + "version": "1.582.2", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 69ea07ac25..e5791defc6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.582.1 +1.582.2 From 3ae3b40cc7b610548b4528b4759f633717fd284d Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 21 Nov 2025 07:43:23 +0000 Subject: [PATCH 78/81] nit style --- backend/ee-repo-ref.txt | 2 +- backend/src/main.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index c8c41644d5..9af7fc595d 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -7f27da56373dde0c37081f38227abc629436819e \ No newline at end of file +40f77067886158bc816637004673ea5cf03a51f8 \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 4c4a848e4d..a619572f50 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -211,7 +211,6 @@ lazy_static::lazy_static! { } pub fn main() -> anyhow::Result<()> { - // On Windows with enterprise feature, check if running as a service #[cfg(all(windows, feature = "enterprise", feature = "private"))] { @@ -318,10 +317,6 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> { } async fn windmill_main() -> anyhow::Result<()> { - // windmill_common::db_iam::main().await?; - - // return Ok(()); - let (killpill_tx, mut killpill_rx) = KillpillSender::new(2); let mut monitor_killpill_rx = killpill_tx.subscribe(); let (killpill_phase2_tx, _killpill_phase2_rx) = tokio::sync::broadcast::channel::<()>(2); @@ -516,9 +511,14 @@ async fn windmill_main() -> anyhow::Result<()> { conn } else { // This time we use a pool of connections - let db = windmill_common::connect_db(server_mode, indexer_mode, worker_mode, + let db = windmill_common::connect_db( + server_mode, + indexer_mode, + worker_mode, #[cfg(feature = "private")] - killpill_rx.resubscribe()).await?; + killpill_rx.resubscribe(), + ) + .await?; // NOTE: Variable/resource cache initialization moved to API server in windmill-api From 9a2e27533c7b427c1b8eb0ddbb1bdd3da131d6c8 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 21 Nov 2025 09:05:04 +0000 Subject: [PATCH 79/81] nit style --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 9af7fc595d..25b896040a 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -40f77067886158bc816637004673ea5cf03a51f8 \ No newline at end of file +bb4021e25e3adee47d77041a84365698d4a60241 \ No newline at end of file From 0aaaed9590b5b8d69d2ea6a2d7c11bfe786f3129 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 21 Nov 2025 09:30:18 +0000 Subject: [PATCH 80/81] nit style --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 25b896040a..5e8cbdea21 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -bb4021e25e3adee47d77041a84365698d4a60241 \ No newline at end of file +c90bfc11c7c643b2c8c5111f092e7ae142318e9d \ No newline at end of file From 1910daeb92d95240e33fc7eecab22f0fa18a22dc Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:30:26 +0100 Subject: [PATCH 81/81] update claude code nix (#7195) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 019aff4b34..57fa428e72 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs-claude": { "locked": { - "lastModified": 1753694789, - "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", + "lastModified": 1763421233, + "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", + "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648", "type": "github" }, "original": {