Compare commits

..

7 Commits

Author SHA1 Message Date
Ruben Fiszel
7a1c28f6d7 chore(main): release 1.537.0 (#6486)
* chore(main): release 1.537.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2025-08-28 19:29:04 +00:00
BaptisteMoureaux
6f4bdc0148 bump lsp go version & add private packages go support (#6484) 2025-08-28 18:06:36 +00:00
Ruben Fiszel
4973c860f2 fix: fix workflow as code behavior with multithread 2025-08-28 17:44:57 +00:00
hugocasa
2ce11cea3b prevent past due team plans to overuse (#6487)
* prevent past due team plans to overuse

* nit

* update ee ref
2025-08-28 16:56:57 +00:00
centdix
0cc11b3f31 feat(aichat): allow reverting specific line for inline script suggestions (#6480)
* draft

* cleaning

* settimeout temp fix

* adjust colors

* cleaning

* good stuff

* no timeout

* add on finish callback

* cleaning

* fix

* adpat accept all / reject all

* cleaning

* cleaning

* adapt click on module reject/accept

* clearer function names

* nit

* simplify
2025-08-28 10:00:59 +00:00
Ruben Fiszel
73a3f4cc73 fix: fix okta and oauth0 sso settings 2025-08-27 18:38:07 +00:00
pyranota
006f32602c fix: fix relative imports cache invalidation (#6468)
* fix: new script on deps job for secondary scripts

Signed-off-by: pyranota <pyra@duck.com>

* make it work (dirty)

Signed-off-by: pyranota <pyra@duck.com>

* reduce db calls

* remove `triggered_by_relative_import`

Signed-off-by: pyranota <pyra@duck.com>

* add comment to common_dependency_path

Signed-off-by: pyranota <pyra@duck.com>

* add fallback to old behavior

Signed-off-by: pyranota <pyra@duck.com>

* remove TODOs

Signed-off-by: pyranota <pyra@duck.com>

* pass deployed hash to git sync handler function

Signed-off-by: pyranota <pyra@duck.com>

* fix ci

Signed-off-by: pyranota <pyra@duck.com>

---------

Signed-off-by: pyranota <pyra@duck.com>
2025-08-27 17:44:35 +00:00
53 changed files with 826 additions and 457 deletions

View File

@@ -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)

View 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"
}

View File

@@ -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"
}

View File

@@ -15,7 +15,7 @@
]
},
"nullable": [
null
true
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"

View 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"
}

View 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"
}

View File

@@ -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"
}

91
backend/Cargo.lock generated
View File

@@ -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"

View File

@@ -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 <ruben@windmill.dev>"]
edition = "2021"

View File

@@ -1 +1 @@
6396854336ae27fb14ccb792d80c31ff614b2afa
d16e52d570f10dfdabb04a9061fe7ebdbf5a2aa1

View File

@@ -0,0 +1,2 @@
-- Add down migration script here
DROP TRIGGER script_insert_trigger ON script;

View File

@@ -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');

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();

View File

@@ -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.

View File

@@ -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:

View File

@@ -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>,

View File

@@ -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))

View File

@@ -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
}

View File

@@ -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)]

View File

@@ -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."
)));
}
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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(&current_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,
&current_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!(

View File

@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.536.0";
export const VERSION = "v1.537.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -68,7 +68,7 @@ export {
// }
// });
export const VERSION = "1.536.0";
export const VERSION = "1.537.0";
const command = new Command()
.name("wmill")

View File

@@ -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": {

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.536.0",
"version": "1.537.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -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>

View File

@@ -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)
}}

View File

@@ -676,7 +676,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) {
@@ -1684,10 +1698,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}

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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">

View File

@@ -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()
}
}
])
})
)
}
}
})

View File

@@ -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()
}
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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 />

View File

@@ -26,6 +26,4 @@
<!-- <ScriptWrapper {script} neverShowMeta={true} {customUi} /> -->
<EditableSchemaSdkWrapper onSchemaChange={(schema) => console.log(schema)} {customUi} {schema} />
{JSON.stringify(schema)}
<EditableSchemaSdkWrapper {customUi} {schema} />

View File

@@ -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"]

View File

@@ -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 = "*"

View File

@@ -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(

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.536.0
version: 1.537.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.536.0'
ModuleVersion = '1.537.0'
# Supported PSEditions
# CompatiblePSEditions = @()

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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"]

View File

@@ -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": {

View File

@@ -1 +1 @@
1.536.0
1.537.0