Merge remote-tracking branch 'origin/main' into dieri/triggers-queue-mode

This commit is contained in:
hugocasa
2025-11-24 09:55:52 +01:00
40 changed files with 602 additions and 230 deletions
+1 -1
View File
@@ -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
+7
View File
@@ -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)
@@ -46,11 +46,11 @@
]
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true
]
@@ -59,7 +59,9 @@
"failure",
"command",
"approval",
"preprocessor"
"preprocessor",
"schedule_handler_old",
"dynamic_skip"
]
}
}
+27 -27
View File
@@ -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",
+2 -2
View File
@@ -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 <ruben@windmill.dev>"]
edition = "2021"
+1 -1
View File
@@ -1 +1 @@
a01942048c2464317fc0484c0b2414e14aa5f2a4
a01942048c2464317fc0484c0b2414e14aa5f2a4
+40 -25
View File
@@ -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:
+5 -1
View File
@@ -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",
+5
View File
@@ -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",
+3 -1
View File
@@ -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<String>,
pub edited_by: Option<String>,
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
pub archived: bool,
@@ -1122,6 +1123,7 @@ where
#[derive(Deserialize)]
pub struct ListFlowQuery {
pub without_description: Option<bool>,
pub path_start: Option<String>,
pub path_exact: Option<String>,
pub edited_by: Option<String>,
+3
View File
@@ -353,6 +353,8 @@ pub struct ListableScript {
pub starred: bool,
pub tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_draft: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub draft_only: Option<bool>,
@@ -492,6 +494,7 @@ where
#[derive(Debug, Deserialize)]
pub struct ListScriptQuery {
pub without_description: Option<bool>,
pub path_start: Option<String>,
pub path_exact: Option<String>,
pub created_by: Option<String>,
+379 -1
View File
@@ -598,7 +598,10 @@ async fn transform_s3_uris(query: &str) -> Result<String> {
// 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);
}
}
+1 -1
View File
@@ -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<string> {
return await windmill.UserService.login({
+4 -3
View File
@@ -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;
-20
View File
@@ -662,7 +662,6 @@ export async function elementsToMap(
const map: { [key: string]: string } = {};
const processedBasePaths = new Set<string>();
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);
+1 -1
View File
@@ -68,7 +68,7 @@ export {
// }
// });
export const VERSION = "1.583.0";
export const VERSION = "1.583.1";
export const WM_FORK_PREFIX = "wm-fork";
+2 -2
View File
@@ -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": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.583.0",
"version": "1.583.1",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -57,15 +57,19 @@
async function loadItems(): Promise<void> {
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,
@@ -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)
}
@@ -27,7 +27,8 @@
async function loadFlow(): Promise<void> {
const loadedFlows = await FlowService.listFlows({
workspace: $workspaceStore!,
perPage: 300
perPage: 300,
withoutDescription: true
})
flows = loadedFlows
@@ -27,7 +27,8 @@
async function loadScripts(): Promise<void> {
const loadedScripts = await ScriptService.listScripts({
workspace: $workspaceStore!,
perPage: 300
perPage: 300,
withoutDescription: true
})
scripts = loadedScripts
@@ -374,7 +374,7 @@ class WorkspaceScriptsSearch {
private async init(workspace: string) {
this.scripts = await ScriptService.listScripts({
workspace
workspace,
})
this.workspace = workspace
}
@@ -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()
)
</script>
<SearchItems
@@ -39,7 +49,7 @@
<div class="flex flex-col min-h-0 p-4">
<h3 class="mb-4">Pick a Workspace Flow</h3>
<div class="w-full flex mt-1 items-center gap-2 mb-3">
<slot />
{@render children?.()}
<input
type="text"
@@ -78,7 +88,7 @@
<li class="flex flex-row w-full">
<button
class="p-4 gap-1 flex flex-row grow hover:bg-surface-hover bg-surface transition-all text-primary"
on:click={() => {
onclick={() => {
dispatch('pick', { path })
}}
>
@@ -1,73 +0,0 @@
<script lang="ts">
import { Skeleton } from '$lib/components/common'
import SearchItems from '$lib/components/SearchItems.svelte'
import { FlowService, type Flow } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptyString } from '$lib/utils'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import { createEventDispatcher } from 'svelte'
// export let failureModule: boolean
const dispatch = createEventDispatcher()
let items: Flow[] | undefined = undefined
let filteredItems: (Flow & { marked?: string })[] | undefined = undefined
export let filter = ''
$: $workspaceStore && loadFlows()
let ownerFilter: string | undefined = undefined
$: prefilteredItems = ownerFilter ? items?.filter((x) => x.path.startsWith(ownerFilter!)) : items
export let owners: string[] = []
$: owners = Array.from(
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
).sort()
async function loadFlows() {
items = await FlowService.listFlows({ workspace: $workspaceStore! })
}
</script>
<SearchItems
{filter}
items={prefilteredItems}
bind:filteredItems
f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')}
/>
<div class="flex flex-col min-h-0">
{#if filteredItems}
{#if filter.length > 0 && filteredItems.length == 0}
<div class="text-2xs text-tercary font-extralight text-center py-2 px-3 items-center">
No items found.
</div>
{/if}
<ul class="overflow-auto">
{#each filteredItems as { path, summary, marked }}
<li class="flex flex-row w-full">
<button
class="px-3 py-2 gap-2 flex flex-row w-full hover:bg-surface-hover bg-surface transition-all items-center rounded-md text-left text-2xs text-primary font-normal"
on:click={async () => {
dispatch('pickFlow', {
path,
summary
})
}}
>
<BarsStaggered size={14} />
<span class="grow truncate">
{#if marked}
{@html marked}
{:else}
{!summary || summary.length == 0 ? path : summary}
{/if}
</span>
</button>
</li>
{/each}
</ul>
{:else}
{#each Array(10).fill(0) as _}
<Skeleton layout={[0.5, [1.5]]} />
{/each}
{/if}
</div>
@@ -1,6 +1,6 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import { createEventDispatcher, untrack } from 'svelte'
import { ScriptService } from '$lib/gen'
import SearchItems from '$lib/components/SearchItems.svelte'
import { Badge, Skeleton } from '$lib/components/common'
@@ -11,10 +11,6 @@
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
export let kind: 'script' | 'trigger' | 'approval' | 'failure' = 'script'
export let isTemplate: boolean | undefined = undefined
export let displayLock = false
type Item = {
path: string
summary?: string
@@ -22,33 +18,54 @@
hash?: string
}
let items: Item[] | undefined = undefined
let items = $state(undefined) as Item[] | undefined
let filteredItems: (Item & { marked?: string })[] | undefined = undefined
export let filter = ''
let filteredItems = $state(undefined) as (Item & { marked?: string })[] | undefined
interface Props {
kind?: 'script' | 'trigger' | 'approval' | 'failure'
isTemplate?: boolean | undefined
displayLock?: boolean
filter?: string
children?: import('svelte').Snippet
}
$: $workspaceStore && kind && loadItems()
let {
kind = 'script',
isTemplate = undefined,
displayLock = false,
filter = $bindable(''),
children
}: Props = $props()
async function loadItems(): Promise<void> {
items = await ScriptService.listScripts({
workspace: $workspaceStore!,
kinds: kind,
isTemplate
isTemplate,
withoutDescription: true
})
}
let ownerFilter: string | undefined = undefined
$: if ($workspaceStore) {
ownerFilter = 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)
const dispatch = createEventDispatcher()
let lockHash = false
let lockHash = $state(false)
$effect(() => {
$workspaceStore && kind && untrack(() => loadItems())
})
$effect(() => {
if ($workspaceStore) {
ownerFilter = undefined
}
})
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()
)
</script>
<SearchItems
@@ -59,7 +76,7 @@
/>
<div class="flex flex-col min-h-0">
<div class="w-full flex items-center gap-2 mb-3">
<slot />
{@render children?.()}
<TextInput
inputProps={{
@@ -109,7 +126,7 @@
<li class="flex flex-row w-full">
<button
class="p-4 gap-1 flex flex-row grow hover:bg-surface-hover bg-surface transition-all text-primary"
on:click={() => {
onclick={() => {
dispatch('pick', { path, hash: lockHash ? hash : undefined })
}}
>
@@ -13,8 +13,13 @@
}) =>
workspace && get(userStore)
? kind == 'flow'
? FlowService.listFlows({ workspace })
: ScriptService.listScripts({ workspace, kinds: kind, isTemplate })
? FlowService.listFlows({ workspace, withoutDescription: true })
: ScriptService.listScripts({
workspace,
kinds: kind,
isTemplate,
withoutDescription: true
})
: undefined,
initialWorkspace
? {
@@ -85,7 +85,8 @@
workspace: $workspaceStore!,
showArchived: archived ? true : undefined,
includeWithoutMain: includeWithoutMain ? true : undefined,
includeDraftOnly: true
includeDraftOnly: true,
withoutDescription: true
})
scripts = loadedScripts.map((script: Script) => {
@@ -102,7 +103,8 @@
await FlowService.listFlows({
workspace: $workspaceStore!,
showArchived: archived ? true : undefined,
includeDraftOnly: true
includeDraftOnly: true,
withoutDescription: true
})
).map((x: Flow) => {
return {
@@ -484,10 +484,12 @@
async function fetchCombinedItems() {
const scripts = await ScriptService.listScripts({
workspace: $workspaceStore!
workspace: $workspaceStore!,
withoutDescription: true
})
const flows = await FlowService.listFlows({
workspace: $workspaceStore!
workspace: $workspaceStore!,
withoutDescription: true
})
const apps = await AppService.listApps({ workspace: $workspaceStore! })
const raw_apps = await RawAppService.listRawApps({ workspace: $workspaceStore! })
@@ -202,7 +202,8 @@
const scripts = await ScriptService.listScripts({
starredOnly: favoriteOnly,
workspace,
pathStart
pathStart,
withoutDescription: true
})
return scripts.map((x) => x.path)
}
@@ -219,7 +220,8 @@
const flows = await FlowService.listFlows({
starredOnly: favoriteOnly,
workspace,
pathStart
pathStart,
withoutDescription: true
})
return flows.map((x) => x.path)
}
@@ -155,11 +155,13 @@
const scripts = await ScriptService.listScripts({
workspace: $workspaceStore ?? '',
starredOnly: true,
includeWithoutMain: true
includeWithoutMain: true,
withoutDescription: true
})
const flows = await FlowService.listFlows({
workspace: $workspaceStore ?? '',
starredOnly: true
starredOnly: true,
withoutDescription: true
})
const apps = await AppService.listApps({
workspace: $workspaceStore ?? '',
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.583.0"
wmill_pg = ">=1.583.0"
wmill = ">=1.583.1"
wmill_pg = ">=1.583.1"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.583.0
version: 1.583.1
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.583.0'
ModuleVersion = '1.583.1'
# Supported PSEditions
# CompatiblePSEditions = @()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.583.0"
version = "1.583.1"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.583.0"
version = "1.583.1"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.583.0",
"version": "1.583.1",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.583.0",
"version": "1.583.1",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.583.0
1.583.1