diff --git a/CHANGELOG.md b/CHANGELOG.md index a000ca6d44..6d2b05e2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.458.4](https://github.com/windmill-labs/windmill/compare/v1.458.3...v1.458.4) (2025-02-10) + + +### Bug Fixes + +* fix concurrent limit jobs non restarting ([4828a77](https://github.com/windmill-labs/windmill/commit/4828a77f21fe62f36632490f811fb01b39977662)) + ## [1.458.3](https://github.com/windmill-labs/windmill/compare/v1.458.2...v1.458.3) (2025-02-10) diff --git a/backend/.sqlx/query-3d0f036a3176dcc787bef3f10a336c93f43dec5df570f869580056702b7d1e09.json b/backend/.sqlx/query-3d0f036a3176dcc787bef3f10a336c93f43dec5df570f869580056702b7d1e09.json new file mode 100644 index 0000000000..4356a016c0 --- /dev/null +++ b/backend/.sqlx/query-3d0f036a3176dcc787bef3f10a336c93f43dec5df570f869580056702b7d1e09.json @@ -0,0 +1,118 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger\n WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "url", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 11, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "filters: _", + "type_info": "JsonbArray" + }, + { + "ordinal": 14, + "name": "initial_messages: _", + "type_info": "JsonbArray" + }, + { + "ordinal": 15, + "name": "url_runnable_args: _", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "can_return_message", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + false, + true, + false, + false, + true, + true, + false + ] + }, + "hash": "3d0f036a3176dcc787bef3f10a336c93f43dec5df570f869580056702b7d1e09" +} diff --git a/backend/.sqlx/query-4331bb1a3559f56c1ee91916b7f15251f0b32afa2445401170d931e0fe1febb8.json b/backend/.sqlx/query-4331bb1a3559f56c1ee91916b7f15251f0b32afa2445401170d931e0fe1febb8.json new file mode 100644 index 0000000000..d2648d4230 --- /dev/null +++ b/backend/.sqlx/query-4331bb1a3559f56c1ee91916b7f15251f0b32afa2445401170d931e0fe1febb8.json @@ -0,0 +1,119 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, edited_by, edited_at, email, extra_perms, is_async, requires_auth, http_method as \"http_method: _\", static_asset_config as \"static_asset_config: _\", is_static_website FROM http_trigger\n WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "route_path", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "route_path_key", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "is_async", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "requires_auth", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "http_method: _", + "type_info": { + "Custom": { + "name": "http_method", + "kind": { + "Enum": [ + "get", + "post", + "put", + "delete", + "patch" + ] + } + } + } + }, + { + "ordinal": 13, + "name": "static_asset_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 14, + "name": "is_static_website", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false + ] + }, + "hash": "4331bb1a3559f56c1ee91916b7f15251f0b32afa2445401170d931e0fe1febb8" +} diff --git a/backend/.sqlx/query-59368ac2d4e0918c7ee4275a0b1e3935e9d57be68a1811ffdee904a5f56e7023.json b/backend/.sqlx/query-59368ac2d4e0918c7ee4275a0b1e3935e9d57be68a1811ffdee904a5f56e7023.json new file mode 100644 index 0000000000..d1e0d20596 --- /dev/null +++ b/backend/.sqlx/query-59368ac2d4e0918c7ee4275a0b1e3935e9d57be68a1811ffdee904a5f56e7023.json @@ -0,0 +1,118 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM nats_trigger\n WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "nats_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "subjects", + "type_info": "VarcharArray" + }, + { + "ordinal": 3, + "name": "stream_name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "consumer_name", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "use_jetstream", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 12, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 13, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 15, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 16, + "name": "enabled", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + false + ] + }, + "hash": "59368ac2d4e0918c7ee4275a0b1e3935e9d57be68a1811ffdee904a5f56e7023" +} diff --git a/backend/.sqlx/query-51ddbfec67af268d9bbee12b2730d6109d2a6633e62ce708bad8af1a9f8c3925.json b/backend/.sqlx/query-7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3.json similarity index 80% rename from backend/.sqlx/query-51ddbfec67af268d9bbee12b2730d6109d2a6633e62ce708bad8af1a9f8c3925.json rename to backend/.sqlx/query-7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3.json index e664011467..edec8c5053 100644 --- a/backend/.sqlx/query-51ddbfec67af268d9bbee12b2730d6109d2a6633e62ce708bad8af1a9f8c3925.json +++ b/backend/.sqlx/query-7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH ping AS (UPDATE v2_job_runtime SET ping = NULL WHERE id = $2 RETURNING id)\n UPDATE v2_job_queue SET\n running = false,\n started_at = null,\n scheduled_for = $1\n WHERE id = (SELECT id FROM ping)", + "query": "WITH ping AS (UPDATE v2_job_runtime SET ping = NULL WHERE id = $2 RETURNING id)\n UPDATE v2_job_queue SET\n running = false,\n started_at = null,\n scheduled_for = $1\n WHERE id = $2", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "51ddbfec67af268d9bbee12b2730d6109d2a6633e62ce708bad8af1a9f8c3925" + "hash": "7145a1a6cf0ccfae5874c882e63ff0d370cf1ab4d41f68494a940951eaa52bc3" } diff --git a/backend/.sqlx/query-7f6649b177f4ec948e396e179ea044812f8103498080a24246063e06b0ebfccf.json b/backend/.sqlx/query-7f6649b177f4ec948e396e179ea044812f8103498080a24246063e06b0ebfccf.json new file mode 100644 index 0000000000..6c4a2a910f --- /dev/null +++ b/backend/.sqlx/query-7f6649b177f4ec948e396e179ea044812f8103498080a24246063e06b0ebfccf.json @@ -0,0 +1,106 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM postgres_trigger\n WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 8, + "name": "postgres_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 12, + "name": "replication_slot_name", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "publication_name", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "enabled", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + true, + false, + true, + true, + true, + false, + false, + false + ] + }, + "hash": "7f6649b177f4ec948e396e179ea044812f8103498080a24246063e06b0ebfccf" +} diff --git a/backend/.sqlx/query-51648e377d47815d0b15694572d5c9cc0a303d70980346e1f3c4096a8922d7d5.json b/backend/.sqlx/query-91878f06c6e27d864bd50d8cd4adde4395ab6ef1181237a886fa398dcfa0b589.json similarity index 80% rename from backend/.sqlx/query-51648e377d47815d0b15694572d5c9cc0a303d70980346e1f3c4096a8922d7d5.json rename to backend/.sqlx/query-91878f06c6e27d864bd50d8cd4adde4395ab6ef1181237a886fa398dcfa0b589.json index 813981bae2..91492c037b 100644 --- a/backend/.sqlx/query-51648e377d47815d0b15694572d5c9cc0a303d70980346e1f3c4096a8922d7d5.json +++ b/backend/.sqlx/query-91878f06c6e27d864bd50d8cd4adde4395ab6ef1181237a886fa398dcfa0b589.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n -- slack_team_id, \n -- slack_name, \n -- slack_command_script, \n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\", \n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\", \n webhook, \n deploy_to, \n error_handler, \n ai_resource, \n ai_models,\n code_completion_model,\n error_handler_extra_args, \n error_handler_muted_on_cancel, \n large_file_storage, \n git_sync,\n default_app,\n default_scripts,\n workspace.name\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1", + "query": "SELECT\n -- slack_team_id, \n -- slack_name, \n -- slack_command_script, \n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\", \n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\", \n webhook, \n deploy_to, \n error_handler, \n ai_resource, \n ai_models,\n code_completion_model,\n error_handler_extra_args, \n error_handler_muted_on_cancel, \n large_file_storage, \n git_sync,\n default_app,\n default_scripts,\n workspace.name,\n mute_critical_alerts,\n color,\n operator_settings\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1", "describe": { "columns": [ { @@ -82,6 +82,21 @@ "ordinal": 15, "name": "name", "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "mute_critical_alerts", + "type_info": "Bool" + }, + { + "ordinal": 17, + "name": "color", + "type_info": "Varchar" + }, + { + "ordinal": 18, + "name": "operator_settings", + "type_info": "Jsonb" } ], "parameters": { @@ -105,8 +120,11 @@ true, true, true, - false + false, + true, + true, + true ] }, - "hash": "51648e377d47815d0b15694572d5c9cc0a303d70980346e1f3c4096a8922d7d5" + "hash": "91878f06c6e27d864bd50d8cd4adde4395ab6ef1181237a886fa398dcfa0b589" } diff --git a/backend/.sqlx/query-a79b1d0884c02f92fd40b23c6181717ffa5e01250020cfb24e4bad276397dee2.json b/backend/.sqlx/query-a79b1d0884c02f92fd40b23c6181717ffa5e01250020cfb24e4bad276397dee2.json new file mode 100644 index 0000000000..659dedc8cf --- /dev/null +++ b/backend/.sqlx/query-a79b1d0884c02f92fd40b23c6181717ffa5e01250020cfb24e4bad276397dee2.json @@ -0,0 +1,106 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM kafka_trigger\n WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "kafka_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "topics", + "type_info": "VarcharArray" + }, + { + "ordinal": 3, + "name": "group_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 11, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 13, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "enabled", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + false + ] + }, + "hash": "a79b1d0884c02f92fd40b23c6181717ffa5e01250020cfb24e4bad276397dee2" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9aaad31e90..335f1a7fa5 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -8548,9 +8548,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +checksum = "1d08feb8f695b465baed819b03c128dc23f57a694510ab1f06c77f763975685e" dependencies = [ "cc", "cfg-if", @@ -10858,7 +10858,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "axum", @@ -10901,7 +10901,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "argon2", @@ -10994,7 +10994,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.458.3" +version = "1.458.4" dependencies = [ "base64 0.22.1", "chrono", @@ -11012,7 +11012,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.458.3" +version = "1.458.4" dependencies = [ "chrono", "serde", @@ -11025,7 +11025,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "serde", @@ -11039,7 +11039,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "async-stream", @@ -11098,7 +11098,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.458.3" +version = "1.458.4" dependencies = [ "regex", "serde", @@ -11112,7 +11112,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "bytes", @@ -11135,7 +11135,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.458.3" +version = "1.458.4" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -11147,7 +11147,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.458.3" +version = "1.458.4" dependencies = [ "convert_case 0.6.0", "serde", @@ -11156,7 +11156,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "lazy_static", @@ -11168,7 +11168,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "serde_json", @@ -11180,7 +11180,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "gosyn", @@ -11192,7 +11192,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "lazy_static", @@ -11204,7 +11204,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "itertools 0.14.0", @@ -11215,7 +11215,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "itertools 0.14.0", @@ -11226,7 +11226,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "async-recursion", @@ -11246,7 +11246,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -11263,7 +11263,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "lazy_static", @@ -11275,7 +11275,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "lazy_static", @@ -11293,7 +11293,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -11315,7 +11315,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "serde_json", @@ -11325,7 +11325,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "async-recursion", @@ -11358,7 +11358,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.458.3" +version = "1.458.4" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -11368,7 +11368,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.458.3" +version = "1.458.4" dependencies = [ "anyhow", "async-recursion", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index acb347572b..fe9c780d14 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.458.3" +version = "1.458.4" authors.workspace = true edition.workspace = true @@ -30,7 +30,7 @@ members = [ ] [workspace.package] -version = "1.458.3" +version = "1.458.4" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 77cb564474..b3154d15ef 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -3dab441fe1eb5c9a042a6a4d9933ddea29c1bf4e \ No newline at end of file +8dab3198496461e40610145e4c818fce2345e20a \ No newline at end of file diff --git a/backend/parsers/windmill-parser-py-imports/src/lib.rs b/backend/parsers/windmill-parser-py-imports/src/lib.rs index 58f857ef08..99b3b37cf5 100644 --- a/backend/parsers/windmill-parser-py-imports/src/lib.rs +++ b/backend/parsers/windmill-parser-py-imports/src/lib.rs @@ -65,6 +65,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma "pythonjsonlogger" => "python-json-logger", "socks" => "PySocks", "taiga" => "python-taiga", + "docx" => "python-docx", }; fn replace_import(x: String) -> String { diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index aecefbff9f..a3f117721f 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -1713,7 +1713,7 @@ async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker worker_name, send_result_never_used, #[cfg(feature = "benchmark")] - &mut windmill_common::bench::BenchmarkIter::new(), + &mut windmill_worker::bench::BenchmarkIter::new(), ) .await; } diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 1574cf64ca..7573e139ee 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -4498,4 +4498,124 @@ mod job_payload { }; test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } + + #[sqlx::test(fixtures("base", "hello"))] + async fn test_raw_flow_payload_with_restarted_from(db: Pool) { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await; + let port = server.addr.port(); + + let db = &db; + let test = |restarted_from, arg, result| async move { + let job = RunJob::from(JobPayload::RawFlow { + value: serde_json::from_value(json!({ + "modules": [{ + "id": "a", + "value": { + "type": "rawscript", + "content": r#"export function main(world: string) { + return `Hello ${world}!`; + }"#, + "language": "deno", + "input_transforms": { + "world": { "type": "javascript", "expr": "flow_input.world" } + } + } + }, { + "id": "b", + "value": { + "type": "rawscript", + "content": r#"export function main(world: string, a: string) { + return `${a} ${world}!`; + }"#, + "language": "deno", + "input_transforms": { + "world": { "type": "javascript", "expr": "flow_input.world" }, + "a": { "type": "javascript", "expr": "results.a" } + } + } + }, { + "id": "c", + "value": { + "type": "forloopflow", + "iterator": { "type": "javascript", "expr": "['a', 'b', 'c']" }, + "modules": [{ + "value": { + "input_transforms": { + "world": { "type": "javascript", "expr": "flow_input.world" }, + "b": { "type": "javascript", "expr": "results.b" }, + "x": { "type": "javascript", "expr": "flow_input.iter.value" } + }, + "type": "rawscript", + "language": "deno", + "content": r#"export function main(world: string, b: string, x: string) { + return `${x}: ${b} ${world}!`; + }"#, + }, + }], + } + }], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { "world": { "type": "string" } }, + "type": "object", + "order": [ "world" ] + } + })) + .unwrap(), + path: None, + restarted_from, + }) + .arg("world", arg) + .run_until_complete(db, port) + .await; + + assert_eq!(job.json_result().unwrap(), result); + job.id + }; + let flow_job_id = test( + None, + json!("foo"), + json!([ + "a: Hello foo! foo! foo!", + "b: Hello foo! foo! foo!", + "c: Hello foo! foo! foo!" + ]), + ) + .await; + let flow_job_id = test( + Some(RestartedFrom { flow_job_id, step_id: "a".into(), branch_or_iteration_n: None }), + json!("foo"), + json!([ + "a: Hello foo! foo! foo!", + "b: Hello foo! foo! foo!", + "c: Hello foo! foo! foo!" + ]), + ) + .await; + let flow_job_id = test( + Some(RestartedFrom { flow_job_id, step_id: "b".into(), branch_or_iteration_n: None }), + json!("bar"), + json!([ + "a: Hello foo! bar! bar!", + "b: Hello foo! bar! bar!", + "c: Hello foo! bar! bar!" + ]), + ) + .await; + let _ = test( + Some(RestartedFrom { + flow_job_id, + step_id: "c".into(), + branch_or_iteration_n: Some(1), + }), + json!("yolo"), + json!([ + "a: Hello foo! bar! bar!", + "b: Hello foo! bar! yolo!", + "c: Hello foo! bar! yolo!" + ]), + ) + .await; + } } diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 50184b857d..9f2ece7d4b 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.458.3 + version: 1.458.4 title: Windmill API contact: diff --git a/backend/windmill-api/src/groups.rs b/backend/windmill-api/src/groups.rs index b63d0f9de8..e25d3ee7da 100644 --- a/backend/windmill-api/src/groups.rs +++ b/backend/windmill-api/src/groups.rs @@ -72,6 +72,7 @@ pub struct NewGroup { pub struct GroupInfo { pub workspace_id: String, pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] pub summary: Option, pub members: Vec, pub extra_perms: serde_json::Value, diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index 68e21d17ed..f09451c3af 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -114,22 +114,23 @@ struct NewTrigger { } #[derive(FromRow, Serialize)] -struct HttpTrigger { - workspace_id: String, - path: String, - route_path: String, - route_path_key: String, - script_path: String, - is_flow: bool, - edited_by: String, - email: String, - edited_at: chrono::DateTime, - extra_perms: serde_json::Value, - is_async: bool, - requires_auth: bool, - http_method: HttpMethod, - static_asset_config: Option>, - is_static_website: bool, +pub struct HttpTrigger { + pub workspace_id: String, + pub path: String, + pub route_path: String, + pub route_path_key: String, + pub script_path: String, + pub is_flow: bool, + pub edited_by: String, + pub email: String, + pub edited_at: chrono::DateTime, + pub extra_perms: serde_json::Value, + pub is_async: bool, + pub requires_auth: bool, + pub http_method: HttpMethod, + #[serde(skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option>, + pub is_static_website: bool, } #[derive(Deserialize)] diff --git a/backend/windmill-api/src/kafka_triggers_ee.rs b/backend/windmill-api/src/kafka_triggers_ee.rs index d02ea970e3..de50eea53b 100644 --- a/backend/windmill-api/src/kafka_triggers_ee.rs +++ b/backend/windmill-api/src/kafka_triggers_ee.rs @@ -18,3 +18,25 @@ pub fn start_kafka_consumers( #[derive(Serialize, Deserialize)] pub enum KafkaTriggerConfigConnection {} + +#[derive(Serialize, Clone)] +pub struct KafkaTrigger { + pub workspace_id: String, + pub path: String, + pub kafka_resource_path: String, + pub group_id: String, + pub topics: Vec, + pub script_path: String, + pub is_flow: bool, + pub edited_by: String, + pub email: String, + pub edited_at: chrono::DateTime, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub extra_perms: serde_json::Value, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + pub enabled: bool, +} diff --git a/backend/windmill-api/src/nats_triggers_ee.rs b/backend/windmill-api/src/nats_triggers_ee.rs index bfa5fbdd73..92894a2a34 100644 --- a/backend/windmill-api/src/nats_triggers_ee.rs +++ b/backend/windmill-api/src/nats_triggers_ee.rs @@ -15,3 +15,29 @@ pub fn start_nats_consumers(_db: DB, mut _killpill_rx: tokio::sync::broadcast::R #[derive(Serialize, Deserialize)] pub enum NatsTriggerConfigConnection {} + +#[derive(Serialize, Clone)] +pub struct NatsTrigger { + pub workspace_id: String, + pub path: String, + pub nats_resource_path: String, + pub subjects: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub stream_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub consumer_name: Option, + pub use_jetstream: bool, + pub script_path: String, + pub is_flow: bool, + pub edited_by: String, + pub email: String, + pub edited_at: chrono::DateTime, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub extra_perms: serde_json::Value, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + pub enabled: bool, +} diff --git a/backend/windmill-api/src/postgres_triggers/handler.rs b/backend/windmill-api/src/postgres_triggers/handler.rs index 760be062f7..dcf350bd91 100644 --- a/backend/windmill-api/src/postgres_triggers/handler.rs +++ b/backend/windmill-api/src/postgres_triggers/handler.rs @@ -221,12 +221,16 @@ pub struct PostgresTrigger { pub edited_by: String, pub email: String, pub edited_at: chrono::DateTime, + #[serde(skip_serializing_if = "Option::is_none")] pub extra_perms: Option, pub postgres_resource_path: String, + #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub server_id: Option, pub replication_slot_name: String, pub publication_name: String, + #[serde(skip_serializing_if = "Option::is_none")] pub last_server_ping: Option>, pub enabled: bool, } diff --git a/backend/windmill-api/src/postgres_triggers/mod.rs b/backend/windmill-api/src/postgres_triggers/mod.rs index 8adb635030..12d7b558e0 100644 --- a/backend/windmill-api/src/postgres_triggers/mod.rs +++ b/backend/windmill-api/src/postgres_triggers/mod.rs @@ -16,7 +16,7 @@ use handler::{ create_template_script, delete_postgres_trigger, delete_publication, drop_slot_name, exists_postgres_trigger, get_postgres_trigger, get_publication_info, get_template_script, is_database_in_logical_level, list_database_publication, list_postgres_triggers, - list_slot_name, set_enabled, update_postgres_trigger, Database, PostgresTrigger, + list_slot_name, set_enabled, update_postgres_trigger, Database, }; use windmill_common::{db::UserDB, error::Error, utils::StripPath}; use windmill_queue::PushArgsOwned; @@ -30,6 +30,7 @@ mod relation; mod replication_message; mod trigger; +pub use handler::PostgresTrigger; pub use trigger::start_database; pub async fn get_database_resource( diff --git a/backend/windmill-api/src/websocket_triggers.rs b/backend/windmill-api/src/websocket_triggers.rs index a28ed01425..4de01f14f3 100644 --- a/backend/windmill-api/src/websocket_triggers.rs +++ b/backend/windmill-api/src/websocket_triggers.rs @@ -88,23 +88,27 @@ enum InitialMessage { #[derive(FromRow, Serialize, Clone)] pub struct WebsocketTrigger { - workspace_id: String, - path: String, - url: String, - script_path: String, - is_flow: bool, - edited_by: String, - email: String, - edited_at: chrono::DateTime, - server_id: Option, - last_server_ping: Option>, - extra_perms: serde_json::Value, - error: Option, - enabled: bool, - filters: Vec>>, - initial_messages: Option>>>, - url_runnable_args: Option>>, - can_return_message: bool, + pub workspace_id: String, + pub path: String, + pub url: String, + pub script_path: String, + pub is_flow: bool, + pub edited_by: String, + pub email: String, + pub edited_at: chrono::DateTime, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + pub extra_perms: serde_json::Value, + pub error: Option, + pub enabled: bool, + pub filters: Vec>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_messages: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub url_runnable_args: Option>>, + pub can_return_message: bool, } #[derive(Deserialize)] diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index af074c2d81..f832b85f14 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -165,32 +165,54 @@ struct Workspace { #[derive(FromRow, Serialize, Debug)] pub struct WorkspaceSettings { pub workspace_id: String, + #[serde(skip_serializing_if = "Option::is_none")] pub slack_team_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub slack_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub slack_command_script: Option, pub slack_email: String, + #[serde(skip_serializing_if = "Option::is_none")] pub auto_invite_domain: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub auto_invite_operator: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub auto_add: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub customer_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub plan: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub webhook: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub deploy_to: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub ai_resource: Option, pub ai_models: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub code_completion_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub error_handler: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub error_handler_extra_args: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub error_handler_muted_on_cancel: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub large_file_storage: Option, // effectively: DatasetsStorage - pub git_sync: Option, // effectively: WorkspaceGitSyncSettings - pub deploy_ui: Option, // effectively: WorkspaceDeploymentUISettings + #[serde(skip_serializing_if = "Option::is_none")] + pub git_sync: Option, // effectively: WorkspaceGitSyncSettings + #[serde(skip_serializing_if = "Option::is_none")] + pub deploy_ui: Option, // effectively: WorkspaceDeploymentUISettings + #[serde(skip_serializing_if = "Option::is_none")] pub default_app: Option, pub automatic_billing: bool, + #[serde(skip_serializing_if = "Option::is_none")] pub default_scripts: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub mute_critical_alerts: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub color: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub operator_settings: Option, } diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 1f55d0172a..40a80025ea 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -148,6 +148,7 @@ pub(crate) struct ArchiveQueryParams { skip_variables: Option, skip_resources: Option, include_schedules: Option, + include_triggers: Option, include_users: Option, include_groups: Option, include_settings: Option, @@ -185,6 +186,8 @@ where "has_draft", "draft_only", "error", + "last_server_ping", + "server_id", ], ignore_keys.unwrap_or(vec![]), ] @@ -221,6 +224,7 @@ struct SimplifiedUser { #[derive(Serialize)] struct SimplifiedGroup { name: String, + #[serde(skip_serializing_if = "Option::is_none")] summary: Option, members: Vec, admins: Vec, @@ -235,20 +239,35 @@ struct SimplifiedSettings { auto_invite_enabled: bool, auto_invite_as: String, auto_invite_mode: String, + #[serde(skip_serializing_if = "Option::is_none")] webhook: Option, + #[serde(skip_serializing_if = "Option::is_none")] deploy_to: Option, + #[serde(skip_serializing_if = "Option::is_none")] error_handler: Option, + #[serde(skip_serializing_if = "Option::is_none")] error_handler_extra_args: Option, error_handler_muted_on_cancel: bool, + #[serde(skip_serializing_if = "Option::is_none")] ai_resource: Option, ai_models: Vec, #[serde(skip_serializing_if = "Option::is_none")] code_completion_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] large_file_storage: Option, + #[serde(skip_serializing_if = "Option::is_none")] git_sync: Option, + #[serde(skip_serializing_if = "Option::is_none")] default_app: Option, + #[serde(skip_serializing_if = "Option::is_none")] default_scripts: Option, name: String, + #[serde(skip_serializing_if = "Option::is_none")] + mute_critical_alerts: Option, + #[serde(skip_serializing_if = "Option::is_none")] + color: Option, + #[serde(skip_serializing_if = "Option::is_none")] + operator_settings: Option, } pub(crate) async fn tarball_workspace( @@ -264,6 +283,7 @@ pub(crate) async fn tarball_workspace( skip_secrets, skip_variables, include_schedules, + include_triggers, include_users, include_groups, include_settings, @@ -510,6 +530,113 @@ pub(crate) async fn tarball_workspace( } } + if include_triggers.unwrap_or(false) { + #[cfg(feature = "http_trigger")] + { + let http_triggers = sqlx::query_as!( + crate::http_triggers::HttpTrigger, + "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, edited_by, edited_at, email, extra_perms, is_async, requires_auth, http_method as \"http_method: _\", static_asset_config as \"static_asset_config: _\", is_static_website FROM http_trigger + WHERE workspace_id = $1", + &w_id + ) + .fetch_all(&mut *tx) + .await?; + + for trigger in http_triggers { + let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive(&trigger_str, &format!("{}.http_trigger.json", trigger.path)) + .await?; + } + } + + #[cfg(feature = "websocket")] + { + let websocket_triggers = sqlx::query_as!( + crate::websocket_triggers::WebsocketTrigger, + "SELECT workspace_id, path, url, script_path, is_flow, edited_by, email, edited_at, server_id, last_server_ping, extra_perms, error, enabled, filters as \"filters: _\", initial_messages as \"initial_messages: _\", url_runnable_args as \"url_runnable_args: _\", can_return_message FROM websocket_trigger + WHERE workspace_id = $1", + &w_id + ) + .fetch_all(&mut *tx) + .await?; + + for trigger in websocket_triggers { + let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive( + &trigger_str, + &format!("{}.websocket_trigger.json", trigger.path), + ) + .await?; + } + } + + #[cfg(all(feature = "enterprise", feature = "kafka"))] + { + let kafka_triggers = sqlx::query_as!( + crate::kafka_triggers_ee::KafkaTrigger, + "SELECT * FROM kafka_trigger + WHERE workspace_id = $1", + &w_id + ) + .fetch_all(&mut *tx) + .await?; + + for trigger in kafka_triggers { + let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive( + &trigger_str, + &format!("{}.kafka_trigger.json", trigger.path), + ) + .await?; + } + } + + #[cfg(all(feature = "enterprise", feature = "nats"))] + { + let nats_triggers = sqlx::query_as!( + crate::nats_triggers_ee::NatsTrigger, + "SELECT * FROM nats_trigger + WHERE workspace_id = $1", + &w_id + ) + .fetch_all(&mut *tx) + .await?; + + for trigger in nats_triggers { + let trigger_str: &String = + &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive(&trigger_str, &format!("{}.nats_trigger.json", trigger.path)) + .await?; + } + } + + #[cfg(feature = "postgres_trigger")] + { + let postgres_triggers = sqlx::query_as!( + crate::postgres_triggers::PostgresTrigger, + "SELECT * FROM postgres_trigger + WHERE workspace_id = $1", + &w_id + ) + .fetch_all(&mut *tx) + .await?; + + for trigger in postgres_triggers { + let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive( + &trigger_str, + &format!("{}.postgres_trigger.json", trigger.path), + ) + .await?; + } + } + } + if include_users.unwrap_or(false) { let users = sqlx::query!( "SELECT * FROM usr @@ -532,12 +659,7 @@ pub(crate) async fn tarball_workspace( disabled: user.disabled, email: user.email, }; - let user_str = &to_string_without_metadata( - &user, - false, - Some(vec!["is_admin", "operator", "email"]), - ) - .unwrap(); + let user_str = &to_string_without_metadata(&user, false, Some(vec!["email"])).unwrap(); archive .write_to_archive(&user_str, &format!("users/{}.user.json", user.email)) .await?; @@ -627,7 +749,10 @@ pub(crate) async fn tarball_workspace( git_sync, default_app, default_scripts, - workspace.name + workspace.name, + mute_critical_alerts, + color, + operator_settings FROM workspace_settings LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id WHERE workspace_id = $1"#, diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 4bf68f59f9..802af6196b 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -19,8 +19,6 @@ use sqlx::{Pool, Postgres}; pub mod apps; pub mod auth; -#[cfg(feature = "benchmark")] -pub mod bench; pub mod cache; pub mod db; pub mod ee; diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 1b9df8a673..f561549af8 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -2040,13 +2040,14 @@ pub async fn pull( running = false, started_at = null, scheduled_for = $1 - WHERE id = (SELECT id FROM ping)", + WHERE id = $2", estimated_next_schedule_timestamp, job_uuid, ) - .fetch_all(&mut *tx) + .execute(&mut *tx) .await .map_err(|e| Error::internal_err(format!("Could not update and re-queue job {job_uuid}. The job will be marked as running but it is not running: {e:#}")))?; + tx.commit().await? } } diff --git a/backend/windmill-common/src/bench.rs b/backend/windmill-worker/src/bench.rs similarity index 99% rename from backend/windmill-common/src/bench.rs rename to backend/windmill-worker/src/bench.rs index dafbfdb6e5..5006d5a34c 100644 --- a/backend/windmill-common/src/bench.rs +++ b/backend/windmill-worker/src/bench.rs @@ -1,9 +1,9 @@ -use crate::{ +use serde::Serialize; +use tokio::time::Instant; +use windmill_common::{ worker::{write_file, TMP_DIR}, DB, }; -use serde::Serialize; -use tokio::time::Instant; #[derive(Serialize)] pub struct BenchmarkInfo { @@ -79,7 +79,7 @@ impl BenchmarkIter { } pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) { - use crate::{jobs::JobKind, scripts::ScriptLang}; + use windmill_common::{jobs::JobKind, scripts::ScriptLang}; let benchmark_kind = std::env::var("BENCHMARK_KIND").unwrap_or("noop".to_string()); diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index cc34dd72ab..9b77ca840f 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -9,6 +9,8 @@ mod snowflake_executor; mod ansible_executor; mod bash_executor; +#[cfg(feature = "benchmark")] +pub mod bench; mod bun_executor; pub mod common; mod config; @@ -38,6 +40,7 @@ mod rust_executor; mod worker; mod worker_flow; mod worker_lockfiles; + pub use worker::*; pub use result_processor::handle_job_error; diff --git a/backend/windmill-worker/src/result_processor.rs b/backend/windmill-worker/src/result_processor.rs index dc99d1aae8..1b7ec63a1c 100644 --- a/backend/windmill-worker/src/result_processor.rs +++ b/backend/windmill-worker/src/result_processor.rs @@ -26,7 +26,7 @@ use windmill_common::{ }; #[cfg(feature = "benchmark")] -use windmill_common::bench::{BenchmarkInfo, BenchmarkIter}; +use crate::bench::{BenchmarkInfo, BenchmarkIter}; use windmill_queue::{append_logs, get_queued_job, CanceledBy, WrappedError}; diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 0f57dc2f10..90b0444901 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -146,7 +146,7 @@ use crate::mssql_executor::do_mssql; use crate::bigquery_executor::do_bigquery; #[cfg(feature = "benchmark")] -use windmill_common::bench::{benchmark_init, BenchmarkInfo, BenchmarkIter}; +use crate::bench::{benchmark_init, BenchmarkInfo, BenchmarkIter}; use windmill_common::add_time; diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index e25aa6b8ab..42dd66c217 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -11,6 +11,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; +#[cfg(feature = "benchmark")] +use crate::bench::BenchmarkIter; use crate::common::{cached_result_path, save_in_cache}; use crate::js_eval::{eval_timeout, IdContext}; use crate::{ @@ -30,8 +32,6 @@ use tracing::instrument; use uuid::Uuid; use windmill_common::add_time; use windmill_common::auth::JobPerms; -#[cfg(feature = "benchmark")] -use windmill_common::bench::BenchmarkIter; use windmill_common::cache::{self, RawData}; use windmill_common::db::Authed; use windmill_common::flow_status::{ diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 9b51beb235..7221d958bb 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.458.3"; +export const VERSION = "v1.458.4"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/conf.ts b/cli/conf.ts index 2bb3a69b9b..a8598d5d0b 100644 --- a/cli/conf.ts +++ b/cli/conf.ts @@ -12,6 +12,7 @@ export interface SyncOptions { skipResources?: boolean; skipSecrets?: boolean; includeSchedules?: boolean; + includeTriggers?: boolean; includeUsers?: boolean; includeGroups?: boolean; includeSettings?: boolean; diff --git a/cli/gen/core/OpenAPI.ts b/cli/gen/core/OpenAPI.ts index 38bbd34aac..4488a855bf 100644 --- a/cli/gen/core/OpenAPI.ts +++ b/cli/gen/core/OpenAPI.ts @@ -54,7 +54,7 @@ export const OpenAPI: OpenAPIConfig = { PASSWORD: undefined, TOKEN: getEnv("WM_TOKEN"), USERNAME: undefined, - VERSION: '1.454.1', + VERSION: '1.457.1', WITH_CREDENTIALS: true, interceptors: { request: new Interceptors(), diff --git a/cli/gen/services.gen.ts b/cli/gen/services.gen.ts index 66481a2857..4992329350 100644 --- a/cli/gen/services.gen.ts +++ b/cli/gen/services.gen.ts @@ -2895,7 +2895,7 @@ export const deleteScriptByHash = (data: DeleteScriptByHashData): CancelableProm }); }; /** - * delete all scripts at a given path (require admin) + * delete script at a given path (require admin) * @param data The data for the request. * @param data.workspace * @param data.path diff --git a/cli/gen/types.gen.ts b/cli/gen/types.gen.ts index 4695b99980..075aa848ee 100644 --- a/cli/gen/types.gen.ts +++ b/cli/gen/types.gen.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -export type AIProvider = 'openai' | 'anthropic' | 'mistral' | 'deepseek' | 'customai'; +export type AIProvider = 'openai' | 'anthropic' | 'mistral' | 'deepseek' | 'groq' | 'openrouter' | 'customai'; export type AIResource = { path: string; @@ -168,6 +168,7 @@ export type QueuedJob = { self_wait_time_ms?: number; aggregate_wait_time_ms?: number; suspend?: number; + preprocessed?: boolean; }; export type job_kind = 'script' | 'preview' | 'dependencies' | 'flowdependencies' | 'appdependencies' | 'flow' | 'flowpreview' | 'script_hub' | 'identity' | 'deploymentcallback' | 'singlescriptflow' | 'flowscript' | 'flownode' | 'appscript'; @@ -212,6 +213,7 @@ export type CompletedJob = { labels?: Array<(string)>; self_wait_time_ms?: number; aggregate_wait_time_ms?: number; + preprocessed?: boolean; }; export type ObscuredJob = { @@ -575,6 +577,7 @@ export type HttpTrigger = TriggerExtraProperty & { http_method: 'get' | 'post' | 'put' | 'delete' | 'patch'; is_async: boolean; requires_auth: boolean; + is_static_website: boolean; }; export type http_method = 'get' | 'post' | 'put' | 'delete' | 'patch'; @@ -592,6 +595,7 @@ export type NewHttpTrigger = { http_method: 'get' | 'post' | 'put' | 'delete' | 'patch'; is_async: boolean; requires_auth: boolean; + is_static_website: boolean; }; export type EditHttpTrigger = { @@ -607,6 +611,7 @@ export type EditHttpTrigger = { http_method: 'get' | 'post' | 'put' | 'delete' | 'patch'; is_async: boolean; requires_auth: boolean; + is_static_website: boolean; }; export type TriggersCount = { @@ -2598,7 +2603,7 @@ export type GetCopilotInfoData = { }; export type GetCopilotInfoResponse = ({ - ai_provider: AIProvider; + ai_provider?: AIProvider; exists_ai_resource: boolean; code_completion_model?: string; ai_models: Array<(string)>; @@ -5571,6 +5576,7 @@ export type ExistsRouteData = { requestBody: { route_path: string; http_method: 'get' | 'post' | 'put' | 'delete' | 'patch'; + trigger_path?: string; }; workspace: string; }; diff --git a/cli/instance.ts b/cli/instance.ts index e80db19700..5a68f29c30 100644 --- a/cli/instance.ts +++ b/cli/instance.ts @@ -363,6 +363,7 @@ async function instancePull(opts: InstanceSyncOptions) { baseUrl: undefined, includeGroups: true, includeSchedules: true, + includeTriggers: true, includeSettings: true, includeUsers: true, includeKey: true, @@ -529,6 +530,7 @@ async function instancePush(opts: InstanceSyncOptions) { baseUrl: undefined, includeGroups: true, includeSchedules: true, + includeTriggers: true, includeSettings: true, includeUsers: true, includeKey: true, diff --git a/cli/main.ts b/cli/main.ts index e3fe5322c5..aad1a2ec83 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -17,6 +17,7 @@ import variable from "./variable.ts"; import hub from "./hub.ts"; import folder from "./folder.ts"; import schedule from "./schedule.ts"; +import trigger from "./trigger.ts"; import sync from "./sync.ts"; import instance from "./instance.ts"; import workerGroups from "./worker_groups.ts"; @@ -44,6 +45,7 @@ export { hub, folder, schedule, + trigger, sync, instance, dev, @@ -60,7 +62,7 @@ export { // } // }); -export const VERSION = "1.458.3"; +export const VERSION = "1.458.4"; const command = new Command() .name("wmill") @@ -108,6 +110,7 @@ const command = new Command() skipResources: true, skipSecrets: true, includeSchedules: false, + includeTriggers: false, }) ); log.info(colors.green("wmill.yaml created")); @@ -122,6 +125,7 @@ const command = new Command() .command("hub", hub) .command("folder", folder) .command("schedule", schedule) + .command("trigger", trigger) .command("dev", dev) .command("sync", sync) .command("instance", instance) diff --git a/cli/pull.ts b/cli/pull.ts index 769f46bf0b..51474f9eb8 100644 --- a/cli/pull.ts +++ b/cli/pull.ts @@ -11,6 +11,7 @@ export async function downloadZip( skipResources?: boolean, skipSecrets?: boolean, includeSchedules?: boolean, + includeTriggers?: boolean, includeUsers?: boolean, includeGroups?: boolean, includeSettings?: boolean, @@ -38,11 +39,11 @@ export async function downloadZip( skipResources ?? false }&skip_secrets=${skipSecrets ?? false}&include_schedules=${ includeSchedules ?? false - }&include_users=${includeUsers ?? false}&include_groups=${ - includeGroups ?? false - }&include_settings=${includeSettings ?? false}&include_key=${ - includeKey ?? false - }&default_ts=${defaultTs ?? "bun"}`, + }&include_triggers=${includeTriggers ?? false}&include_users=${ + includeUsers ?? false + }&include_groups=${includeGroups ?? false}&include_settings=${ + includeSettings ?? false + }&include_key=${includeKey ?? false}&default_ts=${defaultTs ?? "bun"}`, { headers: requestHeaders, method: "GET", diff --git a/cli/settings.ts b/cli/settings.ts index ff4b0e80cf..054763a16b 100644 --- a/cli/settings.ts +++ b/cli/settings.ts @@ -28,6 +28,9 @@ export interface SimplifiedSettings { default_app?: string; default_scripts?: any; name: string; + mute_critical_alerts?: boolean; + color?: string; + operator_settings?: any; } const INSTANCE_SETTINGS_PATH = "instance_settings.yaml"; @@ -85,6 +88,9 @@ export async function pushWorkspaceSettings( default_app: remoteSettings.default_app, default_scripts: remoteSettings.default_scripts, name: workspaceName, + mute_critical_alerts: remoteSettings.mute_critical_alerts, + color: remoteSettings.color, + operator_settings: remoteSettings.operator_settings, }; } catch (err) { throw new Error(`Failed to get workspace settings: ${err}`); @@ -166,12 +172,12 @@ export async function pushWorkspaceSettings( }); } if ( - localSettings.error_handler !== settings.error_handler || + localSettings.error_handler != settings.error_handler || !deepEqual( localSettings.error_handler_extra_args, settings.error_handler_extra_args ) || - localSettings.error_handler_muted_on_cancel !== + localSettings.error_handler_muted_on_cancel != settings.error_handler_muted_on_cancel ) { log.debug(`Updating error handler...`); @@ -185,7 +191,7 @@ export async function pushWorkspaceSettings( }, }); } - if (localSettings.deploy_to !== settings.deploy_to) { + if (localSettings.deploy_to != settings.deploy_to) { log.debug(`Updating deploy to...`); await wmill.editDeployTo({ workspace, @@ -221,7 +227,7 @@ export async function pushWorkspaceSettings( requestBody: localSettings.default_scripts, }); } - if (localSettings.default_app !== settings.default_app) { + if (localSettings.default_app != settings.default_app) { log.debug(`Updating default app...`); await wmill.editWorkspaceDefaultApp({ workspace, @@ -231,7 +237,7 @@ export async function pushWorkspaceSettings( }); } - if (localSettings.name !== settings.name) { + if (localSettings.name != settings.name) { log.debug(`Updating workspace name...`); await wmill.changeWorkspaceName({ workspace, @@ -240,6 +246,34 @@ export async function pushWorkspaceSettings( }, }); } + + if (localSettings.mute_critical_alerts != settings.mute_critical_alerts) { + log.debug(`Updating mute critical alerts...`); + await wmill.workspaceMuteCriticalAlertsUi({ + workspace, + requestBody: { + mute_critical_alerts: localSettings.mute_critical_alerts, + }, + }); + } + + if (localSettings.color != settings.color) { + log.debug(`Updating workspace color...`); + await wmill.changeWorkspaceColor({ + workspace, + requestBody: { + color: localSettings.color, + }, + }); + } + + if (localSettings.operator_settings != settings.operator_settings) { + log.debug(`Updating operator settings...`); + await wmill.updateOperatorSettings({ + workspace, + requestBody: localSettings.operator_settings, + }); + } } export async function pushWorkspaceKey( diff --git a/cli/sync.ts b/cli/sync.ts index b691a0d42c..e350b94fef 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -116,7 +116,7 @@ async function addCodebaseDigestIfRelevant( const parsed: any = yamlParseContent(path, content); if (parsed && typeof parsed == "object") { parsed["codebase"] = c.digest; - parsed["lock"] = ''; + parsed["lock"] = ""; return yamlStringify(parsed, yamlOptions); } else { throw Error( @@ -159,8 +159,12 @@ export async function FSFSElement( // }, async getContentText(): Promise { const content = await Deno.readTextFile(localP); - const itemPath = localP.substring(p.length + 1) - const r = await addCodebaseDigestIfRelevant(itemPath, content, codebases); + const itemPath = localP.substring(p.length + 1); + const r = await addCodebaseDigestIfRelevant( + itemPath, + content, + codebases + ); return r; }, }; @@ -352,12 +356,12 @@ function ZipFSElement( ) ? "flow" : p.endsWith("app.json") - ? "app" - : p.endsWith("script.json") - ? "script" - : p.endsWith("resource.json") - ? "resource" - : "other"; + ? "app" + : p.endsWith("script.json") + ? "script" + : p.endsWith("resource.json") + ? "resource" + : "other"; const isJson = p.endsWith(".json"); @@ -387,7 +391,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, s.path), - async *getChildren() { }, + async *getChildren() {}, // deno-lint-ignore require-await async getContentText() { return s.content; @@ -398,7 +402,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, "flow.yaml"), - async *getChildren() { }, + async *getChildren() {}, // deno-lint-ignore require-await async getContentText() { return yamlStringify(flow, yamlOptions); @@ -414,7 +418,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, s.path), - async *getChildren() { }, + async *getChildren() {}, // deno-lint-ignore require-await async getContentText() { return s.content; @@ -425,7 +429,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, "app.yaml"), - async *getChildren() { }, + async *getChildren() {}, // deno-lint-ignore require-await async getContentText() { return yamlStringify(app, yamlOptions); @@ -490,7 +494,7 @@ function ZipFSElement( r.push({ isDirectory: false, path: removeSuffix(finalPath, ".json") + ".lock", - async *getChildren() { }, + async *getChildren() {}, // deno-lint-ignore require-await async getContentText() { return lock; @@ -513,7 +517,7 @@ function ZipFSElement( removeSuffix(finalPath, ".resource.json") + ".resource.file." + formatExtension, - async *getChildren() { }, + async *getChildren() {}, // deno-lint-ignore require-await async getContentText() { return fileContent; @@ -574,19 +578,19 @@ export async function* readDirRecursiveWithIgnore( // getContentBytes(): Promise; getContentText(): Promise; }[] = [ - { - path: root.path, - ignored: ignore(root.path, root.isDirectory), - isDirectory: root.isDirectory, - c: root.getChildren, - // getContentBytes(): Promise { - // throw undefined; - // }, - getContentText(): Promise { - throw undefined; - }, + { + path: root.path, + ignored: ignore(root.path, root.isDirectory), + isDirectory: root.isDirectory, + c: root.getChildren, + // getContentBytes(): Promise { + // throw undefined; + // }, + getContentText(): Promise { + throw undefined; }, - ]; + }, + ]; while (stack.length > 0) { const e = stack.pop()!; @@ -606,7 +610,13 @@ export async function* readDirRecursiveWithIgnore( type Added = { name: "added"; path: string; content: string }; type Deleted = { name: "deleted"; path: string }; -type Edit = { name: "edited"; path: string; before: string; after: string; codebase?: string }; +type Edit = { + name: "edited"; + path: string; + before: string; + after: string; + codebase?: string; +}; type Change = Added | Deleted | Edit; @@ -624,6 +634,15 @@ export async function elementsToMap( if (!json && path.endsWith(".json") && !isFileResource(path)) continue; const ext = json ? ".json" : ".yaml"; if (!skips.includeSchedules && path.endsWith(".schedule" + ext)) continue; + if ( + !skips.includeTriggers && + (path.endsWith(".http_trigger" + ext) || + path.endsWith(".websocket_trigger" + ext) || + path.endsWith(".kafka_trigger" + ext) || + path.endsWith(".nats_trigger" + ext) || + path.endsWith(".postgres_trigger" + ext)) + ) + continue; if (!skips.includeUsers && path.endsWith(".user" + ext)) continue; if (!skips.includeGroups && path.endsWith(".group" + ext)) continue; if (!skips.includeSettings && path === "settings" + ext) continue; @@ -683,6 +702,7 @@ export interface Skips { skipSecrets?: boolean | undefined; skipScriptsMetadata?: boolean | undefined; includeSchedules?: boolean | undefined; + includeTriggers?: boolean | undefined; includeUsers?: boolean | undefined; includeGroups?: boolean | undefined; includeSettings?: boolean | undefined; @@ -700,9 +720,9 @@ async function compareDynFSElement( ): Promise { const [m1, m2] = els2 ? await Promise.all([ - elementsToMap(els1, ignore, json, skips), - elementsToMap(els2, ignore, json, skips), - ]) + elementsToMap(els1, ignore, json, skips), + elementsToMap(els2, ignore, json, skips), + ]) : [await elementsToMap(els1, ignore, json, skips), {}]; const changes: Change[] = []; @@ -738,7 +758,8 @@ async function compareDynFSElement( } const codebaseChanges: Record = {}; for (let [k, v] of Object.entries(m1)) { - const isScriptMetadata = k.endsWith(".script.yaml") || k.endsWith(".script.json"); + const isScriptMetadata = + k.endsWith(".script.yaml") || k.endsWith(".script.json"); const skipMetadata = skips.skipScriptsMetadata && isScriptMetadata; if (m2[k] === undefined) { @@ -749,8 +770,7 @@ async function compareDynFSElement( } else { if (m2[k] == v) { continue; - } - else if (k.endsWith(".json")) { + } else if (k.endsWith(".json")) { if (deepEqual(JSON.parse(v), JSON.parse(m2[k]))) { continue; } @@ -775,14 +795,23 @@ async function compareDynFSElement( continue; } } - changes.push({ name: "edited", path: k, after: v, before: m2[k], codebase: codebaseChanges[k] }); + changes.push({ + name: "edited", + path: k, + after: v, + before: m2[k], + codebase: codebaseChanges[k], + }); } } const remoteCodebase: Record = {}; for (const [k] of Object.entries(m2)) { if (m1[k] === undefined) { - if (!ignoreMetadataDeletion || (!k?.endsWith(".script.yaml") && !k?.endsWith(".script.json"))) { + if ( + !ignoreMetadataDeletion || + (!k?.endsWith(".script.yaml") && !k?.endsWith(".script.json")) + ) { changes.push({ name: "deleted", path: k }); } else if (k?.endsWith(".script.yaml")) { let o = parseYaml(k, m2[k]); @@ -795,12 +824,22 @@ async function compareDynFSElement( for (const [k, v] of Object.entries(remoteCodebase)) { const tsFile = k.replace(".script.yaml", ".ts"); - if (changes.find(c => c.path == tsFile && (c.name == "edited" || c.name == "deleted"))) { + if ( + changes.find( + (c) => c.path == tsFile && (c.name == "edited" || c.name == "deleted") + ) + ) { continue; } let c = findCodebase(tsFile, codebases); if (c?.digest != v) { - changes.push({ name: "edited", path: tsFile, codebase: v, before: m1[tsFile], after: m2[tsFile] }); + changes.push({ + name: "edited", + path: tsFile, + codebase: v, + before: m1[tsFile], + after: m2[tsFile], + }); } } @@ -841,16 +880,24 @@ function getOrderFromPath(p: string) { return 6; } else if (typ == "schedule") { return 7; - } else if (typ == "variable") { + } else if ( + typ == "http_trigger" || + typ == "websocket_trigger" || + typ == "kafka_trigger" || + typ == "nats_trigger" || + typ == "postgres_trigger" + ) { return 8; - } else if (typ == "user") { + } else if (typ == "variable") { return 9; - } else if (typ == "group") { + } else if (typ == "user") { return 10; - } else if (typ == "encryption_key") { + } else if (typ == "group") { return 11; - } else { + } else if (typ == "encryption_key") { return 12; + } else { + return 13; } } @@ -956,8 +1003,7 @@ async function addToChangedIfNotExists(p: string, tracker: ChangeTracker) { const isScript = exts.some((e) => p.endsWith(e)); if (isScript) { if (p.includes(".flow" + SEP)) { - const folder = - p.substring(0, p.indexOf(".flow" + SEP)) + ".flow" + SEP; + const folder = p.substring(0, p.indexOf(".flow" + SEP)) + ".flow" + SEP; if (!tracker.flows.includes(folder)) { tracker.flows.push(folder); } @@ -1031,6 +1077,7 @@ export async function pull(opts: GlobalOptions & SyncOptions) { opts.skipResources, opts.skipSecrets, opts.includeSchedules, + opts.includeTriggers, opts.includeUsers, opts.includeGroups, opts.includeSettings, @@ -1071,8 +1118,6 @@ export async function pull(opts: GlobalOptions & SyncOptions) { const conflicts = []; - - log.info(colors.gray(`Applying changes to files ...`)); for await (const change of changes) { const target = path.join(Deno.cwd(), change.path); @@ -1224,7 +1269,11 @@ function prettyChanges(changes: Change[]) { ); } else if (change.name === "edited") { log.info( - colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path + (change.codebase ? ` (codebase changed)` : "")) + colors.yellow( + `~ ${getTypeStrFromPath(change.path)} ` + + change.path + + (change.codebase ? ` (codebase changed)` : "") + ) ); if (change.before != change.after) { showDiff(change.before, change.after); @@ -1300,6 +1349,7 @@ export async function push(opts: GlobalOptions & SyncOptions) { opts.skipResources, opts.skipSecrets, opts.includeSchedules, + opts.includeTriggers, opts.includeUsers, opts.includeGroups, opts.includeSettings, @@ -1322,7 +1372,6 @@ export async function push(opts: GlobalOptions & SyncOptions) { codebases ); - const globalDeps = await findGlobalDeps(); const tracker: ChangeTracker = await buildTracker(changes); @@ -1346,28 +1395,38 @@ export async function push(opts: GlobalOptions & SyncOptions) { } if (staleScripts.length > 0) { - log.info("") - log.warn("Stale scripts metadata found, you may want to update them using 'wmill script generate-metadata' before pushing:"); + log.info(""); + log.warn( + "Stale scripts metadata found, you may want to update them using 'wmill script generate-metadata' before pushing:" + ); for (const stale of staleScripts) { log.warn(stale); } - log.info("") + log.info(""); } for (const change of tracker.flows) { - const stale = await generateFlowLockInternal(change, true, workspace, false, true); + const stale = await generateFlowLockInternal( + change, + true, + workspace, + false, + true + ); if (stale) { staleFlows.push(stale); } } if (staleFlows.length > 0) { - log.warn("Stale flows locks found, you may want to update them using 'wmill flow generate-locks' before pushing:"); + log.warn( + "Stale flows locks found, you may want to update them using 'wmill flow generate-locks' before pushing:" + ); for (const stale of staleFlows) { log.warn(stale); } - log.info("") + log.info(""); } const version = await fetchVersion(workspace.remote); @@ -1393,7 +1452,6 @@ export async function push(opts: GlobalOptions & SyncOptions) { const start = performance.now(); log.info(colors.gray(`Applying changes to files ...`)); - let stateful = opts.stateful; if (stateful) { try { @@ -1406,21 +1464,26 @@ export async function push(opts: GlobalOptions & SyncOptions) { // Group changes by base path (before first dot) const groupedChanges = new Map(); for (const change of changes) { - const basePath = change.path.split('.')[0]; + const basePath = change.path.split(".")[0]; if (!groupedChanges.has(basePath)) { groupedChanges.set(basePath, []); } groupedChanges.get(basePath)!.push(change); } - - let parallelizationFactor = opts.parallel ?? 1; if (parallelizationFactor <= 0) { parallelizationFactor = 1; } const groupedChangesArray = Array.from(groupedChanges.entries()); - log.info(`found changes for ${groupedChangesArray.length} items with a total of ${groupedChangesArray.reduce((acc, [_, changes]) => acc + changes.length, 0)} files to process`); + log.info( + `found changes for ${ + groupedChangesArray.length + } items with a total of ${groupedChangesArray.reduce( + (acc, [_, changes]) => acc + changes.length, + 0 + )} files to process` + ); if (parallelizationFactor > 1) { log.info(`Parallelizing ${parallelizationFactor} changes at a time`); } @@ -1481,7 +1544,9 @@ export async function push(opts: GlobalOptions & SyncOptions) { } if (stateTarget) { await ensureDir(path.dirname(stateTarget)); - log.info(`Editing ${getTypeStrFromPath(change.path)} ${change.path}`); + log.info( + `Editing ${getTypeStrFromPath(change.path)} ${change.path}` + ); } if (isFileResource(change.path)) { @@ -1545,7 +1610,9 @@ export async function push(opts: GlobalOptions & SyncOptions) { } if (stateTarget) { await ensureDir(path.dirname(stateTarget)); - log.info(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`); + log.info( + `Adding ${getTypeStrFromPath(change.path)} ${change.path}` + ); } const obj = parseFromPath(change.path, change.content); await pushObj( @@ -1622,6 +1689,36 @@ export async function push(opts: GlobalOptions & SyncOptions) { path: removeSuffix(target, ".schedule.json"), }); break; + case "http_trigger": + await wmill.deleteHttpTrigger({ + workspace: workspaceId, + path: removeSuffix(target, ".http_trigger.json"), + }); + break; + case "websocket_trigger": + await wmill.deleteWebsocketTrigger({ + workspace: workspaceId, + path: removeSuffix(target, ".websocket_trigger.json"), + }); + break; + case "kafka_trigger": + await wmill.deleteKafkaTrigger({ + workspace: workspaceId, + path: removeSuffix(target, ".kafka_trigger.json"), + }); + break; + case "nats_trigger": + await wmill.deleteNatsTrigger({ + workspace: workspaceId, + path: removeSuffix(target, ".nats_trigger.json"), + }); + break; + case "postgres_trigger": + await wmill.deletePostgresTrigger({ + workspace: workspaceId, + path: removeSuffix(target, ".postgres_trigger.json"), + }); + break; case "variable": await wmill.deleteVariable({ workspace: workspaceId, @@ -1682,7 +1779,9 @@ export async function push(opts: GlobalOptions & SyncOptions) { } log.info( colors.bold.green.underline( - `\nDone! All ${changes.length} changes pushed to the remote workspace ${workspace.workspaceId} named ${workspace.name} (${(performance.now() - start).toFixed(0)}ms)` + `\nDone! All ${changes.length} changes pushed to the remote workspace ${ + workspace.workspaceId + } named ${workspace.name} (${(performance.now() - start).toFixed(0)}ms)` ) ); } @@ -1706,6 +1805,7 @@ const command = new Command() .option("--skip-resources", "Skip syncing resources") // .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic") .option("--include-schedules", "Include syncing schedules") + .option("--include-triggers", "Include syncing triggers") .option("--include-users", "Include syncing users") .option("--include-groups", "Include syncing groups") .option("--include-settings", "Include syncing workspace settings") @@ -1734,6 +1834,7 @@ const command = new Command() .option("--skip-resources", "Skip syncing resources") // .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic") .option("--include-schedules", "Include syncing schedules") + .option("--include-triggers", "Include syncing triggers") .option("--include-users", "Include syncing users") .option("--include-groups", "Include syncing groups") .option("--include-settings", "Include syncing workspace settings") @@ -1754,10 +1855,7 @@ const command = new Command() "--message ", "Include a message that will be added to all scripts/flows/apps updated during this push" ) - .option( - "--parallel ", - "Number of changes to process in parallel" - ) + .option("--parallel ", "Number of changes to process in parallel") // deno-lint-ignore no-explicit-any .action(push as any); diff --git a/cli/trigger.ts b/cli/trigger.ts new file mode 100644 index 0000000000..9f0b9d887b --- /dev/null +++ b/cli/trigger.ts @@ -0,0 +1,245 @@ +import * as wmill from "./gen/services.gen.ts"; +import { + HttpTrigger, + KafkaTrigger, + NatsTrigger, + PostgresTrigger, + WebsocketTrigger, +} from "./gen/types.gen.ts"; +import { colors, Command, log, SEP, Table } from "./deps.ts"; +import { + GlobalOptions, + isSuperset, + parseFromFile, + removeType, +} from "./types.ts"; +import { requireLogin } from "./context.ts"; +import { validatePath } from "./context.ts"; +import { resolveWorkspace } from "./context.ts"; + +type Trigger = { + http: HttpTrigger; + websocket: WebsocketTrigger; + kafka: KafkaTrigger; + nats: NatsTrigger; + postgres: PostgresTrigger; +}; + +type TriggerFile = Omit< + Trigger[K], + | "path" + | "workspace" + | "edited_by" + | "edited_at" + | "error" + | "last_server_ping" + | "server_id" +>; + +type TriggerType = keyof Trigger; + +async function getTrigger( + triggerType: K, + workspace: string, + path: string +): Promise { + const triggerFunctions: { + [K in TriggerType]: (args: { + workspace: string; + path: string; + }) => Promise; + } = { + http: wmill.getHttpTrigger, + websocket: wmill.getWebsocketTrigger, + kafka: wmill.getKafkaTrigger, + nats: wmill.getNatsTrigger, + postgres: wmill.getPostgresTrigger, + }; + const triggerFunction = triggerFunctions[triggerType]; + + const trigger = await triggerFunction({ workspace, path }); + return trigger; +} + +async function updateTrigger( + triggerType: K, + workspace: string, + path: string, + trigger: Trigger[K] +): Promise { + const triggerFunctions: { + [K in TriggerType]: (args: { + workspace: string; + path: string; + requestBody: Trigger[K]; + }) => Promise; + } = { + http: wmill.updateHttpTrigger, + websocket: wmill.updateWebsocketTrigger, + kafka: wmill.updateKafkaTrigger, + nats: wmill.updateNatsTrigger, + postgres: wmill.updatePostgresTrigger, + }; + const triggerFunction = triggerFunctions[triggerType]; + await triggerFunction({ workspace, path, requestBody: trigger }); +} + +async function createTrigger( + triggerType: K, + workspace: string, + path: string, + trigger: Trigger[K] +): Promise { + const triggerFunctions: { + [K in TriggerType]: (args: { + workspace: string; + path: string; + requestBody: Trigger[K]; + }) => Promise; + } = { + http: wmill.createHttpTrigger, + websocket: wmill.createWebsocketTrigger, + kafka: wmill.createKafkaTrigger, + nats: wmill.createNatsTrigger, + postgres: wmill.createPostgresTrigger, + }; + const triggerFunction = triggerFunctions[triggerType]; + await triggerFunction({ workspace, path, requestBody: trigger }); +} + +export async function pushTrigger( + triggerType: K, + workspace: string, + path: string, + trigger: TriggerFile | Trigger[K] | undefined, + localTrigger: TriggerFile +): Promise { + path = removeType(path, triggerType + "_trigger").replaceAll(SEP, "/"); + log.debug(`Processing local ${triggerType} trigger ${path}`); + + try { + trigger = await getTrigger(triggerType, workspace, path); + log.debug(`${triggerType} trigger ${path} exists on remote`); + } catch { + log.debug(`${triggerType} trigger ${path} does not exist on remote`); + //ignore + } + + if (trigger) { + if (isSuperset(localTrigger, trigger)) { + log.debug(`${triggerType} trigger ${path} is up to date`); + return; + } + log.debug(`${triggerType} trigger ${path} is not up-to-date, updating...`); + try { + await updateTrigger(triggerType, workspace, path, { + ...localTrigger, + path, + } as Trigger[K]); + } catch (e) { + console.error(e.body); + throw e; + } + } else { + console.log( + colors.bold.yellow(`Creating new ${triggerType} trigger: ${path}`) + ); + try { + await createTrigger(triggerType, workspace, path, { + ...localTrigger, + path, + } as Trigger[K]); + } catch (e) { + console.error(e.body); + throw e; + } + } +} + +async function list(opts: GlobalOptions) { + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + const httpTriggers = await wmill.listHttpTriggers({ + workspace: workspace.workspaceId, + }); + const websocketTriggers = await wmill.listWebsocketTriggers({ + workspace: workspace.workspaceId, + }); + const kafkaTriggers = await wmill.listKafkaTriggers({ + workspace: workspace.workspaceId, + }); + const natsTriggers = await wmill.listNatsTriggers({ + workspace: workspace.workspaceId, + }); + const postgresTriggers = await wmill.listPostgresTriggers({ + workspace: workspace.workspaceId, + }); + + const triggers = [ + ...httpTriggers.map((x) => ({ path: x.path, kind: "http" })), + ...websocketTriggers.map((x) => ({ path: x.path, kind: "websocket" })), + ...kafkaTriggers.map((x) => ({ path: x.path, kind: "kafka" })), + ...natsTriggers.map((x) => ({ path: x.path, kind: "nats" })), + ...postgresTriggers.map((x) => ({ path: x.path, kind: "postgres" })), + ]; + + new Table() + .header(["Path", "Kind"]) + .padding(2) + .border(true) + .body(triggers.map((x) => [x.path, x.kind])) + .render(); +} + +function checkIfValidTrigger(kind: string | undefined): kind is TriggerType { + if ( + kind && + ["http", "websocket", "kafka", "nats", "postgres"].includes(kind) + ) { + return true; + } else { + return false; + } +} + +async function push(opts: GlobalOptions, filePath: string, remotePath: string) { + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + if (!validatePath(remotePath)) { + return; + } + + const fstat = await Deno.stat(filePath); + if (!fstat.isFile) { + throw new Error("file path must refer to a file."); + } + + console.log(colors.bold.yellow("Pushing trigger...")); + + const triggerKind = filePath.split(".")[1].split("_")[0]; + if (!checkIfValidTrigger(triggerKind)) { + throw new Error("Invalid trigger kind: " + triggerKind); + } + await pushTrigger( + triggerKind, + workspace.workspaceId, + remotePath, + undefined, + parseFromFile(filePath) + ); + console.log(colors.bold.underline.green("Trigger pushed")); +} + +const command = new Command() + .description("trigger related commands") + .action(list as any) + .command( + "push", + "push a local trigger spec. This overrides any remote versions." + ) + .arguments(" ") + .action(push as any); + +export default command; diff --git a/cli/types.ts b/cli/types.ts index 0ba5a1c476..c075ca51d3 100644 --- a/cli/types.ts +++ b/cli/types.ts @@ -22,6 +22,7 @@ import { pushSchedule } from "./schedule.ts"; import { pushWorkspaceUser } from "./user.ts"; import { pushGroup } from "./user.ts"; import { pushWorkspaceSettings, pushWorkspaceKey } from "./settings.ts"; +import { pushTrigger } from "./trigger.ts"; export interface DifferenceCreate { type: "CREATE"; @@ -80,7 +81,7 @@ export function showDiff(local: string, remote: string) { return; } - for (const part of Diff.diffLines(local ?? '', remote ?? '')) { + for (const part of Diff.diffLines(local ?? "", remote ?? "")) { if (part.removed) { // print red if removed without newline finalString += `\x1b[31m${part.value}\x1b[0m`; @@ -139,6 +140,16 @@ export async function pushObj( await pushResourceType(workspace, p, befObj, newObj); } else if (typeEnding === "schedule") { await pushSchedule(workspace, p, befObj, newObj); + } else if (typeEnding === "http_trigger") { + await pushTrigger("http", workspace, p, befObj, newObj); + } else if (typeEnding === "websocket_trigger") { + await pushTrigger("websocket", workspace, p, befObj, newObj); + } else if (typeEnding === "kafka_trigger") { + await pushTrigger("kafka", workspace, p, befObj, newObj); + } else if (typeEnding === "nats_trigger") { + await pushTrigger("nats", workspace, p, befObj, newObj); + } else if (typeEnding === "postgres_trigger") { + await pushTrigger("postgres", workspace, p, befObj, newObj); } else if (typeEnding === "user") { await pushWorkspaceUser(workspace, p, befObj, newObj); } else if (typeEnding === "group") { @@ -158,8 +169,8 @@ export function parseFromPath(p: string, content: string): any { return p.endsWith(".yaml") ? yamlParseContent(p, content) : p.endsWith(".json") - ? JSON.parse(content) - : content; + ? JSON.parse(content) + : content; } export function parseFromFile(p: string): any { if (p.endsWith(".json")) { @@ -181,6 +192,11 @@ export function getTypeStrFromPath( | "folder" | "app" | "schedule" + | "http_trigger" + | "websocket_trigger" + | "kafka_trigger" + | "nats_trigger" + | "postgres_trigger" | "user" | "group" | "settings" @@ -221,6 +237,11 @@ export function getTypeStrFromPath( typeEnding === "resource-type" || typeEnding === "app" || typeEnding === "schedule" || + typeEnding === "http_trigger" || + typeEnding === "websocket_trigger" || + typeEnding === "kafka_trigger" || + typeEnding === "nats_trigger" || + typeEnding === "postgres_trigger" || typeEnding === "user" || typeEnding === "group" || typeEnding === "settings" || diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a1c0a9b2bf..1d4f18629b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.458.3", + "version": "1.458.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.458.3", + "version": "1.458.4", "license": "AGPL-3.0", "dependencies": { "@anthropic-ai/sdk": "^0.32.1", diff --git a/frontend/package.json b/frontend/package.json index ee0502f965..5f29a86923 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.458.3", + "version": "1.458.4", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/StringTypeNarrowing.svelte b/frontend/src/lib/components/StringTypeNarrowing.svelte index b960653b64..ae9b4e9a9d 100644 --- a/frontend/src/lib/components/StringTypeNarrowing.svelte +++ b/frontend/src/lib/components/StringTypeNarrowing.svelte @@ -120,6 +120,8 @@ } if (e.detail == 'date-time') { format = 'date-time' + } else if (format) { + format = undefined } if (e.detail == 'none') { pattern = undefined diff --git a/frontend/src/lib/components/sidebar/SidebarContent.svelte b/frontend/src/lib/components/sidebar/SidebarContent.svelte index 115276055d..2496506c7c 100644 --- a/frontend/src/lib/components/sidebar/SidebarContent.svelte +++ b/frontend/src/lib/components/sidebar/SidebarContent.svelte @@ -86,7 +86,7 @@ goto('/user/workspaces') } - const defaultExtraTriggerLinks = [ + $: defaultExtraTriggerLinks = [ { label: 'HTTP', href: '/routes', diff --git a/lsp/Pipfile b/lsp/Pipfile index 2c5c28437f..4808e50712 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.458.3" -wmill_pg = ">=1.458.3" +wmill = ">=1.458.4" +wmill_pg = ">=1.458.4" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 8d61285341..252d9ced7e 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.458.3 + version: 1.458.4 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 2fb802eab8..4964ae3ec4 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.458.3' + ModuleVersion = '1.458.4' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 4c41d571a0..03ec4e6c26 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.458.3" +version = "1.458.4" 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 e27ef5ec8f..871e6fa5c4 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.458.3" +version = "1.458.4" 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 6fcc0234dd..6d21e4d8c7 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.458.3", + "version": "1.458.4", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 8afa90c42b..5357cc3dd2 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.458.3", + "version": "1.458.4", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 2e49bbd074..dcd4fcaeb2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.458.3 +1.458.4