diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 43e8954c33..940eb34b1f 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -86,4 +86,4 @@ jobs: 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,duckdb,license,python,rust,scoped_cache,private --all -- --nocapture + DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private --all -- --nocapture diff --git a/CHANGELOG.md b/CHANGELOG.md index d047267e1b..587414b789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.583.1](https://github.com/windmill-labs/windmill/compare/v1.583.0...v1.583.1) (2025-11-21) + + +### Bug Fixes + +* **cli:** improve cli local policy generation ([b857d15](https://github.com/windmill-labs/windmill/commit/b857d15352ab422c1d477cd9172856956c2c4b7c)) + ## [1.583.0](https://github.com/windmill-labs/windmill/compare/v1.582.2...v1.583.0) (2025-11-21) diff --git a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json index e7ed0aee65..d29a18c691 100644 --- a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json +++ b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json @@ -46,11 +46,11 @@ ] }, "nullable": [ - false, - false, - false, - false, - false, + true, + true, + true, + true, + true, true, true ] diff --git a/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json b/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json index ecaf828737..bc15928e38 100644 --- a/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json +++ b/backend/.sqlx/query-0a7132202ecf6c4c10340921644a90d9206c45d92a0423c0bc2396d0d66a0b0d.json @@ -59,7 +59,9 @@ "failure", "command", "approval", - "preprocessor" + "preprocessor", + "schedule_handler_old", + "dynamic_skip" ] } } diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 3f992182f2..ea9bffcffe 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15213,7 +15213,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -15275,7 +15275,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "argon2", @@ -15396,7 +15396,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.583.0" +version = "1.583.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15411,7 +15411,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.583.0" +version = "1.583.1" dependencies = [ "chrono", "lazy_static", @@ -15425,7 +15425,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "axum", @@ -15444,7 +15444,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "async-recursion", @@ -15533,7 +15533,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.583.0" +version = "1.583.1" dependencies = [ "regex", "serde", @@ -15548,7 +15548,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "bytes", @@ -15572,7 +15572,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.583.0" +version = "1.583.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15584,7 +15584,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.583.0" +version = "1.583.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15593,7 +15593,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "lazy_static", @@ -15605,7 +15605,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "serde_json", @@ -15617,7 +15617,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "gosyn", @@ -15629,7 +15629,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "lazy_static", @@ -15641,7 +15641,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "serde_json", @@ -15653,7 +15653,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "nu-parser", @@ -15664,7 +15664,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15675,7 +15675,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15687,7 +15687,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "async-recursion", @@ -15710,7 +15710,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "lazy_static", @@ -15724,7 +15724,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15741,7 +15741,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "lazy_static", @@ -15755,7 +15755,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "lazy_static", @@ -15773,7 +15773,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "serde", @@ -15784,7 +15784,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "async-recursion", @@ -15821,7 +15821,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.583.0" +version = "1.583.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15831,7 +15831,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.583.0" +version = "1.583.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b949addccb..53dc39da96 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.583.0" +version = "1.583.1" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.583.0" +version = "1.583.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 3ba8aa1b96..873a0984da 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -a01942048c2464317fc0484c0b2414e14aa5f2a4 \ No newline at end of file +a01942048c2464317fc0484c0b2414e14aa5f2a4 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index dac0767c1d..f6aa53283b 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.583.0 + version: 1.583.1 title: Windmill API contact: @@ -5180,6 +5180,14 @@ paths: Accepts multiple values as a comma-separated list. schema: type: string + - name: without_description + in: query + description: | + (default false) + If true, the description field will be omitted from the response. + schema: + type: boolean + responses: "200": description: All scripts @@ -6463,6 +6471,13 @@ paths: in: query schema: type: boolean + - name: without_description + in: query + description: | + (default false) + If true, the description field will be omitted from the response. + schema: + type: boolean responses: "200": description: All flow @@ -12061,32 +12076,32 @@ paths: 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 + 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: - application/json: + text/plain: schema: - type: object - properties: - enabled: - type: boolean - required: - - enabled - responses: - "200": - description: email trigger enable/disable - content: - text/plain: - schema: - type: string + type: string /groups/list: get: diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index 5f1fe2361c..83ff9cf46d 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -133,7 +133,11 @@ async fn list_flows( "o.workspace_id", "o.path", "summary", - "description", + if !lq.without_description.unwrap_or(false) { + "description" + } else { + "NULL as description" + }, "fv.created_by as edited_by", "fv.created_at as edited_at", "archived", diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index b45f53c9ec..df17a765ab 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -228,6 +228,11 @@ async fn list_scripts( "COALESCE(draft.created_at, o.created_at) as created_at", "archived", "extra_perms", + if !lq.without_description.unwrap_or(false) { + "description" + } else { + "NULL as description" + }, "CASE WHEN lock_error_logs IS NOT NULL THEN true ELSE false END as has_deploy_errors", "language", "favorite.path IS NOT NULL as starred", diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 27044c1dd3..41e02b7ee8 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -78,7 +78,8 @@ pub struct ListableFlow { pub workspace_id: String, pub path: String, pub summary: String, - pub description: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, pub edited_by: Option, pub edited_at: Option>, pub archived: bool, @@ -1122,6 +1123,7 @@ where #[derive(Deserialize)] pub struct ListFlowQuery { + pub without_description: Option, pub path_start: Option, pub path_exact: Option, pub edited_by: Option, diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 11d6758caa..a5ba95f93b 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -353,6 +353,8 @@ pub struct ListableScript { pub starred: bool, pub tag: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub has_draft: Option, #[serde(skip_serializing_if = "Option::is_none")] pub draft_only: Option, @@ -492,6 +494,7 @@ where #[derive(Debug, Deserialize)] pub struct ListScriptQuery { + pub without_description: Option, pub path_start: Option, pub path_exact: Option, pub created_by: Option, diff --git a/backend/windmill-worker/src/duckdb_executor.rs b/backend/windmill-worker/src/duckdb_executor.rs index 8acdbb8b07..f8c7febad4 100644 --- a/backend/windmill-worker/src/duckdb_executor.rs +++ b/backend/windmill-worker/src/duckdb_executor.rs @@ -598,7 +598,10 @@ async fn transform_s3_uris(query: &str) -> Result { // BigQuery extension requires a json file as credentials // The file path is set as an env var by do_duckdb // It is created by transform_attach_db_resource_query (when bigquery is detected) -// and deleted by do_duckdb after the query is executed +// and deleted by do_duckdb after the query is executed. +// +// This relies on the fact that DuckDB does not run in native worker, so +// a worker will only run a single job at a time. pub struct UseBigQueryCredentialsFile { path: String, } @@ -660,6 +663,8 @@ fn remove_comments(stmt: &str) -> &str { #[cfg(test)] mod tests { use super::*; + + // Tests for remove_comments function #[test] fn test_remove_comments_single_line() { let sql = "-- This is a comment\nSELECT * FROM table;"; @@ -690,4 +695,377 @@ mod tests { let sql = " -- Comment\n -- Comment2\n -- Comment3\n SELECT\n\n * FROM\n table\n;\n\n -- end comment "; assert_eq!(remove_comments(sql), "SELECT\n\n * FROM\n table\n;"); } + + // Tests for parse_attach_db_resource function + #[test] + fn test_parse_attach_db_resource_postgres_res_prefix() { + let query = "ATTACH '$res:u/user/my_postgres' AS mydb (TYPE POSTGRES)"; + let result = parse_attach_db_resource(query); + assert!(result.is_some()); + let parsed = result.unwrap(); + assert_eq!(parsed.resource_path, "u/user/my_postgres"); + assert_eq!(parsed.name, "mydb"); + assert_eq!(parsed.db_type, "POSTGRES"); + assert!(parsed.extra_args.is_none() || parsed.extra_args.unwrap().is_empty()); + } + + #[test] + fn test_parse_attach_db_resource_res_protocol() { + let query = "ATTACH 'res://f/folder/database' AS db (TYPE postgresql)"; + let result = parse_attach_db_resource(query); + assert!(result.is_some()); + let parsed = result.unwrap(); + assert_eq!(parsed.resource_path, "f/folder/database"); + assert_eq!(parsed.name, "db"); + assert_eq!(parsed.db_type, "postgresql"); + } + + #[test] + fn test_parse_attach_db_resource_mysql() { + let query = "ATTACH '$res:u/admin/mysql_prod' AS mysql_db (TYPE mysql)"; + let result = parse_attach_db_resource(query); + assert!(result.is_some()); + let parsed = result.unwrap(); + assert_eq!(parsed.resource_path, "u/admin/mysql_prod"); + assert_eq!(parsed.name, "mysql_db"); + assert_eq!(parsed.db_type, "mysql"); + } + + #[test] + fn test_parse_attach_db_resource_bigquery() { + let query = "ATTACH '$res:u/user/bq_resource' AS bq (TYPE bigquery)"; + let result = parse_attach_db_resource(query); + assert!(result.is_some()); + let parsed = result.unwrap(); + assert_eq!(parsed.resource_path, "u/user/bq_resource"); + assert_eq!(parsed.name, "bq"); + assert_eq!(parsed.db_type, "bigquery"); + } + + #[test] + fn test_parse_attach_db_resource_with_extra_args() { + let query = "ATTACH '$res:u/user/db' AS mydb (TYPE POSTGRES, READ_ONLY)"; + let result = parse_attach_db_resource(query); + assert!(result.is_some()); + let parsed = result.unwrap(); + assert_eq!(parsed.resource_path, "u/user/db"); + assert_eq!(parsed.name, "mydb"); + assert_eq!(parsed.db_type, "POSTGRES"); + assert_eq!(parsed.extra_args.unwrap(), ", READ_ONLY"); + } + + #[test] + fn test_parse_attach_db_resource_case_insensitive() { + let query = "attach '$res:u/user/db' as mydb (type postgres)"; + let result = parse_attach_db_resource(query); + assert!(result.is_some()); + let parsed = result.unwrap(); + assert_eq!(parsed.resource_path, "u/user/db"); + assert_eq!(parsed.name, "mydb"); + assert_eq!(parsed.db_type, "postgres"); + } + + #[test] + fn test_parse_attach_db_resource_no_match() { + let query = "SELECT * FROM table"; + let result = parse_attach_db_resource(query); + assert!(result.is_none()); + } + + #[test] + fn test_parse_attach_db_resource_regular_attach() { + // Regular ATTACH without $res: or res:// should not match + let query = "ATTACH 'mydb.duckdb' AS mydb (TYPE duckdb)"; + let result = parse_attach_db_resource(query); + assert!(result.is_none()); + } + + // Tests for format_attach_db_conn_str function + #[test] + fn test_format_attach_db_conn_str_postgres_full() { + let db_resource = json!({ + "host": "localhost", + "port": 5432, + "user": "admin", + "password": "secret123", + "dbname": "mydb", + "sslmode": "require" + }); + let result = format_attach_db_conn_str(db_resource, "postgres").unwrap(); + assert!(result.contains("dbname=mydb")); + assert!(result.contains("user=admin")); + assert!(result.contains("host=localhost")); + assert!(result.contains("password=secret123")); + assert!(result.contains("port=5432")); + assert!(result.contains("sslmode=require")); + } + + #[test] + fn test_format_attach_db_conn_str_postgres_minimal() { + let db_resource = json!({ + "host": "db.example.com", + "dbname": "production" + }); + let result = format_attach_db_conn_str(db_resource, "postgres").unwrap(); + assert!(result.contains("dbname=production")); + assert!(result.contains("host=db.example.com")); + // Optional fields should result in empty strings + assert!(!result.contains("user=")); + assert!(!result.contains("password=")); + } + + #[test] + fn test_format_attach_db_conn_str_postgresql_alias() { + let db_resource = json!({ + "host": "localhost", + "dbname": "test" + }); + let result = format_attach_db_conn_str(db_resource, "postgresql").unwrap(); + assert!(result.contains("dbname=test")); + assert!(result.contains("host=localhost")); + } + + #[test] + fn test_format_attach_db_conn_str_bigquery() { + let db_resource = json!({ + "project_id": "my-gcp-project" + }); + let result = format_attach_db_conn_str(db_resource, "bigquery").unwrap(); + assert_eq!(result, "project=my-gcp-project"); + } + + #[test] + fn test_format_attach_db_conn_str_bigquery_missing_project_id() { + let db_resource = json!({ + "other_field": "value" + }); + let result = format_attach_db_conn_str(db_resource, "bigquery"); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("project_id")); + } + + #[test] + fn test_format_attach_db_conn_str_unsupported_type() { + let db_resource = json!({}); + let result = format_attach_db_conn_str(db_resource, "oracle"); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Unsupported db type")); + } + + #[test] + fn test_format_attach_db_conn_str_case_insensitive() { + let db_resource = json!({ + "host": "localhost", + "dbname": "test" + }); + let result = format_attach_db_conn_str(db_resource, "POSTGRES").unwrap(); + assert!(result.contains("dbname=test")); + } + + #[cfg(feature = "mysql")] + #[test] + fn test_format_attach_db_conn_str_mysql_full() { + let db_resource = json!({ + "host": "mysql.example.com", + "port": 3306, + "user": "root", + "password": "mysecret", + "database": "app_db", + "ssl": true + }); + let result = format_attach_db_conn_str(db_resource, "mysql").unwrap(); + assert!(result.contains("database=app_db")); + assert!(result.contains("host=mysql.example.com")); + assert!(result.contains("ssl_mode=required")); + assert!(result.contains("password=mysecret")); + assert!(result.contains("port=3306")); + assert!(result.contains("user=root")); + } + + #[cfg(feature = "mysql")] + #[test] + fn test_format_attach_db_conn_str_mysql_ssl_disabled() { + let db_resource = json!({ + "host": "localhost", + "database": "test", + "ssl": false + }); + let result = format_attach_db_conn_str(db_resource, "mysql").unwrap(); + assert!(result.contains("ssl_mode=disabled")); + } + + // Tests for get_attach_db_install_str function + #[test] + fn test_get_attach_db_install_str_postgres() { + let result = get_attach_db_install_str("postgres").unwrap(); + assert_eq!(result, "INSTALL postgres;"); + } + + #[test] + fn test_get_attach_db_install_str_bigquery() { + let result = get_attach_db_install_str("bigquery").unwrap(); + assert_eq!(result, "INSTALL bigquery FROM community;"); + } + + #[test] + fn test_get_attach_db_install_str_unsupported() { + let result = get_attach_db_install_str("sqlite"); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Unsupported db type")); + } + + #[test] + fn test_get_attach_db_install_str_case_insensitive() { + let result = get_attach_db_install_str("POSTGRES").unwrap(); + assert_eq!(result, "INSTALL postgres;"); + } + + #[cfg(feature = "mysql")] + #[test] + fn test_get_attach_db_install_str_mysql() { + let result = get_attach_db_install_str("mysql").unwrap(); + assert_eq!(result, "INSTALL mysql;"); + } + + #[cfg(not(feature = "mysql"))] + #[test] + fn test_get_attach_db_install_str_mysql_disabled() { + let result = get_attach_db_install_str("mysql"); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("MySQL feature is not enabled")); + } + + // Tests for transform_s3_uris function + #[tokio::test] + async fn test_transform_s3_uris_empty_storage() { + let query = "SELECT * FROM read_parquet('s3:///path/to/file.parquet')"; + let result = transform_s3_uris(query).await.unwrap(); + assert_eq!( + result, + "SELECT * FROM read_parquet('s3://_default_/path/to/file.parquet')" + ); + } + + #[tokio::test] + async fn test_transform_s3_uris_with_storage() { + // URIs with explicit storage should not be transformed + let query = "SELECT * FROM read_parquet('s3://mybucket/path/to/file.parquet')"; + let result = transform_s3_uris(query).await.unwrap(); + assert_eq!(result, query); + } + + #[tokio::test] + async fn test_transform_s3_uris_multiple_empty() { + let query = "SELECT * FROM read_parquet('s3:///file1.parquet') UNION SELECT * FROM read_parquet('s3:///file2.parquet')"; + let result = transform_s3_uris(query).await.unwrap(); + assert!(result.contains("s3://_default_/file1.parquet")); + assert!(result.contains("s3://_default_/file2.parquet")); + } + + #[tokio::test] + async fn test_transform_s3_uris_no_s3() { + let query = "SELECT * FROM my_table"; + let result = transform_s3_uris(query).await.unwrap(); + assert_eq!(result, query); + } + + #[tokio::test] + async fn test_transform_s3_uris_mixed() { + let query = "SELECT * FROM read_parquet('s3:///default.parquet'), read_csv('s3://explicit/file.csv')"; + let result = transform_s3_uris(query).await.unwrap(); + assert!(result.contains("s3://_default_/default.parquet")); + assert!(result.contains("s3://explicit/file.csv")); + } + + #[tokio::test] + async fn test_transform_s3_uris_nested_path() { + let query = "SELECT * FROM read_parquet('s3:///deep/nested/path/file.parquet')"; + let result = transform_s3_uris(query).await.unwrap(); + assert_eq!( + result, + "SELECT * FROM read_parquet('s3://_default_/deep/nested/path/file.parquet')" + ); + } + + // Tests for Arg struct + #[test] + fn test_arg_serialization() { + let arg = Arg { + name: "test_arg".to_string(), + arg_type: "string".to_string(), + json_value: json!("hello"), + }; + let serialized = serde_json::to_string(&arg).unwrap(); + assert!(serialized.contains("\"name\":\"test_arg\"")); + assert!(serialized.contains("\"arg_type\":\"string\"")); + assert!(serialized.contains("\"json_value\":\"hello\"")); + } + + #[test] + fn test_arg_serialization_number() { + let arg = Arg { + name: "count".to_string(), + arg_type: "integer".to_string(), + json_value: json!(42), + }; + let serialized = serde_json::to_string(&arg).unwrap(); + assert!(serialized.contains("\"json_value\":42")); + } + + #[test] + fn test_arg_serialization_null() { + let arg = Arg { + name: "optional".to_string(), + arg_type: "text".to_string(), + json_value: json!(null), + }; + let serialized = serde_json::to_string(&arg).unwrap(); + assert!(serialized.contains("\"json_value\":null")); + } + + #[test] + fn test_arg_serialization_array() { + let arg = Arg { + name: "items".to_string(), + arg_type: "array".to_string(), + json_value: json!([1, 2, 3]), + }; + let serialized = serde_json::to_string(&arg).unwrap(); + assert!(serialized.contains("\"json_value\":[1,2,3]")); + } + + #[test] + fn test_arg_serialization_object() { + let arg = Arg { + name: "config".to_string(), + arg_type: "object".to_string(), + json_value: json!({"key": "value"}), + }; + let serialized = serde_json::to_string(&arg).unwrap(); + assert!(serialized.contains("\"json_value\":{\"key\":\"value\"}")); + } + + #[test] + fn test_remove_comments_comment_in_string() { + let sql = "SELECT '-- not a comment' FROM table;"; + let result = remove_comments(sql); + assert_eq!(result, "SELECT '-- not a comment' FROM table;"); + } + + #[test] + fn test_remove_comments_multiple_dashes() { + let sql = "SELECT 5 - - 3;"; + let result = remove_comments(sql); + // This correctly handles the subtraction of negative number + assert_eq!(result, sql); + } } diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index a99958080c..3b7305e758 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.583.0"; +export const VERSION = "v1.583.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/commands/app/apps.ts b/cli/src/commands/app/apps.ts index d04c2edbdb..46dea1e349 100644 --- a/cli/src/commands/app/apps.ts +++ b/cli/src/commands/app/apps.ts @@ -45,7 +45,7 @@ export async function pushApp( } catch { //ignore } - app.value.policy = undefined; + app.policy = undefined; if (!localPath.endsWith(SEP)) { localPath += SEP; @@ -78,7 +78,7 @@ export async function pushApp( } replaceInlineScripts(localApp.value); - await generatingPolicy(localApp.value, remotePath); + await generatingPolicy(localApp, remotePath); if (app) { if (isSuperset(localApp, app)) { log.info(colors.green(`App ${remotePath} is up to date`)); @@ -110,7 +110,8 @@ export async function pushApp( async function generatingPolicy(app: any, path: string) { log.info(colors.gray(`Generating fresh policy for app ${path}...`)); try { - app.policy = await windmillUtils.updatePolicy(app, undefined); + app.policy = await windmillUtils.updatePolicy(app.value, undefined); + app.policy.execution_mode = "publisher"; } catch (e) { log.error(colors.red(`Error generating policy for app ${path}: ${e}`)); throw e; diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index e1ea241dad..b0e2b74682 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -662,7 +662,6 @@ export async function elementsToMap( const map: { [key: string]: string } = {}; const processedBasePaths = new Set(); - for await (const entry of readDirRecursiveWithIgnore(ignore, els)) { if (entry.isDirectory || entry.ignored) continue; const path = entry.path; @@ -854,25 +853,6 @@ async function compareDynFSElement( } } return o; - } else if (k.endsWith(".app.yaml")) { - let o: any; - try { - o = yamlParseContent(k, v); - } catch (error) { - log.error(`Failed to parse app YAML content at path: ${k}`); - throw error; - } - const o2 = o["policy"]; - - if (typeof o2 == "object") { - if (o2["on_behalf_of"] != undefined) { - delete o2["on_behalf_of"]; - } - if (o2["on_behalf_of_email"] != undefined) { - delete o2["on_behalf_of_email"]; - } - } - return o; } else { try { return yamlParseContent(k, v); diff --git a/cli/src/main.ts b/cli/src/main.ts index 73019749ad..fe41a1abed 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.583.0"; +export const VERSION = "1.583.1"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e78d52f945..78b86f5a33 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.583.0", + "version": "1.583.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.583.0", + "version": "1.583.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 6fdf1740c3..a157a4954c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.583.0", + "version": "1.583.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/ScriptPicker.svelte b/frontend/src/lib/components/ScriptPicker.svelte index a10f1c30fe..5fa0618edd 100644 --- a/frontend/src/lib/components/ScriptPicker.svelte +++ b/frontend/src/lib/components/ScriptPicker.svelte @@ -57,15 +57,19 @@ async function loadItems(): Promise { if (itemKind == 'flow') { - items = (await FlowService.listFlows({ workspace: $workspaceStore! })).map((flow) => ({ + items = ( + await FlowService.listFlows({ workspace: $workspaceStore!, withoutDescription: true }) + ).map((flow) => ({ value: flow.path, - label: `${flow.path}${flow.summary ? ` | ${truncate(flow.summary, 20)}` : ''}` + label: `${flow.path}${flow.summary ? ` | ${truncate(flow.summary, 20)}` : ''}`, + withoutDescription: true })) } else if (itemKind == 'script') { items = ( await ScriptService.listScripts({ workspace: $workspaceStore!, - kinds: kinds.join(',') + kinds: kinds.join(','), + withoutDescription: true }) ).map((script) => ({ value: script.path, diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte index 99d892c535..d66675bbe8 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte @@ -196,7 +196,7 @@ id: string result_stream?: string }) { - setResult(nresult_stream, id) + setResult(nresult_stream, id, false) }, cancel({ id }: { id: string }) { onCancel?.() @@ -630,7 +630,7 @@ result = res } - async function setResult(res: any, jobId: string | undefined) { + async function setResult(res: any, jobId: string | undefined, dispatchSuccess: boolean = true) { dispatch('resultSet', res) const errors = getResultErrors(res) @@ -669,7 +669,9 @@ recordJob(jobId, result, undefined, transformerResult) delete $errorByComponent[id] - dispatch('success', result) + if (dispatchSuccess) { + dispatch('success', result) + } // callbacks?.done(res) } diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceFlowList.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceFlowList.svelte index 1f6fdcf8b0..b2cc91fc01 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceFlowList.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceFlowList.svelte @@ -27,7 +27,8 @@ async function loadFlow(): Promise { const loadedFlows = await FlowService.listFlows({ workspace: $workspaceStore!, - perPage: 300 + perPage: 300, + withoutDescription: true }) flows = loadedFlows diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceScriptList.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceScriptList.svelte index c477c7ef1f..be261ca5e8 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceScriptList.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/WorkspaceScriptList.svelte @@ -27,7 +27,8 @@ async function loadScripts(): Promise { const loadedScripts = await ScriptService.listScripts({ workspace: $workspaceStore!, - perPage: 300 + perPage: 300, + withoutDescription: true }) scripts = loadedScripts diff --git a/frontend/src/lib/components/copilot/chat/flow/core.ts b/frontend/src/lib/components/copilot/chat/flow/core.ts index d9879f8399..8fd4233570 100644 --- a/frontend/src/lib/components/copilot/chat/flow/core.ts +++ b/frontend/src/lib/components/copilot/chat/flow/core.ts @@ -374,7 +374,7 @@ class WorkspaceScriptsSearch { private async init(workspace: string) { this.scripts = await ScriptService.listScripts({ - workspace + workspace, }) this.workspace = workspace } diff --git a/frontend/src/lib/components/flows/content/FlowInputsFlow.svelte b/frontend/src/lib/components/flows/content/FlowInputsFlow.svelte index faee8fb3d9..9f16d95bab 100644 --- a/frontend/src/lib/components/flows/content/FlowInputsFlow.svelte +++ b/frontend/src/lib/components/flows/content/FlowInputsFlow.svelte @@ -6,28 +6,38 @@ import { workspaceStore } from '$lib/stores' import { emptyString } from '$lib/utils' - import { createEventDispatcher } from 'svelte' + import { createEventDispatcher, untrack } from 'svelte' import { flip } from 'svelte/animate' import { fade } from 'svelte/transition' + interface Props { + children?: import('svelte').Snippet + } + + let { children }: Props = $props() // export let failureModule: boolean const dispatch = createEventDispatcher() - let items: Flow[] | undefined = undefined - let filteredItems: (Flow & { marked?: string })[] | undefined = undefined - let filter = '' - $: $workspaceStore && loadFlows() + let items = $state(undefined) as Flow[] | undefined + let filteredItems = $state(undefined) as (Flow & { marked?: string })[] | undefined + let filter = $state('') - let ownerFilter: string | undefined = undefined - $: prefilteredItems = ownerFilter ? items?.filter((x) => x.path.startsWith(ownerFilter!)) : items - - $: owners = Array.from( - new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? []) - ).sort() + let ownerFilter: string | undefined = $state(undefined) async function loadFlows() { - items = await FlowService.listFlows({ workspace: $workspaceStore! }) + items = await FlowService.listFlows({ workspace: $workspaceStore!, withoutDescription: true }) } + $effect(() => { + $workspaceStore && untrack(() => loadFlows()) + }) + let prefilteredItems = $derived( + ownerFilter ? items?.filter((x) => x.path.startsWith(ownerFilter!)) : items + ) + let owners = $derived( + Array.from( + new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? []) + ).sort() + )

Pick a Workspace Flow

- + {@render children?.()} - - {/each} - - {:else} - {#each Array(10).fill(0) as _} - - {/each} - {/if} -
diff --git a/frontend/src/lib/components/flows/pickers/WorkspaceScriptPicker.svelte b/frontend/src/lib/components/flows/pickers/WorkspaceScriptPicker.svelte index def0c1e58e..ce1a7e63e0 100644 --- a/frontend/src/lib/components/flows/pickers/WorkspaceScriptPicker.svelte +++ b/frontend/src/lib/components/flows/pickers/WorkspaceScriptPicker.svelte @@ -1,6 +1,6 @@
- + {@render children?.()}