Compare commits
12 Commits
rf/schemaT
...
v1.537.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89e20f5197 | ||
|
|
641d5651c5 | ||
|
|
e2b344ed02 | ||
|
|
fb25e413f8 | ||
|
|
e28c9df60f | ||
|
|
7a1c28f6d7 | ||
|
|
6f4bdc0148 | ||
|
|
4973c860f2 | ||
|
|
2ce11cea3b | ||
|
|
0cc11b3f31 | ||
|
|
73a3f4cc73 | ||
|
|
006f32602c |
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [1.537.1](https://github.com/windmill-labs/windmill/compare/v1.537.0...v1.537.1) (2025-08-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix error handling of pre-processor steps ([641d565](https://github.com/windmill-labs/windmill/commit/641d5651c5b6fa898e4fc8ad29a8508f9400c6d8))
|
||||
* fix preprocessor not displaying immediately on addition ([e28c9df](https://github.com/windmill-labs/windmill/commit/e28c9df60f20b70a4dcef802839750bb843b91a4))
|
||||
* skipPreprocessor on re-running job immedaitely from UI ([e2b344e](https://github.com/windmill-labs/windmill/commit/e2b344ed02b02972366a011c3e873e0f43ab2843))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
34
backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json
generated
Normal file
34
backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json
generated
Normal file
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
58
backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json
generated
Normal file
58
backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json
generated
Normal file
@@ -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"
|
||||
}
|
||||
17
backend/.sqlx/query-ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68.json
generated
Normal file
17
backend/.sqlx/query-ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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, $4, 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",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
103
backend/Cargo.lock
generated
103
backend/Cargo.lock
generated
@@ -1018,9 +1018,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-json"
|
||||
version = "0.61.4"
|
||||
version = "0.61.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9"
|
||||
checksum = "eaa31b350998e703e9826b2104dd6f63be0508666e1aba88137af060e8944047"
|
||||
dependencies = [
|
||||
"aws-smithy-types",
|
||||
]
|
||||
@@ -2105,9 +2105,9 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "comfy-table"
|
||||
version = "7.1.4"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a"
|
||||
checksum = "3f8e18d0dca9578507f13f9803add0df13362b02c501c1c17734f0dbb52eaf0b"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.2.1",
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -9877,9 +9877,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
|
||||
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
@@ -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.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -15183,7 +15183,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15300,7 +15300,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -15315,7 +15315,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -15328,7 +15328,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -15347,7 +15347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15427,7 +15427,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -15442,7 +15442,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -15466,7 +15466,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -15478,7 +15478,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -15487,7 +15487,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15499,7 +15499,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15511,7 +15511,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -15523,7 +15523,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15535,7 +15535,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15547,7 +15547,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -15558,7 +15558,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15569,7 +15569,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15581,7 +15581,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15604,7 +15604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15618,7 +15618,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -15635,7 +15635,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15649,7 +15649,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15667,7 +15667,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.16",
|
||||
@@ -15692,7 +15692,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15702,7 +15702,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15735,7 +15735,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -15745,7 +15745,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -33,7 +33,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
6396854336ae27fb14ccb792d80c31ff614b2afa
|
||||
d16e52d570f10dfdabb04a9061fe7ebdbf5a2aa1
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
DROP TRIGGER script_insert_trigger ON script;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add up migration script here
|
||||
CREATE TRIGGER script_insert_trigger
|
||||
AFTER INSERT ON script
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.lock IS NOT NULL)
|
||||
EXECUTE FUNCTION notify_runnable_version_change('script');
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
|
||||
@@ -4679,30 +4679,42 @@ mod job_payload {
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dependencies_payload(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await;
|
||||
let port = server.addr.port();
|
||||
|
||||
let test = || async {
|
||||
let result = RunJob::from(JobPayload::Dependencies {
|
||||
path: "f/system/hello".to_string(),
|
||||
hash: ScriptHash(123412),
|
||||
language: ScriptLang::Deno,
|
||||
dedicated_worker: None,
|
||||
})
|
||||
.run_until_complete(&db, port)
|
||||
.await
|
||||
.json_result()
|
||||
.unwrap();
|
||||
let result = RunJob::from(JobPayload::Dependencies {
|
||||
path: "f/system/hello".to_string(),
|
||||
hash: ScriptHash(123412),
|
||||
language: ScriptLang::Deno,
|
||||
dedicated_worker: None,
|
||||
})
|
||||
.run_until_complete(&db, port)
|
||||
.await
|
||||
.json_result()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
result.get("status").unwrap(),
|
||||
&json!("Successful lock file generation")
|
||||
);
|
||||
};
|
||||
test_for_versions(VERSION_FLAGS.iter().cloned(), test).await;
|
||||
assert_eq!(
|
||||
result.get("status").unwrap(),
|
||||
&json!("Successful lock file generation")
|
||||
);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dependencies_payload_min_1_427(db: Pool<Postgres>) {
|
||||
*MIN_VERSION_IS_AT_LEAST_1_427.write().await = true;
|
||||
test_dependencies_payload(db).await;
|
||||
}
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dependencies_payload_min_1_432(db: Pool<Postgres>) {
|
||||
*MIN_VERSION_IS_AT_LEAST_1_432.write().await = true;
|
||||
test_dependencies_payload(db).await;
|
||||
}
|
||||
#[sqlx::test(fixtures("base", "hello"))]
|
||||
async fn test_dependencies_payload_min_1_440(db: Pool<Postgres>) {
|
||||
*MIN_VERSION_IS_AT_LEAST_1_440.write().await = true;
|
||||
test_dependencies_payload(db).await;
|
||||
}
|
||||
|
||||
// Just test that deploying a flow work as expected.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.536.0
|
||||
version: 1.537.1
|
||||
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:
|
||||
|
||||
@@ -33,11 +33,7 @@ use serde_json::json;
|
||||
use serde_json::value::RawValue;
|
||||
use sql_builder::prelude::*;
|
||||
use sqlx::{FromRow, Postgres, Transaction};
|
||||
use std::{
|
||||
collections::{hash_map::DefaultHasher, HashMap},
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_worker::process_relative_imports;
|
||||
@@ -45,6 +41,7 @@ use windmill_worker::process_relative_imports;
|
||||
use windmill_common::{
|
||||
assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType},
|
||||
error::to_anyhow,
|
||||
scripts::hash_script,
|
||||
utils::WarnAfterExt,
|
||||
worker::CLOUD_HOSTED,
|
||||
};
|
||||
@@ -379,12 +376,6 @@ async fn get_top_hub_scripts(
|
||||
Ok::<_, Error>((status_code, headers, response))
|
||||
}
|
||||
|
||||
fn hash_script(ns: &NewScript) -> i64 {
|
||||
let mut dh = DefaultHasher::new();
|
||||
ns.hash(&mut dh);
|
||||
dh.finish() as i64
|
||||
}
|
||||
|
||||
async fn create_snapshot_script(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -415,7 +415,9 @@ async fn is_premium(
|
||||
) -> JsonResult<bool> {
|
||||
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))
|
||||
|
||||
@@ -649,3 +649,9 @@ pub struct HubScript {
|
||||
pub schema: Box<serde_json::value::RawValue>,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
pub fn hash_script(ns: &NewScript) -> i64 {
|
||||
let mut dh = std::hash::DefaultHasher::new();
|
||||
ns.hash(&mut dh);
|
||||
dh.finish() as i64
|
||||
}
|
||||
|
||||
@@ -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<i32>,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref IS_PREMIUM_CACHE: Cache<String, bool> = Cache::new(5000);
|
||||
pub static ref TEAM_PLAN_CACHE: Cache<String, TeamPlanStatus> = 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)]
|
||||
|
||||
@@ -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."
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,11 +680,12 @@ pub async fn resolve_job_timeout(
|
||||
let mut warn_msg: Option<String> = 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;
|
||||
|
||||
|
||||
@@ -520,7 +520,7 @@ function get_from_env(name) {{
|
||||
.map(|a| { format!("let {a} = get_from_env(\"{a}\");\n",) })
|
||||
.join(""),
|
||||
if expr.contains("error") && transform_context.contains(&"previous_result".to_string()) {
|
||||
"let error = previous_result.error"
|
||||
"let error = previous_result.error;"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
|
||||
@@ -63,7 +63,9 @@ mod worker_lockfiles;
|
||||
mod worker_utils;
|
||||
|
||||
pub use worker::*;
|
||||
pub use worker_lockfiles::process_relative_imports;
|
||||
pub use worker_lockfiles::{
|
||||
process_relative_imports, trigger_dependents_to_recompute_dependencies,
|
||||
};
|
||||
|
||||
pub use result_processor::handle_job_error;
|
||||
|
||||
|
||||
@@ -1027,7 +1027,7 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
.ok_or_else(|| Error::internal_err(format!("requiring flow to be in the queue")))?;
|
||||
tx.commit().await?;
|
||||
|
||||
if matches!(module_step, Step::PreprocessorStep) {
|
||||
if matches!(module_step, Step::PreprocessorStep) && success {
|
||||
let tag_and_concurrency_key = get_tag_and_concurrency(&flow, db).await;
|
||||
let require_args = tag_and_concurrency_key.as_ref().is_some_and(|x| {
|
||||
x.tag.as_ref().is_some_and(|t| t.contains("$args"))
|
||||
@@ -1144,9 +1144,7 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
"error while updating args in preprocessing step: {e:#}"
|
||||
))
|
||||
})?;
|
||||
if success {
|
||||
return Ok(UpdateFlowStatusAfterJobCompletion::PreprocessingStep);
|
||||
}
|
||||
return Ok(UpdateFlowStatusAfterJobCompletion::PreprocessingStep);
|
||||
}
|
||||
|
||||
let job_root = flow_job
|
||||
|
||||
@@ -17,7 +17,7 @@ use windmill_common::error::Result;
|
||||
use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeId};
|
||||
use windmill_common::get_latest_deployed_hash_for_path;
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::scripts::ScriptHash;
|
||||
use windmill_common::scripts::{hash_script, NewScript, ScriptHash};
|
||||
#[cfg(feature = "python")]
|
||||
use windmill_common::worker::PythonAnnotations;
|
||||
use windmill_common::worker::{to_raw_value, to_raw_value_owned, write_file, Connection};
|
||||
@@ -38,6 +38,11 @@ use windmill_parser_py_imports::parse_relative_imports;
|
||||
use windmill_parser_ts::parse_expr_for_imports;
|
||||
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob, PushIsolationLevel};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
// TODO: To be removed in future versions
|
||||
static ref WMDEBUG_NO_HASH_CHANGE_ON_DJ: bool = std::env::var("WMDEBUG_NO_HASH_CHANGE_ON_DJ").is_ok();
|
||||
}
|
||||
|
||||
use crate::common::OccupancyMetrics;
|
||||
use crate::csharp_executor::generate_nuget_lockfile;
|
||||
|
||||
@@ -251,6 +256,7 @@ pub async fn handle_dependency_job(
|
||||
.is_some_and(|y| y.to_string().as_str() == "true")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let npm_mode = if job
|
||||
.script_lang
|
||||
.as_ref()
|
||||
@@ -338,30 +344,140 @@ pub async fn handle_dependency_job(
|
||||
));
|
||||
}
|
||||
|
||||
let hash = job.runnable_id.unwrap_or(ScriptHash(0));
|
||||
let current_hash = job.runnable_id.unwrap_or(ScriptHash(0));
|
||||
let w_id = &job.workspace_id;
|
||||
sqlx::query!(
|
||||
"UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
|
||||
&content,
|
||||
&hash.0,
|
||||
w_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
// `lock` has been updated; invalidate the cache.
|
||||
cache::script::invalidate(hash);
|
||||
|
||||
let (deployment_message, parent_path) =
|
||||
get_deployment_msg_and_parent_path_from_args(job.args.clone());
|
||||
|
||||
let script_info = sqlx::query_as::<_, windmill_common::scripts::Script>(
|
||||
"SELECT * FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
)
|
||||
.bind(¤t_hash.0)
|
||||
.bind(w_id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
|
||||
// DependencyJob can be triggered only from 2 places:
|
||||
// 1. create_script function in windmill-api/src/scripts.rs
|
||||
// 2. trigger_dependents_to_recompute_dependencies (in this file)
|
||||
//
|
||||
// First will **always** produce script with null in `lock`
|
||||
// where Second will **always** do with lock being not null
|
||||
let deployed_hash = if script_info.lock.is_some() && !*WMDEBUG_NO_HASH_CHANGE_ON_DJ {
|
||||
let mut tx = db.begin().await?;
|
||||
// This entire section exists to solve following problem:
|
||||
//
|
||||
// 2 workers, one script that depend on another in python
|
||||
// run the original script on both workers
|
||||
// you update the dependenecy of a relative import,
|
||||
// run it again until you ran it on both, normally it should fail on one of those
|
||||
//
|
||||
// It happens because every worker has cached their own script versions.
|
||||
// However usual dependency job does not update hash of the script (and cache is keyed by the hash).
|
||||
// This logical branch will create new script which will update the hash and automatically invalidate cache.
|
||||
//
|
||||
// IMPORTANT: This will **only** be triggered by another DependencyJob. It will never be triggered by script (re)deployement
|
||||
|
||||
let ns = NewScript {
|
||||
path: script_info.path,
|
||||
parent_hash: Some(current_hash),
|
||||
summary: script_info.summary,
|
||||
description: script_info.description,
|
||||
content: script_info.content,
|
||||
schema: script_info.schema,
|
||||
is_template: Some(script_info.is_template),
|
||||
// TODO: Make it either None everywhere (particularely when raw reqs are calculated)
|
||||
// Or handle this case and conditionally make Some (only with raw reqs)
|
||||
lock: None,
|
||||
language: script_info.language,
|
||||
kind: Some(script_info.kind),
|
||||
tag: script_info.tag,
|
||||
draft_only: script_info.draft_only,
|
||||
envs: script_info.envs,
|
||||
concurrent_limit: script_info.concurrent_limit,
|
||||
concurrency_time_window_s: script_info.concurrency_time_window_s,
|
||||
cache_ttl: script_info.cache_ttl,
|
||||
dedicated_worker: script_info.dedicated_worker,
|
||||
ws_error_handler_muted: script_info.ws_error_handler_muted,
|
||||
priority: script_info.priority,
|
||||
timeout: script_info.timeout,
|
||||
delete_after_use: script_info.delete_after_use,
|
||||
restart_unless_cancelled: script_info.restart_unless_cancelled,
|
||||
deployment_message: deployment_message.clone(),
|
||||
concurrency_key: script_info.concurrency_key,
|
||||
visible_to_runner_only: script_info.visible_to_runner_only,
|
||||
no_main_func: script_info.no_main_func,
|
||||
codebase: script_info.codebase,
|
||||
has_preprocessor: script_info.has_preprocessor,
|
||||
on_behalf_of_email: script_info.on_behalf_of_email,
|
||||
assets: script_info.assets,
|
||||
};
|
||||
|
||||
let new_hash = hash_script(&ns);
|
||||
|
||||
sqlx::query!("
|
||||
INSERT INTO script
|
||||
(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)
|
||||
|
||||
SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, \
|
||||
content, created_by, schema, is_template, extra_perms, $4, 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
|
||||
|
||||
FROM script WHERE hash = $2 AND workspace_id = $3;
|
||||
",
|
||||
new_hash, current_hash.0, w_id, &content).execute(db).await?;
|
||||
|
||||
// Archive current
|
||||
sqlx::query!(
|
||||
"UPDATE script SET archived = true WHERE hash = $1 AND workspace_id = $2",
|
||||
current_hash.0,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
ScriptHash(new_hash)
|
||||
} else {
|
||||
// We do not create new row for this update
|
||||
// That means we can keep current hash and just update lock
|
||||
sqlx::query!(
|
||||
"UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
|
||||
&content,
|
||||
¤t_hash.0,
|
||||
w_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
// `lock` has been updated; invalidate the cache.
|
||||
// Since only worker that ran this Dependency Job has the cache
|
||||
// we do not need to think about invalidating cache for other workers.
|
||||
cache::script::invalidate(current_hash);
|
||||
|
||||
if *WMDEBUG_NO_HASH_CHANGE_ON_DJ {
|
||||
tracing::warn!("WMDEBUG_NO_HASH_CHANGE_ON_DJ usually should not be used. Behavior might be unstable. Please contact Windmill Team for support.")
|
||||
}
|
||||
|
||||
current_hash
|
||||
};
|
||||
|
||||
if let Err(e) = handle_deployment_metadata(
|
||||
&job.permissioned_as_email,
|
||||
&job.created_by,
|
||||
&db,
|
||||
&w_id,
|
||||
DeployedObject::Script {
|
||||
hash,
|
||||
hash: deployed_hash,
|
||||
path: script_path.to_string(),
|
||||
parent_path: parent_path.clone(),
|
||||
},
|
||||
@@ -502,7 +618,7 @@ pub async fn process_relative_imports(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn trigger_dependents_to_recompute_dependencies(
|
||||
pub async fn trigger_dependents_to_recompute_dependencies(
|
||||
w_id: &str,
|
||||
script_path: &str,
|
||||
deployment_message: Option<String>,
|
||||
@@ -535,6 +651,10 @@ async fn trigger_dependents_to_recompute_dependencies(
|
||||
args.insert("deployment_message".to_string(), to_raw_value(&dm));
|
||||
}
|
||||
if let Some(ref p_path) = parent_path {
|
||||
// NOTE:
|
||||
// it's not used but maybe one day it will be useful. allows more back-compatibility for the workers when we need it
|
||||
// also very useful for debugging/observability
|
||||
// it adds that information to the job args so you can see from the runs page
|
||||
args.insert("common_dependency_path".to_string(), to_raw_value(&p_path));
|
||||
}
|
||||
|
||||
@@ -542,6 +662,7 @@ async fn trigger_dependents_to_recompute_dependencies(
|
||||
"already_visited".to_string(),
|
||||
to_raw_value(&already_visited),
|
||||
);
|
||||
|
||||
let kind = s.importer_kind.clone().unwrap_or_default();
|
||||
let job_payload = if kind == "script" {
|
||||
let r = get_latest_deployed_hash_for_path(db, w_id, s.importer_path.as_str()).await;
|
||||
@@ -577,7 +698,7 @@ async fn trigger_dependents_to_recompute_dependencies(
|
||||
Ok(Some(version)) => JobPayload::FlowDependencies {
|
||||
path: s.importer_path.clone(),
|
||||
dedicated_worker: None,
|
||||
version: version,
|
||||
version,
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::error!(
|
||||
|
||||
@@ -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.1";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -68,7 +68,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.536.0";
|
||||
export const VERSION = "1.537.1";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.536.0",
|
||||
"version": "1.537.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.536.0",
|
||||
"version": "1.537.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.536.0",
|
||||
"version": "1.537.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte'
|
||||
import CollapseLink from './CollapseLink.svelte'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
@@ -32,8 +33,12 @@
|
||||
}
|
||||
}
|
||||
let enabled = $derived(value != undefined)
|
||||
$effect(() => {
|
||||
changeDomain(value?.['domain'], value?.['custom'])
|
||||
let lastValues = { domain: undefined, custom: undefined }
|
||||
$effect.pre(() => {
|
||||
if (value?.['domain'] != lastValues.domain || value?.['custom'] != lastValues.custom) {
|
||||
lastValues = { domain: value?.['domain'], custom: value?.['custom'] }
|
||||
untrack(() => changeDomain(value?.['domain'], value?.['custom']))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -523,6 +523,7 @@
|
||||
<div class="shrink-0">
|
||||
<Toggle
|
||||
bind:checked={jsonView}
|
||||
label="JSON View"
|
||||
size="xs"
|
||||
options={{
|
||||
right: 'JSON editor',
|
||||
@@ -649,7 +650,7 @@
|
||||
bind:order={schema.properties[argName].order}
|
||||
{isFlowInput}
|
||||
{isAppInput}
|
||||
onChange={() => {
|
||||
on:change={() => {
|
||||
schema = $state.snapshot(schema)
|
||||
dispatch('change', schema)
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
<GlobalReviewButtons
|
||||
onAcceptAll={() => {
|
||||
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}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte'
|
||||
import CollapseLink from './CollapseLink.svelte'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
@@ -12,6 +13,8 @@
|
||||
|
||||
let { value = $bindable() }: Props = $props()
|
||||
|
||||
let lastValues = { domain: undefined, custom: undefined }
|
||||
|
||||
function changeDomain(domain, custom) {
|
||||
if (value) {
|
||||
let baseUrl = custom ? `https://${domain}` : `https://${domain}.okta.com`
|
||||
@@ -32,8 +35,11 @@
|
||||
}
|
||||
}
|
||||
let enabled = $derived(value != undefined)
|
||||
$effect(() => {
|
||||
changeDomain(value?.['domain'], value?.['custom'])
|
||||
$effect.pre(() => {
|
||||
if (value?.['domain'] != lastValues.domain || value?.['custom'] != lastValues.custom) {
|
||||
lastValues = { domain: value?.['domain'], custom: value?.['custom'] }
|
||||
untrack(() => changeDomain(value?.['domain'], value?.['custom']))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import type { EnumType } from '$lib/common'
|
||||
import { computeKind } from '$lib/utils'
|
||||
import { untrack } from 'svelte'
|
||||
import Label from './Label.svelte'
|
||||
import ResourceTypePicker from './ResourceTypePicker.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
@@ -28,7 +29,6 @@
|
||||
enumLabels?: Record<string, string> | undefined
|
||||
overrideAllowKindChange?: boolean
|
||||
originalType?: string | undefined
|
||||
onChange?: () => void
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -45,8 +45,7 @@
|
||||
dateFormat = $bindable(),
|
||||
enumLabels = $bindable(undefined),
|
||||
overrideAllowKindChange = true,
|
||||
originalType = undefined,
|
||||
onChange = () => {}
|
||||
originalType = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let kind: 'none' | 'pattern' | 'enum' | 'resource' | 'format' | 'base64' | 'date-time' = $state(
|
||||
@@ -83,22 +82,18 @@
|
||||
['Pattern', 'pattern']
|
||||
]
|
||||
|
||||
$effect.pre(() => {
|
||||
run(() => {
|
||||
format =
|
||||
kind == 'resource'
|
||||
? resource != undefined
|
||||
? `resource-${resource}`
|
||||
: 'resource'
|
||||
: untrack(() => format)
|
||||
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : format
|
||||
})
|
||||
$effect.pre(() => {
|
||||
run(() => {
|
||||
pattern = patternStr == '' ? undefined : patternStr
|
||||
})
|
||||
$effect.pre(() => {
|
||||
run(() => {
|
||||
contentEncoding = kind == 'base64' ? 'base64' : undefined
|
||||
})
|
||||
|
||||
$effect.pre(() => {
|
||||
run(() => {
|
||||
if (format == 'email') {
|
||||
pattern = '^[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,63}$'
|
||||
}
|
||||
@@ -374,7 +369,6 @@
|
||||
options={{ right: 'Is Password' }}
|
||||
checked={password}
|
||||
on:change={(e) => {
|
||||
onChange?.()
|
||||
if (e.detail) {
|
||||
password = true
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { createBubbler, stopPropagation } from 'svelte/legacy'
|
||||
|
||||
const bubble = createBubbler()
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -9,62 +6,35 @@
|
||||
import { AlertTriangle } from 'lucide-svelte'
|
||||
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
|
||||
|
||||
export let options: {
|
||||
left?: string
|
||||
leftTooltip?: string
|
||||
right?: string
|
||||
rightTooltip?: string
|
||||
rightDocumentationLink?: string
|
||||
} = {}
|
||||
export let checked: boolean = false
|
||||
export let disabled = false
|
||||
export let textClass = ''
|
||||
export let textStyle = ''
|
||||
export let color: 'blue' | 'red' | 'nord' = 'blue'
|
||||
export let id = (Math.random() + 1).toString(36).substring(10)
|
||||
export let lightMode: boolean = false
|
||||
export let eeOnly: boolean = false
|
||||
export let aiId: string | undefined = undefined
|
||||
export let aiDescription: string | undefined = undefined
|
||||
|
||||
export let size: 'sm' | 'xs' | '2xs' | '2sm' = 'sm'
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: boolean }>()
|
||||
|
||||
interface Props {
|
||||
options?: {
|
||||
left?: string
|
||||
leftTooltip?: string
|
||||
right?: string
|
||||
rightTooltip?: string
|
||||
rightDocumentationLink?: string
|
||||
}
|
||||
checked?: boolean | undefined
|
||||
disabled?: boolean
|
||||
textClass?: string
|
||||
textStyle?: string
|
||||
color?: 'blue' | 'red' | 'nord'
|
||||
id?: any
|
||||
lightMode?: boolean
|
||||
eeOnly?: boolean
|
||||
aiId?: string | undefined
|
||||
aiDescription?: string | undefined
|
||||
size?: 'sm' | 'xs' | '2xs' | '2sm'
|
||||
class?: string | undefined
|
||||
textDisabled?: boolean
|
||||
right?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
options = {},
|
||||
checked = $bindable(undefined),
|
||||
disabled = false,
|
||||
textClass = '',
|
||||
textStyle = '',
|
||||
color = 'blue',
|
||||
id = (Math.random() + 1).toString(36).substring(10),
|
||||
lightMode = false,
|
||||
eeOnly = false,
|
||||
aiId = undefined,
|
||||
aiDescription = undefined,
|
||||
size = 'sm',
|
||||
class: clazz = undefined,
|
||||
textDisabled = false,
|
||||
right
|
||||
}: Props = $props()
|
||||
|
||||
$effect.pre(() => {
|
||||
if (checked == undefined) {
|
||||
checked = false
|
||||
}
|
||||
})
|
||||
|
||||
const bothOptions = Boolean(options.left) && Boolean(options.right)
|
||||
|
||||
export let textDisabled = false
|
||||
</script>
|
||||
|
||||
<label
|
||||
for={id}
|
||||
class="{clazz || ''} z-auto flex flex-row items-center duration-50 {disabled
|
||||
class="{$$props.class || ''} z-auto flex flex-row items-center duration-50 {disabled
|
||||
? 'grayscale opacity-50'
|
||||
: 'cursor-pointer'}"
|
||||
>
|
||||
@@ -85,11 +55,11 @@
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="relative"
|
||||
onclick={stopPropagation(bubble('click'))}
|
||||
on:click|stopPropagation
|
||||
use:triggerableByAI={{
|
||||
id: aiId,
|
||||
description: aiDescription,
|
||||
@@ -99,16 +69,16 @@
|
||||
}}
|
||||
>
|
||||
<input
|
||||
onfocus={bubble('focus')}
|
||||
onclick={bubble('click')}
|
||||
on:focus
|
||||
on:click
|
||||
{disabled}
|
||||
type="checkbox"
|
||||
{id}
|
||||
class="sr-only peer"
|
||||
bind:checked
|
||||
onchange={stopPropagation((e) => {
|
||||
on:change|stopPropagation={(e) => {
|
||||
dispatch('change', checked)
|
||||
})}
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class={classNames(
|
||||
@@ -146,7 +116,7 @@
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{@render right?.()}
|
||||
<slot name="right" />
|
||||
</label>
|
||||
{#if eeOnly && disabled}
|
||||
<span class="inline-flex text-xs items-center gap-1 !text-yellow-500 whitespace-nowrap ml-8">
|
||||
|
||||
@@ -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,21 +93,10 @@
|
||||
hasDiff: () => {
|
||||
return Object.keys(affectedModules).length > 0
|
||||
},
|
||||
acceptAllModuleActions: () => {
|
||||
for (const [id, affectedModule] of Object.entries(affectedModules)) {
|
||||
if (affectedModule.action === 'removed') {
|
||||
deleteStep(id)
|
||||
}
|
||||
// Hide diff editor if the module is a rawscript
|
||||
if (
|
||||
affectedModule.action === 'modified' &&
|
||||
$currentEditor?.type === 'script' &&
|
||||
$currentEditor.stepId === id
|
||||
) {
|
||||
$currentEditor.hideDiffMode()
|
||||
}
|
||||
acceptAllModuleActions() {
|
||||
for (const id of Object.keys(affectedModules)) {
|
||||
this.acceptModuleAction(id)
|
||||
}
|
||||
affectedModules = {}
|
||||
},
|
||||
rejectAllModuleActions() {
|
||||
for (const id of Object.keys(affectedModules)) {
|
||||
@@ -189,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()
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(newModule).forEach((k) => delete newModule[k])
|
||||
@@ -206,6 +198,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
|
||||
@@ -614,43 +618,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()
|
||||
}
|
||||
}
|
||||
])
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -24,6 +24,12 @@ export class AIChatEditorHandler {
|
||||
reviewingChanges: Writable<boolean> = 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VisualChange, { type: 'added_inline' }>) {
|
||||
function addInlineGhostText(
|
||||
change: Extract<VisualChange, { type: 'added_inline' }>,
|
||||
revertMode?: boolean
|
||||
) {
|
||||
const cssId = createLongHash()
|
||||
const decoration = {
|
||||
range: {
|
||||
@@ -81,8 +86,13 @@ function addInlineGhostText(change: Extract<VisualChange, { type: 'added_inline'
|
||||
endColumn: change.position.column + change.value.length
|
||||
},
|
||||
options: {
|
||||
beforeContentClassName: `editor-ghost-text editor-ghost-text-content-${cssId} ${change.options?.greenHighlight ? 'editor-ghost-text-green' : ''
|
||||
}`
|
||||
beforeContentClassName: `editor-ghost-text editor-ghost-text-content-${cssId} ${
|
||||
change.options?.greenHighlight
|
||||
? revertMode
|
||||
? 'editor-ghost-text-removed'
|
||||
: 'editor-ghost-text-green'
|
||||
: ''
|
||||
}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +173,8 @@ async function addMultilineGhostText(
|
||||
rejectFn: () => 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<string>((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)
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@
|
||||
additionalAssetsMap: flowGraphAssetsCtx?.val.additionalAssetsMap
|
||||
},
|
||||
untrack(() => failureModule),
|
||||
untrack(() => preprocessorModule),
|
||||
preprocessorModule,
|
||||
eventHandler,
|
||||
success,
|
||||
$useDataflow,
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
|
||||
import { onMount, untrack } from 'svelte'
|
||||
import { createEventDispatcher, onMount, untrack } from 'svelte'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
|
||||
interface Props {
|
||||
description?: string
|
||||
@@ -44,7 +45,6 @@
|
||||
| undefined
|
||||
typeeditor?: import('svelte').Snippet
|
||||
children?: import('svelte').Snippet
|
||||
onChange?: () => void
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -66,8 +66,7 @@
|
||||
order = $bindable(),
|
||||
itemsType = $bindable(undefined),
|
||||
typeeditor,
|
||||
children,
|
||||
onChange = undefined
|
||||
children
|
||||
}: Props = $props()
|
||||
|
||||
$effect.pre(() => {
|
||||
@@ -76,6 +75,8 @@
|
||||
}
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
|
||||
let el: HTMLTextAreaElement | undefined = undefined
|
||||
|
||||
let oneOfSelected: string | undefined = $state(
|
||||
@@ -139,8 +140,7 @@
|
||||
if (!deepEqual(extra, initialExtra)) {
|
||||
initialExtra = structuredClone($state.snapshot(extra))
|
||||
console.debug('property content updated')
|
||||
|
||||
onChange?.()
|
||||
dispatchIfMounted('change')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
order
|
||||
}
|
||||
console.debug('property schema updated')
|
||||
onChange?.()
|
||||
dispatchIfMounted('change')
|
||||
}
|
||||
}
|
||||
$effect(() => {
|
||||
@@ -177,7 +177,7 @@
|
||||
rows="2"
|
||||
bind:value={description}
|
||||
onkeydown={onKeyDown}
|
||||
onchange={onChange}
|
||||
onchange={() => dispatch('change')}
|
||||
placeholder="Field description"
|
||||
></textarea>
|
||||
</Label>
|
||||
@@ -188,7 +188,7 @@
|
||||
{/snippet}
|
||||
<input
|
||||
bind:value={title}
|
||||
onchange={onChange}
|
||||
onchange={() => dispatch('change')}
|
||||
onkeydown={onKeyDown}
|
||||
placeholder="Field title"
|
||||
/>
|
||||
@@ -206,7 +206,7 @@
|
||||
placeholder="Enter a placeholder"
|
||||
rows="1"
|
||||
bind:value={placeholder}
|
||||
onchange={onChange}
|
||||
onchange={() => dispatch('change')}
|
||||
disabled={!shouldDisplayPlaceholder(type, format, enum_, contentEncoding, pattern, extra)}
|
||||
></textarea>
|
||||
</Label>
|
||||
@@ -236,7 +236,6 @@
|
||||
bind:enumLabels={extra['enumLabels']}
|
||||
originalType={extra['originalType']}
|
||||
overrideAllowKindChange={isFlowInput || isAppInput}
|
||||
{onChange}
|
||||
/>
|
||||
{:else if type == 'number' || type == 'integer'}
|
||||
<NumberTypeNarrowing
|
||||
|
||||
@@ -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 @@
|
||||
</div>
|
||||
{#if premiumInfo?.status === 'past_due'}
|
||||
<p class="text-red-500 text-base">
|
||||
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}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth />
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,4 @@
|
||||
|
||||
<!-- <ScriptWrapper {script} neverShowMeta={true} {customUi} /> -->
|
||||
|
||||
<EditableSchemaSdkWrapper onSchemaChange={(schema) => console.log(schema)} {customUi} {schema} />
|
||||
|
||||
{JSON.stringify(schema)}
|
||||
<EditableSchemaSdkWrapper {customUi} {schema} />
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -4,8 +4,8 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.536.0"
|
||||
wmill_pg = ">=1.536.0"
|
||||
wmill = ">=1.537.1"
|
||||
wmill_pg = ">=1.537.1"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.536.0
|
||||
version: 1.537.1
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.536.0'
|
||||
ModuleVersion = '1.537.1'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill-pg"
|
||||
version = "1.536.0"
|
||||
version = "1.537.1"
|
||||
description = "An extension client for the wmill client library focused on pg"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.536.0",
|
||||
"version": "1.537.1",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.536.0",
|
||||
"version": "1.537.1",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.536.0
|
||||
1.537.1
|
||||
|
||||
Reference in New Issue
Block a user