diff --git a/CHANGELOG.md b/CHANGELOG.md index be301a9f36..6a9df670c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [1.537.0](https://github.com/windmill-labs/windmill/compare/v1.536.0...v1.537.0) (2025-08-28) + + +### Features + +* **aichat:** allow reverting specific line for inline script suggestions ([#6480](https://github.com/windmill-labs/windmill/issues/6480)) ([0cc11b3](https://github.com/windmill-labs/windmill/commit/0cc11b3f31aeee60a9d4a231cea5d4285d7ab37e)) +* autovacuum or high intensity tables ([4ad0d25](https://github.com/windmill-labs/windmill/commit/4ad0d255f3eea303e97eab5325f89930b26f9e52)) + + +### Bug Fixes + +* fix okta and oauth0 sso settings ([73a3f4c](https://github.com/windmill-labs/windmill/commit/73a3f4cc73271759650e9246f4eb2e0efb7c7e37)) +* fix relative imports cache invalidation ([#6468](https://github.com/windmill-labs/windmill/issues/6468)) ([006f326](https://github.com/windmill-labs/windmill/commit/006f32602c7609b282f15135989c5f164c109c1c)) +* fix workflow as code behavior with multithread ([4973c86](https://github.com/windmill-labs/windmill/commit/4973c860f2c28d9bdc2af94530d90eb177234e5d)) + ## [1.536.0](https://github.com/windmill-labs/windmill/compare/v1.535.0...v1.536.0) (2025-08-27) diff --git a/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json b/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json deleted file mode 100644 index af706cdac9..0000000000 --- a/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a" -} diff --git a/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json b/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json deleted file mode 100644 index eee4c4d493..0000000000 --- a/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Text" - ] - }, - "nullable": [] - }, - "hash": "03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e" -} diff --git a/backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json b/backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json new file mode 100644 index 0000000000..51a1ec68cb --- /dev/null +++ b/backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n w.premium,\n COALESCE(cw.is_past_due, false) as \"is_past_due!\",\n cw.max_tolerated_executions\n FROM\n workspace w\n LEFT JOIN cloud_workspace_settings cw ON cw.workspace_id = w.id\n WHERE\n w.id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "premium", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "is_past_due!", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "max_tolerated_executions", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + null, + true + ] + }, + "hash": "124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec" +} diff --git a/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json b/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json deleted file mode 100644 index ab5b04afc6..0000000000 --- a/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT owner, premium, usage.usage as \"usage?\", workspace_settings.customer_id, workspace_settings.plan FROM workspace LEFT JOIN workspace_settings ON workspace_settings.workspace_id = $1 LEFT JOIN usage ON usage.id = $1 AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND usage.is_workspace IS true WHERE workspace.id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "owner", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "premium", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "usage?", - "type_info": "Int4" - }, - { - "ordinal": 3, - "name": "customer_id", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "plan", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - true, - true - ] - }, - "hash": "4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25" -} diff --git a/backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json b/backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json new file mode 100644 index 0000000000..9e3e7cb863 --- /dev/null +++ b/backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n owner, \n premium, \n usage.usage as \"usage?\", \n workspace_settings.customer_id, \n workspace_settings.plan, \n COALESCE(cw.is_past_due, false) as \"is_past_due!\", \n cw.max_tolerated_executions\n FROM workspace\n LEFT JOIN workspace_settings \n ON workspace_settings.workspace_id = $1\n LEFT JOIN usage \n ON usage.id = $1\n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND usage.is_workspace IS true\n LEFT JOIN cloud_workspace_settings cw\n ON cw.workspace_id = $1\n WHERE workspace.id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "owner", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "premium", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "usage?", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "customer_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "plan", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "is_past_due!", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "max_tolerated_executions", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + true, + null, + true + ] + }, + "hash": "ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f" +} diff --git a/backend/.sqlx/query-d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9.json b/backend/.sqlx/query-d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9.json deleted file mode 100644 index 22b38a5217..0000000000 --- a/backend/.sqlx/query-d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT premium FROM workspace WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "premium", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9" -} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7b7bc45206..669bd8efbb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -5863,7 +5863,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.3+wasi-0.2.4", "wasm-bindgen", ] @@ -10253,9 +10253,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases 0.2.1", @@ -10264,7 +10264,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls 0.23.29", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.16", "tokio", "tracing", @@ -10273,9 +10273,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", @@ -10294,16 +10294,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -14770,11 +14770,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.3+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -15129,7 +15129,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "axum", @@ -15183,7 +15183,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "argon2", @@ -15300,7 +15300,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.536.0" +version = "1.537.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15315,7 +15315,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.536.0" +version = "1.537.0" dependencies = [ "chrono", "serde", @@ -15328,7 +15328,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "axum", @@ -15347,7 +15347,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -15427,7 +15427,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.536.0" +version = "1.537.0" dependencies = [ "regex", "serde", @@ -15442,7 +15442,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "bytes", @@ -15466,7 +15466,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.536.0" +version = "1.537.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15478,7 +15478,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.536.0" +version = "1.537.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15487,7 +15487,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15499,7 +15499,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "serde_json", @@ -15511,7 +15511,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "gosyn", @@ -15523,7 +15523,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15535,7 +15535,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "serde_json", @@ -15547,7 +15547,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "nu-parser", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15569,7 +15569,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15581,7 +15581,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -15604,7 +15604,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15618,7 +15618,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15635,7 +15635,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15667,7 +15667,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15692,7 +15692,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "serde_json", @@ -15702,7 +15702,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -15735,7 +15735,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.536.0" +version = "1.537.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15745,7 +15745,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -16325,13 +16325,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.3", -] +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" [[package]] name = "writeable" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3f770576bf..69e5d4333e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.536.0" +version = "1.537.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ ] [workspace.package] -version = "1.536.0" +version = "1.537.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 111a3f2d18..dfa2a01850 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -6396854336ae27fb14ccb792d80c31ff614b2afa \ No newline at end of file +d16e52d570f10dfdabb04a9061fe7ebdbf5a2aa1 \ No newline at end of file diff --git a/backend/migrations/20250827102435_add_team_plan_status.down.sql b/backend/migrations/20250827102435_add_team_plan_status.down.sql new file mode 100644 index 0000000000..980b70d425 --- /dev/null +++ b/backend/migrations/20250827102435_add_team_plan_status.down.sql @@ -0,0 +1,7 @@ +-- Add down migration script here +DROP FUNCTION notify_team_plan_status_change; +DROP TRIGGER notify_team_plan_status_change ON cloud_workspace_settings; + +ALTER TABLE cloud_workspace_settings + DROP COLUMN is_past_due, + DROP COLUMN max_tolerated_executions; \ No newline at end of file diff --git a/backend/migrations/20250827102435_add_team_plan_status.up.sql b/backend/migrations/20250827102435_add_team_plan_status.up.sql new file mode 100644 index 0000000000..1b2007bfe7 --- /dev/null +++ b/backend/migrations/20250827102435_add_team_plan_status.up.sql @@ -0,0 +1,18 @@ +-- Add up migration script here +ALTER TABLE cloud_workspace_settings + ADD COLUMN is_past_due BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN max_tolerated_executions INTEGER; + +CREATE OR REPLACE FUNCTION notify_team_plan_status_change() +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('notify_workspace_premium_change', NEW.workspace_id); -- reuse the same channel as the one used for workspace premium change => clear cache + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER team_plan_status_change_trigger +AFTER UPDATE OF is_past_due, max_tolerated_executions ON cloud_workspace_settings +FOR EACH ROW +EXECUTE FUNCTION notify_team_plan_status_change(); \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 9b80e58eef..be0b86ec46 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -857,7 +857,7 @@ Windmill Community Edition {GIT_VERSION} "notify_workspace_premium_change" => { let workspace_id = n.payload(); tracing::info!("Workspace premium change detected, invalidating workspace premium cache: {}", workspace_id); - windmill_common::workspaces::IS_PREMIUM_CACHE.remove(workspace_id); + windmill_common::workspaces::TEAM_PLAN_CACHE.remove(workspace_id); }, "notify_runnable_version_change" => { let payload = n.payload(); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index c2471cebfb..eead7304a1 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.536.0 + version: 1.537.0 title: Windmill API contact: @@ -2034,6 +2034,11 @@ paths: - workspace parameters: - $ref: "#/components/parameters/WorkspaceId" + - name: skip_subscription_fetch + in: query + description: skip fetching subscription status from stripe + schema: + type: boolean responses: "200": @@ -2051,9 +2056,14 @@ paths: type: string status: type: string + is_past_due: + type: boolean + max_tolerated_executions: + type: number required: - premium - owner + - is_past_due /w/{workspace}/workspaces/threshold_alert: get: diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 78052b8ed8..9e3c7dc25e 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -415,7 +415,9 @@ async fn is_premium( ) -> JsonResult { require_admin(authed.is_admin, &authed.username)?; #[cfg(feature = "cloud")] - let premium = windmill_common::workspaces::is_premium_workspace(&_db, &_w_id).await; + let premium = windmill_common::workspaces::get_team_plan_status(&_db, &_w_id) + .await + .premium; #[cfg(not(feature = "cloud"))] let premium = false; Ok(Json(premium)) diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index 5d7d3d9967..c120e3c39a 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -86,22 +86,47 @@ impl Default for GitSyncSettings { } } +#[derive(Clone)] +pub struct TeamPlanStatus { + pub premium: bool, + pub is_past_due: bool, + pub max_tolerated_executions: Option, +} + lazy_static::lazy_static! { - pub static ref IS_PREMIUM_CACHE: Cache = Cache::new(5000); + pub static ref TEAM_PLAN_CACHE: Cache = Cache::new(5000); } #[cfg(feature = "cloud")] -pub async fn is_premium_workspace(_db: &crate::DB, _w_id: &str) -> bool { - let cached = IS_PREMIUM_CACHE.get(_w_id); +pub async fn get_team_plan_status(_db: &crate::DB, _w_id: &str) -> TeamPlanStatus { + let cached = TEAM_PLAN_CACHE.get(_w_id); if let Some(cached) = cached { return cached; } - let premium = sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", _w_id) - .fetch_one(_db) - .await - .unwrap_or(false); - IS_PREMIUM_CACHE.insert(_w_id.to_string(), premium); - premium + let team_plan_info = sqlx::query_as!( + TeamPlanStatus, + r#" + SELECT + w.premium, + COALESCE(cw.is_past_due, false) as "is_past_due!", + cw.max_tolerated_executions + FROM + workspace w + LEFT JOIN cloud_workspace_settings cw ON cw.workspace_id = w.id + WHERE + w.id = $1 + "#, + _w_id + ) + .fetch_one(_db) + .await + .unwrap_or_else(|_| TeamPlanStatus { + premium: false, + is_past_due: false, + max_tolerated_executions: None, + }); + TEAM_PLAN_CACHE.insert(_w_id.to_string(), team_plan_info.clone()); + team_plan_info } #[derive(Deserialize, Serialize, Debug)] diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 09b2a39695..3667239119 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -1419,8 +1419,9 @@ fn apply_completed_job_cloud_usage( let email2 = email.clone(); tokio::task::spawn(async move { let additional_usage = _duration / 1000; - let premium_workspace = - windmill_common::workspaces::is_premium_workspace(&db, &w_id).await; + let premium_workspace = windmill_common::workspaces::get_team_plan_status(&db, &w_id) + .await + .premium; tokio::time::timeout(std::time::Duration::from_secs(10), async move { let _ = sqlx::query!( "INSERT INTO usage (id, is_workspace, month_, usage) @@ -3636,8 +3637,8 @@ pub async fn push<'c, 'd>( ) -> Result<(Uuid, Transaction<'c, Postgres>), Error> { #[cfg(feature = "cloud")] if *CLOUD_HOSTED { - let premium_workspace = - windmill_common::workspaces::is_premium_workspace(_db, workspace_id).await; + let team_plan_status = + windmill_common::workspaces::get_team_plan_status(_db, workspace_id).await; // we track only non flow steps let (workspace_usage, user_usage) = if !matches!( job_payload, @@ -3655,7 +3656,7 @@ pub async fn push<'c, 'd>( .await .map_err(|e| Error::internal_err(format!("updating usage: {e:#}")))?; - let user_usage = if !premium_workspace { + let user_usage = if !team_plan_status.premium { Some(sqlx::query_scalar!( "INSERT INTO usage (id, is_workspace, month_, usage) VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) @@ -3678,7 +3679,7 @@ pub async fn push<'c, 'd>( Ok((None, None)) }?; - if !premium_workspace { + if !team_plan_status.premium || team_plan_status.is_past_due { let is_super_admin = sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email) .fetch_optional(_db) @@ -3686,7 +3687,8 @@ pub async fn push<'c, 'd>( .unwrap_or(false); if !is_super_admin { - if email != ERROR_HANDLER_USER_EMAIL + if !team_plan_status.premium + && email != ERROR_HANDLER_USER_EMAIL && email != SCHEDULE_ERROR_HANDLER_USER_EMAIL && email != SCHEDULE_RECOVERY_HANDLER_USER_EMAIL && email != "worker@windmill.dev" @@ -3765,43 +3767,53 @@ pub async fn push<'c, 'd>( .flatten() .unwrap_or(1) }; + if team_plan_status.premium { + // team plan is premium but past due, we check if the workspace has exceeded the max tolerated executions + if team_plan_status.max_tolerated_executions.is_none() + || workspace_usage > team_plan_status.max_tolerated_executions.unwrap() + { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} team plan is past due and isn't allowed to run any more jobs. Please fix your payment method in the workspace settings." + ))); + } + } else { + if workspace_usage > MAX_FREE_EXECS + && !matches!(job_payload, JobPayload::Dependencies { .. }) + && !matches!(job_payload, JobPayload::FlowDependencies { .. }) + && !matches!(job_payload, JobPayload::AppDependencies { .. }) + { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." + ))); + } - if workspace_usage > MAX_FREE_EXECS - && !matches!(job_payload, JobPayload::Dependencies { .. }) - && !matches!(job_payload, JobPayload::FlowDependencies { .. }) - && !matches!(job_payload, JobPayload::AppDependencies { .. }) - { - return Err(error::Error::QuotaExceeded(format!( - "Workspace {workspace_id} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." - ))); - } + let in_queue_workspace = sqlx::query_scalar!( + "SELECT COUNT(id) FROM v2_job_queue WHERE workspace_id = $1", + workspace_id + ) + .fetch_one(_db) + .await? + .unwrap_or(0); - let in_queue_workspace = sqlx::query_scalar!( - "SELECT COUNT(id) FROM v2_job_queue WHERE workspace_id = $1", - workspace_id - ) - .fetch_one(_db) - .await? - .unwrap_or(0); + if in_queue_workspace > MAX_FREE_EXECS as i64 { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." + ))); + } - if in_queue_workspace > MAX_FREE_EXECS as i64 { - return Err(error::Error::QuotaExceeded(format!( - "Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." - ))); - } - - let concurrent_runs_workspace = sqlx::query_scalar!( + let concurrent_runs_workspace = sqlx::query_scalar!( "SELECT COUNT(id) FROM v2_job_queue WHERE running = true AND workspace_id = $1", - workspace_id - ) - .fetch_one(_db) - .await? - .unwrap_or(0); + workspace_id + ) + .fetch_one(_db) + .await? + .unwrap_or(0); - if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS as i64 { - return Err(error::Error::QuotaExceeded(format!( - "Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." - ))); + if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS as i64 { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." + ))); + } } } } diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index 2b7523ecad..11718f3cf5 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -680,11 +680,12 @@ pub async fn resolve_job_timeout( let mut warn_msg: Option = None; #[cfg(feature = "cloud")] let cloud_premium_workspace = *CLOUD_HOSTED - && windmill_common::workspaces::is_premium_workspace( + && windmill_common::workspaces::get_team_plan_status( _conn.as_sql().expect("cloud cannot use http connection"), _w_id, ) - .await; + .await + .premium; #[cfg(not(feature = "cloud"))] let cloud_premium_workspace = false; diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index cdebe91692..dd145efd45 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.536.0"; +export const VERSION = "v1.537.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 8f98e43c0c..743ad64c75 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.536.0"; +export const VERSION = "1.537.0"; const command = new Command() .name("wmill") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6401b0b925..144e871b1c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.536.0", + "version": "1.537.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.536.0", + "version": "1.537.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 1f0841eea9..f98f5789ab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.536.0", + "version": "1.537.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index ae35b9fcfb..7ed6954710 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -150,7 +150,7 @@ import { writable } from 'svelte/store' import { formatResourceTypes } from './copilot/chat/script/core' import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte' - import { editorPositionMap, readFieldsRecursively } from '$lib/utils' + import { editorPositionMap } from '$lib/utils' import { extToLang, langToExt } from '$lib/editorLangUtils' import { aiChatManager } from './copilot/chat/AIChatManager.svelte' import type { Selection } from 'monaco-editor' @@ -544,9 +544,7 @@ let sqlSchemaCompletor: IDisposable | undefined = undefined - async function updateSchema() { - const newSchemaRes = lang === 'graphql' ? args?.api : args?.database - + async function updateSchema(newSchemaRes: string | undefined) { if (typeof newSchemaRes === 'string') { const resourcePath = newSchemaRes.replace('$res:', '') dbSchema = $dbSchemas[resourcePath] @@ -676,7 +674,21 @@ let selectedCode = $state('') export function reviewAndApplyCode(code: string, applyAll: boolean = false) { - aiChatEditorHandler?.reviewAndApply(code, applyAll) + aiChatEditorHandler?.reviewChanges(code, { applyAll, mode: 'apply' }) + } + + export function reviewAppliedCode( + originalCode: string, + opts?: { onFinishedReview?: () => void } + ) { + aiChatEditorHandler?.reviewChanges(originalCode, { + mode: 'revert', + onFinishedReview: opts?.onFinishedReview + }) + } + + export function getAiChatEditorHandler() { + return aiChatEditorHandler } function addChatHandler(editor: meditor.IStandaloneCodeEditor) { @@ -1607,10 +1619,14 @@ ? untrack(() => addSqlTypeCompletions()) : sqlTypeCompletor?.dispose() }) + + let lastArg = undefined $effect(() => { - console.log('updating schema', lang, $dbSchemas) - readFieldsRecursively(args) - lang && $dbSchemas && untrack(() => updateSchema()) + let newArg = lang === 'graphql' ? args?.api : args?.database + if (newArg !== lastArg) { + lastArg = newArg + $dbSchemas && untrack(() => updateSchema(newArg)) + } }) $effect(() => { console.log('updating db schema completions', dbSchema, lang) @@ -1684,10 +1700,20 @@ {#if $reviewingChanges} { - aiChatEditorHandler?.acceptAll() + const mode = aiChatEditorHandler?.getReviewMode?.() + if (mode === 'revert') { + aiChatEditorHandler?.keepAll() + } else { + aiChatEditorHandler?.acceptAll() + } }} onRejectAll={() => { - aiChatEditorHandler?.rejectAll() + const mode = aiChatEditorHandler?.getReviewMode?.() + if (mode === 'revert') { + aiChatEditorHandler?.revertAll() + } else { + aiChatEditorHandler?.rejectAll() + } }} /> {/if} diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index 9b7f7f96ba..388a3ab83f 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -6,7 +6,7 @@ import { dfs } from '$lib/components/flows/previousResults' import { dfs as dfsApply } from '$lib/components/flows/dfs' import { getSubModules } from '$lib/components/flows/flowExplorer' - import type { FlowModule, OpenFlow, RawScript } from '$lib/gen' + import type { FlowModule, OpenFlow } from '$lib/gen' import { getIndexInNestedModules, getNestedModules } from './utils' import type { AIModuleAction, FlowAIChatHelpers } from './core' import { @@ -93,13 +93,10 @@ hasDiff: () => { return Object.keys(affectedModules).length > 0 }, - acceptAllModuleActions: () => { - for (const [id, affectedModule] of Object.entries(affectedModules)) { - if (affectedModule.action === 'removed') { - deleteStep(id) - } + acceptAllModuleActions() { + for (const id of Object.keys(affectedModules)) { + this.acceptModuleAction(id) } - affectedModules = {} }, rejectAllModuleActions() { for (const id of Object.keys(affectedModules)) { @@ -181,8 +178,11 @@ $currentEditor?.type === 'script' && $currentEditor.stepId === id ) { - $currentEditor.editor.setCode((oldModule.value as RawScript).content) - $currentEditor.hideDiffMode() + const aiChatEditorHandler = $currentEditor.editor.getAiChatEditorHandler() + if (aiChatEditorHandler) { + aiChatEditorHandler.revertAll({ disableReviewCallback: true }) + $currentEditor.hideDiffMode() + } } newModule.value = oldModule.value @@ -197,6 +197,18 @@ if (affectedModules[id]?.action === 'removed') { deleteStep(id) } + + if ( + affectedModules[id]?.action === 'modified' && + $currentEditor && + $currentEditor.type === 'script' && + $currentEditor.stepId === id + ) { + const aiChatEditorHandler = $currentEditor.editor.getAiChatEditorHandler() + if (aiChatEditorHandler) { + aiChatEditorHandler.keepAll({ disableReviewCallback: true }) + } + } delete affectedModules[id] }, // ai chat tools @@ -536,43 +548,27 @@ return cleanup }) - // Automatically show diff mode when selecting a rawscript module with pending changes + // Automatically show revert review when selecting a rawscript module with pending changes $effect(() => { if ( $currentEditor?.type === 'script' && $selectedId && affectedModules[$selectedId] && - lastSnapshot + $currentEditor.editor.getAiChatEditorHandler() ) { const moduleLastSnapshot = getModule($selectedId, lastSnapshot) - const currentModule = getModule($selectedId) - - if ( - moduleLastSnapshot && - currentModule && - currentModule.value.type === 'rawscript' && - moduleLastSnapshot.value.type === 'rawscript' - ) { - // Show diff mode automatically - $currentEditor.setDiffOriginal?.(moduleLastSnapshot.value.content ?? '') - $currentEditor.showDiffMode() - $currentEditor.setDiffButtons?.([ - { - text: 'Accept Changes', - color: 'green', - onClick: () => { - flowHelpers.acceptModuleAction($selectedId) - $currentEditor?.hideDiffMode() + const content = + moduleLastSnapshot?.value.type === 'rawscript' ? moduleLastSnapshot.value.content : '' + if (content.length > 0) { + untrack(() => + $currentEditor.editor.reviewAppliedCode(content, { + onFinishedReview: () => { + const id = $selectedId + flowHelpers.acceptModuleAction(id) + $currentEditor.hideDiffMode() } - }, - { - text: 'Reject Changes', - onClick: () => { - flowHelpers.revertModuleAction($selectedId) - $currentEditor?.hideDiffMode() - } - } - ]) + }) + ) } } }) diff --git a/frontend/src/lib/components/copilot/chat/monaco-adapter.ts b/frontend/src/lib/components/copilot/chat/monaco-adapter.ts index 65131ff45e..d940f3e3fa 100644 --- a/frontend/src/lib/components/copilot/chat/monaco-adapter.ts +++ b/frontend/src/lib/components/copilot/chat/monaco-adapter.ts @@ -24,6 +24,12 @@ export class AIChatEditorHandler { reviewingChanges: Writable = writable(false) groupChanges: { changes: VisualChangeWithDiffIndex[]; groupIndex: number }[] = [] + // Track review decisions + private reviewState: { + mode: 'apply' | 'revert' + onFinishedReview?: () => void + } | null = null + constructor(editor: meditor.IStandaloneCodeEditor) { this.editor = editor } @@ -65,7 +71,16 @@ export class AIChatEditorHandler { } } - async finish() { + async finish(opts?: { disableReviewCallback?: boolean }) { + // expose mode getter relies on reviewState + // Call completion callback if we're tracking review state + if (this.reviewState?.onFinishedReview && !opts?.disableReviewCallback) { + this.reviewState.onFinishedReview() + } + + // Reset review state + this.reviewState = null + this.clear() this.allowWriting() this.reviewingChanges.set(false) @@ -74,16 +89,30 @@ export class AIChatEditorHandler { }) } - async acceptAll() { + getReviewMode(): 'apply' | 'revert' | null { + return this.reviewState?.mode ?? null + } + + async acceptAll(opts?: { disableReviewCallback?: boolean }) { this.groupChanges.reverse() for (const group of this.groupChanges) { this.applyGroup(group) } - this.finish() + this.finish(opts) } - async rejectAll() { - this.finish() + async rejectAll(opts?: { disableReviewCallback?: boolean }) { + this.finish(opts) + } + + // Keep all changes, used in revert mode + async keepAll(opts?: { disableReviewCallback?: boolean }) { + this.finish(opts) + } + + // Revert all changes, used in revert mode + async revertAll(opts?: { disableReviewCallback?: boolean }) { + this.acceptAll(opts) } applyGroup(group: { changes: VisualChangeWithDiffIndex[]; groupIndex: number }) { @@ -167,23 +196,41 @@ export class AIChatEditorHandler { return changedLines } - async reviewAndApply(newCode: string, applyAll: boolean = false) { - if (aiChatManager.pendingNewCode === newCode) { + async reviewChanges( + targetCode: string, + opts?: { + applyAll?: boolean + mode?: 'apply' | 'revert' + onFinishedReview?: () => void + } + ) { + if (aiChatManager.pendingNewCode === targetCode && opts?.mode === 'apply') { this.acceptAll() return } else if (aiChatManager.pendingNewCode) { this.clear() } - aiChatManager.pendingNewCode = newCode - const changedLines = await this.calculateVisualChanges(newCode) + + aiChatManager.pendingNewCode = targetCode + const changedLines = await this.calculateVisualChanges(targetCode) if (changedLines.length === 0) return + // Initialize review state for tracking + this.reviewState = { + mode: opts?.mode ?? 'apply', + onFinishedReview: opts?.onFinishedReview + } + let indicesOfRejectedLineChanges: number[] = [] for (const [groupIndex, group] of this.groupChanges.entries()) { let collection: meditor.IEditorDecorationsCollection | undefined = undefined let ids: string[] = [] - const acceptFn = () => { + + const isRevert = opts?.mode === 'revert' + + // Apply this group and continue with remaining changes + const onApply = () => { this.applyGroup(group) this.clear() let newCodeWithRejects = '' @@ -196,9 +243,12 @@ export class AIChatEditorHandler { newCodeWithRejects += change.value } } - this.reviewAndApply(newCodeWithRejects) + this.reviewChanges(newCodeWithRejects, opts) } - const rejectFn = () => { + + // Discard this group and continue with remaining changes + const onDiscard = () => { + // This group was not applied (not reverted in revert mode) indicesOfRejectedLineChanges.push(...group.changes.map((c) => c.diffIndex)) collection?.clear() this.editor.changeViewZones((acc) => { @@ -211,28 +261,41 @@ export class AIChatEditorHandler { this.finish() } } + + // In revert mode: Accept = keep current code, Reject = revert to targetCode + // In apply mode: Accept = apply changes, Reject = discard changes + const acceptFn = isRevert ? onDiscard : onApply + const rejectFn = isRevert ? onApply : onDiscard + const changes = group.changes.map((c, i) => { if (i === group.changes.length - 1) { return { ...c, - options: { ...(c.options ?? {}), review: { acceptFn, rejectFn } } + options: { + ...(c.options ?? {}), + review: { + acceptFn, + rejectFn + } + } } } else { return c } }) - if (!applyAll) { + if (!opts?.applyAll) { ;({ collection, ids } = await displayVisualChanges( 'editor-windmill-chat-style', this.editor, - changes + changes, + isRevert )) - this.decorationsCollections.push(collection) - this.viewZoneIds.push(...ids) + this.decorationsCollections.push(collection) + this.viewZoneIds.push(...ids) } } - if (applyAll) { + if (opts?.applyAll) { this.acceptAll() } } diff --git a/frontend/src/lib/components/copilot/shared.ts b/frontend/src/lib/components/copilot/shared.ts index 5830685866..ed37052298 100644 --- a/frontend/src/lib/components/copilot/shared.ts +++ b/frontend/src/lib/components/copilot/shared.ts @@ -3,49 +3,49 @@ import { type editor as meditor } from 'monaco-editor' export type VisualChange = | { - type: 'added_inline' - position: { - line: number - column: number - } - value: string - options?: { - greenHighlight?: boolean - } - } - | { - type: 'added_block' - position: { - afterLineNumber: number - } - value: string - options?: { - greenHighlight?: boolean - review?: { - acceptFn: () => void - rejectFn: () => void + type: 'added_inline' + position: { + line: number + column: number } - extraChanges?: VisualChange[] - } - } - | { - type: 'deleted' - range: { - startLine: number - startColumn: number - endLine: number - endColumn: number - } - options?: { - isWholeLine?: boolean - review?: { - acceptFn: () => void - rejectFn: () => void + value: string + options?: { + greenHighlight?: boolean } - } - } + } + | { + type: 'added_block' + position: { + afterLineNumber: number + } + value: string + options?: { + greenHighlight?: boolean + review?: { + acceptFn: () => void + rejectFn: () => void + } + extraChanges?: VisualChange[] + } + } + | { + type: 'deleted' + range: { + startLine: number + startColumn: number + endLine: number + endColumn: number + } + options?: { + isWholeLine?: boolean + review?: { + acceptFn: () => void + rejectFn: () => void + } + } + } -function applyMonacoStyles(targetEl: HTMLElement, greenHighlight?: boolean) { +function applyMonacoStyles(targetEl: HTMLElement, greenHighlight?: boolean, revertMode?: boolean) { const computedStyles = window.getComputedStyle( document.querySelector('.monaco-editor .view-lines')! ) @@ -57,7 +57,9 @@ function applyMonacoStyles(targetEl: HTMLElement, greenHighlight?: boolean) { whiteSpace: 'pre' }) if (greenHighlight) { - targetEl.style.backgroundColor = 'var(--vscode-diffEditor-insertedTextBackground)' + targetEl.style.backgroundColor = revertMode + ? 'var(--vscode-diffEditor-removedTextBackground)' + : 'var(--vscode-diffEditor-insertedTextBackground)' } } @@ -71,7 +73,10 @@ export function setGlobalCSS(id: string, cssCode: string) { styleTag.textContent = cssCode } -function addInlineGhostText(change: Extract) { +function addInlineGhostText( + change: Extract, + revertMode?: boolean +) { const cssId = createLongHash() const decoration = { range: { @@ -81,8 +86,13 @@ function addInlineGhostText(change: Extract void } extraChanges?: VisualChange[] - } + }, + revertMode?: boolean ) { const el = document.createElement('div') el.textContent = text @@ -172,7 +183,7 @@ async function addMultilineGhostText( const reviewButtons = getReviewButtons(editor, options.review.acceptFn, options.review.rejectFn) el.append(reviewButtons) } - applyMonacoStyles(el, options?.greenHighlight) + applyMonacoStyles(el, options?.greenHighlight, revertMode) const addZonePromise = new Promise((resolve, reject) => { editor?.changeViewZones((acc) => { const id = acc.addZone({ @@ -193,14 +204,15 @@ export let VISUAL_CHANGES_CSS = `.editor-ghost-text-green { background-color: va export async function displayVisualChanges( cssId: string, editor: meditor.IStandaloneCodeEditor, - visualChanges: VisualChange[] + visualChanges: VisualChange[], + revertMode?: boolean ) { let decorations: meditor.IModelDeltaDecoration[] = [] let css = '' let ids: string[] = [] for (const change of visualChanges) { if (change.type === 'added_inline') { - const { css: newCss, decoration } = addInlineGhostText(change) + const { css: newCss, decoration } = addInlineGhostText(change, revertMode) decorations.push(decoration) css += newCss } else if (change.type === 'deleted') { @@ -212,7 +224,7 @@ export async function displayVisualChanges( endColumn: change.range.endColumn }, options: { - className: 'editor-ghost-text-removed', + className: revertMode ? 'editor-ghost-text-green' : 'editor-ghost-text-removed', isWholeLine: change.options?.isWholeLine } } @@ -247,7 +259,8 @@ export async function displayVisualChanges( change.value, change.position.afterLineNumber, change.value.split('\n').length, // we know it won't end by \n - change.options + change.options, + revertMode ) ids.push(id) } diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 68ea11ed58..e8a5ee548d 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -487,7 +487,7 @@ additionalAssetsMap: flowGraphAssetsCtx?.val.additionalAssetsMap }, untrack(() => failureModule), - untrack(() => preprocessorModule), + preprocessorModule, eventHandler, success, $useDataflow, diff --git a/frontend/src/lib/components/settings/PremiumInfo.svelte b/frontend/src/lib/components/settings/PremiumInfo.svelte index 1ed4b2fb12..bd7a91a026 100644 --- a/frontend/src/lib/components/settings/PremiumInfo.svelte +++ b/frontend/src/lib/components/settings/PremiumInfo.svelte @@ -30,6 +30,8 @@ seatsFromExtraComps: number usedSeats: number owner: string + is_past_due: boolean + max_tolerated_executions?: number } | undefined = undefined const plans = { @@ -157,8 +159,13 @@ {#if premiumInfo?.status === 'past_due'}

- Your last invoice is unpaid. Please update your payment method in the customer portal to - prevent account downgrade and the interruption of your job executions. + {#if premiumInfo.max_tolerated_executions === undefined || premiumInfo.usage > premiumInfo.max_tolerated_executions} + Your last invoice is unpaid, you cannot run any more jobs. Please update your payment + method in the customer portal to continue running jobs. + {:else} + Your last invoice is unpaid. Please update your payment method in the customer portal to + prevent the interruption of your job executions. + {/if}

{/if} diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index 0969a4df3f..d03689e3d1 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -11,7 +11,7 @@ UserService, WorkspaceService } from '$lib/gen' - import { capitalize, classNames, getModifierKey } from '$lib/utils' + import { capitalize, classNames, getModifierKey, sendUserToast } from '$lib/utils' import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte' import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte' import CriticalAlertModal from '$lib/components/sidebar/CriticalAlertModal.svelte' @@ -305,6 +305,30 @@ muteSettings = { global: g_muted, workspace: ws_muted } } + + async function checkTeamPlanStatus(workspace: string) { + const premiumInfo = await WorkspaceService.getPremiumInfo({ + workspace, + skipSubscriptionFetch: true // won't load subscription status from stripe but only the past due status from db + }) + if (premiumInfo.is_past_due) { + if ( + premiumInfo.max_tolerated_executions === undefined || + (premiumInfo.usage ?? 0) > premiumInfo.max_tolerated_executions + ) { + sendUserToast( + 'Your last invoice is unpaid, you cannot run any more jobs. Please update your payment method in the workspace settings to continue running jobs.', + true + ) + } else { + sendUserToast( + 'Your last invoice is unpaid. Please update your payment method in the workspace settings to prevent the interruption of your job executions.', + true + ) + } + } + } + $effect(() => { $page.url && userSettings != undefined && untrack(() => onQueryChangeUserSettings()) }) @@ -350,6 +374,15 @@ mountModal = false } }) + + $effect(() => { + if (isCloudHosted()) { + const workspace = $workspaceStore + if (workspace) { + checkTeamPlanStatus(workspace) + } + } + }) diff --git a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte index 94c2d380b6..cb42658b10 100644 --- a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte @@ -355,12 +355,14 @@ if (job?.job_kind == 'script') { id = await JobService.runScriptByHash({ ...commonArgs, - hash: job.script_hash! + hash: job.script_hash!, + skipPreprocessor: true }) } else { id = await JobService.runFlowByPath({ ...commonArgs, - path: job.script_path! + path: job.script_path!, + skipPreprocessor: true }) } diff --git a/lsp/Dockerfile b/lsp/Dockerfile index df34a4549a..de599e45ec 100644 --- a/lsp/Dockerfile +++ b/lsp/Dockerfile @@ -9,7 +9,7 @@ ENV PIPENV_VENV_IN_PROJECT=1 ENV XDG_CACHE_HOME=/pyls/.cache RUN apt-get update \ - && apt-get install -y shellcheck wget \ + && apt-get install -y shellcheck wget git ca-certificates \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip install pipenv @@ -21,13 +21,13 @@ RUN set -eux; \ url=; \ case "$arch" in \ 'amd64') \ - targz='go1.22.5.linux-amd64.tar.gz'; \ + targz='go1.25.0.linux-amd64.tar.gz'; \ ;; \ 'arm64') \ - targz='go1.22.5.linux-arm64.tar.gz'; \ + targz='go1.25.0.linux-arm64.tar.gz'; \ ;; \ 'armhf') \ - targz='go1.22.5.linux-armv6l.tar.gz'; \ + targz='go1.25.0.linux-armv6l.tar.gz'; \ ;; \ *) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \ esac; \ @@ -59,4 +59,4 @@ RUN chmod -R a+rX /usr/local && \ EXPOSE 3001 -CMD ["sh", "-c", "if [ -d /root/.cache ]; then export XDG_CACHE_HOME=/root/.cache && cp -r /pyls/.cache /root/.cache; fi && python3 pyls_launcher.py"] +CMD ["sh", "-c", "if [ -n \"$NETRC\" ]; then echo \"$NETRC\" > /root/.netrc && chmod 600 /root/.netrc; fi && if [ -d /root/.cache ]; then export XDG_CACHE_HOME=/root/.cache && cp -r /pyls/.cache /root/.cache; fi && python3 pyls_launcher.py"] diff --git a/lsp/Pipfile b/lsp/Pipfile index 5fe27bbf38..001b2c6440 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.536.0" -wmill_pg = ">=1.536.0" +wmill = ">=1.537.0" +wmill_pg = ">=1.537.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/lsp/pyls_launcher.py b/lsp/pyls_launcher.py index 614d47294e..8b19ca5ef5 100644 --- a/lsp/pyls_launcher.py +++ b/lsp/pyls_launcher.py @@ -97,7 +97,7 @@ if __name__ == "__main__": go_mod_path = os.path.join(monaco_path, "go.mod") if not os.path.exists(go_mod_path): f = open(go_mod_path, "w") - f.write("module mymod\ngo 1.22") + f.write("module mymod\ngo 1.25") f.close() port = int(os.environ.get("PORT", "3001")) app = web.Application( diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 653590ef27..aec8eecbc7 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.536.0 + version: 1.537.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 68a5fd2cc3..114e9b0818 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.536.0' + ModuleVersion = '1.537.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 459257aee7..73633134e4 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.536.0" +version = "1.537.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index f24f2a35ee..36e7f7dc9a 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -1478,16 +1478,16 @@ def task(*args, **kwargs): params = {} if tag is not None: params["tag"] = tag - r = _client.post( + w_as_code_response = _client.post( f"/w/{w_id}/jobs/run/workflow_as_code/{job_id}/{f_name}", json={"args": json}, params=params, ) - job_id = r.text + job_id = w_as_code_response.text print(f"Executing task {func.__name__} on job {job_id}") - r = _client.wait_job(job_id) + job_result = _client.wait_job(job_id) print(f"Task {func.__name__} ({job_id}) completed") - return r + return job_result return inner diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index a515d6d8ae..ee75201d9b 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.536.0" +version = "1.537.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 49fd1c2814..3126b9087e 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.536.0", + "version": "1.537.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 6018f1935e..674c770bc6 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.536.0", + "version": "1.537.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 3fe765ee77..a2da630536 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.536.0 +1.537.0