Merge branch 'main' into alp/teams_scripts

This commit is contained in:
Alexander Petric
2025-02-07 09:25:45 -05:00
committed by Alex Petric
38 changed files with 458 additions and 214 deletions
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## [1.458.0](https://github.com/windmill-labs/windmill/compare/v1.457.1...v1.458.0) (2025-02-07)
### Features
* serve static websites ([#5218](https://github.com/windmill-labs/windmill/issues/5218)) ([48e4ae6](https://github.com/windmill-labs/windmill/commit/48e4ae6561bfac725bc9ddee2697b44af48ad3a8))
### Bug Fixes
* remove unnecessary rw on cache for powershell in nsjail ([#5236](https://github.com/windmill-labs/windmill/issues/5236)) ([3bb19dd](https://github.com/windmill-labs/windmill/commit/3bb19dd87cbdb0e36148b8b4e19c4312804fb38e))
## [1.457.1](https://github.com/windmill-labs/windmill/compare/v1.457.0...v1.457.1) (2025-02-05)
@@ -0,0 +1,67 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n c.id IS NOT NULL AS completed,\n q.id IS NOT NULL AND q.running AS running,\n SUBSTR(logs, GREATEST($1 - log_offset, 0)) AS logs,\n COALESCE(r.memory_peak, c.memory_peak) AS mem_peak,\n CASE\n -- flow step:\n WHEN flow_step_id IS NOT NULL THEN NULL\n -- completed:\n WHEN c.id IS NOT NULL THEN COALESCE(\n c.workflow_as_code_status || c.flow_status,\n c.workflow_as_code_status,\n c.flow_status\n )\n -- not completed:\n ELSE COALESCE(\n f.workflow_as_code_status || f.flow_status,\n f.workflow_as_code_status,\n f.flow_status\n )\n END AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 AS log_offset,\n created_by AS \"created_by!\",\n CASE WHEN $4::BOOLEAN THEN (\n SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc'\n ) END AS progress\n FROM v2_job j\n LEFT JOIN v2_job_queue q USING (id)\n LEFT JOIN v2_job_runtime r USING (id)\n LEFT JOIN v2_job_status f USING (id)\n LEFT JOIN v2_job_completed c USING (id)\n LEFT JOIN job_logs ON job_logs.job_id = $3\n WHERE j.workspace_id = $2 AND j.id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "completed",
"type_info": "Bool"
},
{
"ordinal": 1,
"name": "running",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "logs",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "mem_peak",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "flow_status: sqlx::types::Json<Box<RawValue>>",
"type_info": "Jsonb"
},
{
"ordinal": 5,
"name": "log_offset",
"type_info": "Int4"
},
{
"ordinal": 6,
"name": "created_by!",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "progress",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4",
"Text",
"Uuid",
"Bool"
]
},
"nullable": [
null,
null,
null,
null,
null,
null,
false,
null
]
},
"hash": "0128194fb539809e15bee670864fff3e22de8332d0c6d8ca98d22d62137fe701"
}
@@ -0,0 +1,31 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_completed AS cj\n ( workspace_id\n , id\n , started_at\n , duration_ms\n , result\n , result_columns\n , canceled_by\n , canceled_reason\n , flow_status\n , workflow_as_code_status\n , memory_peak\n , status\n )\n SELECT q.workspace_id, q.id, started_at, COALESCE($9::bigint, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE(started_at, now()))))*1000), $3, $10, $5, $6,\n flow_status, workflow_as_code_status,\n $8, CASE WHEN $4::BOOL THEN 'canceled'::job_status\n WHEN $7::BOOL THEN 'skipped'::job_status\n WHEN $2::BOOL THEN 'success'::job_status\n ELSE 'failure'::job_status END AS status\n FROM v2_job_queue q LEFT JOIN v2_job_status USING (id) WHERE q.id = $1\n ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, result = $3 RETURNING duration_ms AS \"duration_ms!\"",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "duration_ms!",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Uuid",
"Bool",
"Jsonb",
"Bool",
"Varchar",
"Text",
"Bool",
"Int4",
"Int8",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "3a9441fe8fef1605d02e92b65d1df664b4de9aabf1e0e219596b295d52438008"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_status (id, workflow_as_code_status)\n VALUES ($1, JSONB_SET('{}'::JSONB, array[$2], $3))\n ON CONFLICT (id) DO UPDATE SET workflow_as_code_status =\n COALESCE(EXCLUDED.workflow_as_code_status, '{}'::JSONB) || $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text",
"Jsonb"
]
},
"nullable": []
},
"hash": "3eb447ed317f3d8724b2309cfdf7cfb058a35a81314db981bc953a9505725082"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET\n workflow_as_code_status = jsonb_set(\n jsonb_set(\n COALESCE(workflow_as_code_status, '{}'::jsonb),\n array[$1],\n COALESCE(workflow_as_code_status->$1, '{}'::jsonb)\n ),\n array[$1, 'started_at'],\n to_jsonb(now()::text)\n )\n WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid"
]
},
"nullable": []
},
"hash": "45d616c92ebcbe30a563e1fa7d2d0e53392e238144b039cfe042587d7fe1dea3"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET\n workflow_as_code_status = jsonb_set(\n jsonb_set(\n COALESCE(workflow_as_code_status, '{}'::jsonb),\n array[$1],\n COALESCE(workflow_as_code_status->$1, '{}'::jsonb)\n ),\n array[$1, 'duration_ms'],\n to_jsonb($2::bigint)\n )\n WHERE id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int8",
"Uuid"
]
},
"nullable": []
},
"hash": "56f7325e3b0316866714e76d94b50d9d258c288883b2b5b0ab286f5cb50850b5"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT workflow_as_code_status FROM v2_job_completed WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "workflow_as_code_status",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
true
]
},
"hash": "867d5c75ddc6c5d20136880c7294844b4c1a38701190795a801fa43c74a0beeb"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM _sqlx_migrations WHERE version=20250201145630 OR version=20250201145631",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "976295cc04007f3cf5d8ba8d3fea665692a2192ee664216bdab04e6d2547422f"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_status (id, workflow_as_code_status) VALUES ($1, '{}'::JSONB)\n ON CONFLICT (id) DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "d067bf59ed65562f1efbfbc9c264cbcb90e5a8d94d628cc78d1f2271c43e56f1"
}
+42 -42
View File
@@ -5695,9 +5695,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.20.2"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "oneshot"
@@ -5996,9 +5996,9 @@ dependencies = [
[[package]]
name = "p384"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
dependencies = [
"ecdsa",
"elliptic-curve",
@@ -6688,9 +6688,9 @@ dependencies = [
[[package]]
name = "quick_cache"
version = "0.6.9"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d7c94f8935a9df96bb6380e8592c70edf497a643f94bd23b2f76b399385dbf4"
checksum = "3f67cfc9c723c39f3615eb0840b00c4cb9e2b068d2fa761a30d845ec91730a59"
dependencies = [
"ahash 0.8.11",
"equivalent",
@@ -6708,7 +6708,7 @@ dependencies = [
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.0",
"rustc-hash 2.1.1",
"rustls 0.23.22",
"socket2",
"thiserror 2.0.11",
@@ -6726,7 +6726,7 @@ dependencies = [
"getrandom 0.2.15",
"rand 0.8.5",
"ring 0.17.8",
"rustc-hash 2.1.0",
"rustc-hash 2.1.1",
"rustls 0.23.22",
"rustls-pki-types",
"slab",
@@ -6797,7 +6797,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.0",
"zerocopy 0.8.16",
"zerocopy 0.8.17",
]
[[package]]
@@ -6855,7 +6855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
dependencies = [
"getrandom 0.3.1",
"zerocopy 0.8.16",
"zerocopy 0.8.17",
]
[[package]]
@@ -7334,9 +7334,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
@@ -10859,7 +10859,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"axum",
@@ -10902,7 +10902,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"argon2",
@@ -10995,7 +10995,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -11013,7 +11013,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"chrono",
"serde",
@@ -11026,7 +11026,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"serde",
@@ -11040,7 +11040,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"async-stream",
@@ -11099,7 +11099,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"regex",
"serde",
@@ -11113,7 +11113,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"bytes",
@@ -11136,7 +11136,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -11148,7 +11148,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -11157,7 +11157,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11169,7 +11169,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"serde_json",
@@ -11181,7 +11181,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"gosyn",
@@ -11193,7 +11193,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11205,7 +11205,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -11216,7 +11216,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -11227,7 +11227,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -11247,7 +11247,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -11264,7 +11264,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11276,7 +11276,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11294,7 +11294,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -11316,7 +11316,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"serde_json",
@@ -11326,7 +11326,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -11359,7 +11359,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -11369,7 +11369,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.457.1"
version = "1.458.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -11783,11 +11783,11 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.16"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b8c07a70861ce02bad1607b5753ecb2501f67847b9f9ada7c160fff0ec6300c"
checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713"
dependencies = [
"zerocopy-derive 0.8.16",
"zerocopy-derive 0.8.17",
]
[[package]]
@@ -11803,9 +11803,9 @@ dependencies = [
[[package]]
name = "zerocopy-derive"
version = "0.8.16"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5226bc9a9a9836e7428936cde76bb6b22feea1a8bfdbc0d241136e4d13417e25"
checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626"
dependencies = [
"proc-macro2",
"quote",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.457.1"
version = "1.458.0"
authors.workspace = true
edition.workspace = true
@@ -30,7 +30,7 @@ members = [
]
[workspace.package]
version = "1.457.1"
version = "1.458.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -21,7 +21,7 @@ SELECT
CASE WHEN j.trigger_kind = 'schedule'::job_trigger_kind THEN j.trigger END
AS schedule_path,
j.permissioned_as,
s.flow_status,
COALESCE(s.flow_status, s.workflow_as_code_status) AS flow_status,
j.raw_flow,
j.flow_step_id IS NOT NULL AS is_flow_step,
j.script_lang AS language,
@@ -21,7 +21,7 @@ SELECT
CASE WHEN j.trigger_kind = 'schedule'::job_trigger_kind THEN j.trigger END
AS schedule_path,
j.permissioned_as,
c.flow_status,
COALESCE(c.flow_status, c.workflow_as_code_status) AS flow_status,
j.raw_flow,
j.flow_step_id IS NOT NULL AS is_flow_step,
j.script_lang AS language,
+2 -1
View File
@@ -152,7 +152,8 @@ BEGIN
-- v2_job_status:
IF EXISTS(SELECT 1 FROM v2_job_status WHERE id = OLD.id) THEN
SELECT * INTO job_status FROM v2_job_status WHERE id = OLD.id;
IF job_status.flow_status::TEXT IS DISTINCT FROM OLD.__flow_status::TEXT THEN
IF COALESCE(job_status.flow_status, job_status.workflow_as_code_status)::TEXT IS DISTINCT FROM OLD.__flow_status::TEXT
THEN
RAISE EXCEPTION 'flow_status mismatch';
END IF;
IF job_status.flow_leaf_jobs::TEXT IS DISTINCT FROM OLD.__leaf_jobs::TEXT THEN
+65
View File
@@ -3828,6 +3828,71 @@ async fn test_job_labels(db: Pool<Postgres>) {
test(&["z", "a", "x"]).await;
}
#[cfg(feature = "python")]
const WORKFLOW_AS_CODE: &str = r#"
from wmill import task
import pandas as pd
import numpy as np
@task()
def heavy_compute(n: int):
df = pd.DataFrame(np.random.randn(100, 4), columns=list('ABCD'))
return df.sum().sum()
@task
def send_result(res: int, email: str):
print(f"Sending result {res} to {email}")
return "OK"
def main(n: int):
l = []
for i in range(n):
l.append(heavy_compute(i))
print(l)
return [send_result(sum(l), "example@example.com"), n]
"#;
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "hello"))]
async fn test_workflow_as_code(db: Pool<Postgres>) {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await;
let port = server.addr.port();
// workflow as code require at least 2 workers:
let db = &db;
in_test_worker(
&db,
async move {
let job = RunJob::from(JobPayload::Code(RawCode {
language: ScriptLang::Python3,
content: WORKFLOW_AS_CODE.into(),
..RawCode::default()
}))
.arg("n", json!(3))
.run_until_complete(&db, port)
.await;
assert_eq!(job.json_result().unwrap(), json!(["OK", 3]));
let workflow_as_code_status = sqlx::query_scalar!(
"SELECT workflow_as_code_status FROM v2_job_completed WHERE id = $1",
job.id
)
.fetch_one(db)
.await
.unwrap()
.unwrap();
assert_eq!(
workflow_as_code_status.get("name"),
Some(&json!("send_result"))
);
},
port,
)
.await;
}
async fn test_for_versions<F: Future<Output = ()>>(
version_flags: impl Iterator<Item = Arc<RwLock<bool>>>,
test: impl Fn() -> F,
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.457.1
version: 1.458.0
title: Windmill API
contact:
+11 -1
View File
@@ -84,7 +84,7 @@ impl Migrate for CustomMigrator {
})?
.unwrap_or(false);
if !r {
tracing::info!("PG lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tracing::info!("PG migration lock already acquired by another server or worker, a migration is in progress, this may take a long time if you have many jobs and be normal, rechecking in 5s.");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
@@ -188,6 +188,16 @@ pub async fn migrate(db: &DB) -> Result<(), Error> {
tracing::info!("Could not remove sqlx migration with version=20250201145632: {err:#}");
}
// New version of `v2_as_queue` and `v2_as_completed_job` VIEWs.
if let Err(err) = sqlx::query!(
"DELETE FROM _sqlx_migrations WHERE version=20250201145630 OR version=20250201145631"
)
.execute(db)
.await
{
tracing::info!("Could not remove sqlx migration with version=[20250201145630, 20250201145631] : {err:#}");
}
match sqlx::migrate!("../migrations")
.run_direct(&mut custom_migrator)
.await
+70 -100
View File
@@ -3425,13 +3425,25 @@ pub async fn run_workflow_as_code(
if !wkflow_query.skip_update.unwrap_or(false) {
sqlx::query!(
"UPDATE v2_job_status SET flow_status = jsonb_set(COALESCE(flow_status, '{}'::jsonb), array[$1], jsonb_set(jsonb_set('{}'::jsonb, '{scheduled_for}', to_jsonb(now()::text)), '{name}', to_jsonb($3::text))) WHERE id = $2",
uuid.to_string(),
"INSERT INTO v2_job_status (id, workflow_as_code_status)
VALUES ($1, JSONB_SET('{}'::JSONB, array[$2], $3))
ON CONFLICT (id) DO UPDATE SET workflow_as_code_status =
COALESCE(EXCLUDED.workflow_as_code_status, '{}'::JSONB) || $3",
job_id,
entrypoint
).execute(&mut *tx).await?;
uuid.to_string(),
serde_json::json!({ "scheduled_for": Utc::now(), "name": entrypoint }),
)
.execute(&mut *tx)
.await?;
} else {
tracing::info!("Skipping update of flow status for job {job_id} in workspace {w_id}");
sqlx::query!(
"INSERT INTO v2_job_status (id, workflow_as_code_status) VALUES ($1, '{}'::JSONB)
ON CONFLICT (id) DO NOTHING",
job_id,
)
.execute(&mut *tx)
.await?;
}
if *CLOUD_HOSTED {
@@ -5159,114 +5171,72 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R
)));
}
#[derive(Deserialize, sqlx::FromRow)]
pub struct JobUpdateRow {
pub running: bool,
pub logs: Option<String>,
pub mem_peak: Option<i32>,
pub flow_status: Option<sqlx::types::Json<Box<serde_json::value::RawValue>>>,
pub log_offset: Option<i32>,
pub created_by: String,
}
async fn get_job_update(
OptAuthed(opt_authed): OptAuthed,
Extension(db): Extension<DB>,
Path((w_id, job_id)): Path<(String, Uuid)>,
Query(JobUpdateQuery { running, log_offset, get_progress }): Query<JobUpdateQuery>,
) -> error::JsonResult<JobUpdate> {
Query(JobUpdateQuery { log_offset, get_progress, .. }): Query<JobUpdateQuery>,
) -> JsonResult<JobUpdate> {
let record = sqlx::query!(
"SELECT
running AS \"running!\",
substr(concat(coalesce(v2_as_queue.logs, ''), job_logs.logs), greatest($1 - job_logs.log_offset, 0)) AS logs,
mem_peak,
CASE WHEN is_flow_step is true then NULL else flow_status END AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",
job_logs.log_offset + char_length(job_logs.logs) + 1 AS log_offset,
created_by AS \"created_by!\"
FROM v2_as_queue
LEFT JOIN job_logs ON job_logs.job_id = v2_as_queue.id
WHERE v2_as_queue.workspace_id = $2 AND v2_as_queue.id = $3",
c.id IS NOT NULL AS completed,
q.id IS NOT NULL AND q.running AS running,
SUBSTR(logs, GREATEST($1 - log_offset, 0)) AS logs,
COALESCE(r.memory_peak, c.memory_peak) AS mem_peak,
CASE
-- flow step:
WHEN flow_step_id IS NOT NULL THEN NULL
-- completed:
WHEN c.id IS NOT NULL THEN COALESCE(
c.workflow_as_code_status || c.flow_status,
c.workflow_as_code_status,
c.flow_status
)
-- not completed:
ELSE COALESCE(
f.workflow_as_code_status || f.flow_status,
f.workflow_as_code_status,
f.flow_status
)
END AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",
job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 AS log_offset,
created_by AS \"created_by!\",
CASE WHEN $4::BOOLEAN THEN (
SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc'
) END AS progress
FROM v2_job j
LEFT JOIN v2_job_queue q USING (id)
LEFT JOIN v2_job_runtime r USING (id)
LEFT JOIN v2_job_status f USING (id)
LEFT JOIN v2_job_completed c USING (id)
LEFT JOIN job_logs ON job_logs.job_id = $3
WHERE j.workspace_id = $2 AND j.id = $3",
log_offset,
&w_id,
job_id
job_id,
get_progress.unwrap_or(false)
)
.fetch_optional(&db)
.await?;
.await?
.ok_or_else(|| Error::NotFound(format!("Job not found: {}", job_id)))?;
let progress: Option<i32> = if get_progress == Some(true) {
sqlx::query_scalar!(
"SELECT scalar_int FROM job_stats WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
&w_id,
job_id,
"progress_perc"
)
.fetch_optional(&db)
.await?.and_then(|inner| inner)
} else {
None
};
if let Some(record) = record {
if opt_authed.is_none() && record.created_by != "anonymous" {
return Err(Error::BadRequest(
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &job_id).await?;
Ok(Json(JobUpdate {
running: if !running && record.running {
Some(true)
} else {
None
},
log_offset: record.log_offset,
completed: None,
new_logs: record.logs,
mem_peak: record.mem_peak,
progress,
flow_status: record
.flow_status
.map(|x: sqlx::types::Json<Box<RawValue>>| x.0),
}))
} else {
let record = sqlx::query!(
"SELECT
substr(concat(coalesce(v2_as_completed_job.logs, ''), job_logs.logs), greatest($1 - job_logs.log_offset, 0)) AS logs,
mem_peak,
CASE WHEN is_flow_step is true then NULL else flow_status END AS \"flow_status: sqlx::types::Json<Box<RawValue>>\",
job_logs.log_offset + char_length(job_logs.logs) + 1 AS log_offset,
created_by AS \"created_by!\"
FROM v2_as_completed_job
LEFT JOIN job_logs ON job_logs.job_id = v2_as_completed_job.id
WHERE v2_as_completed_job.workspace_id = $2 AND v2_as_completed_job.id = $3",
log_offset,
&w_id,
job_id
)
.fetch_optional(&db)
.await?;
if let Some(record) = record {
if opt_authed.is_none() && record.created_by != "anonymous" {
return Err(Error::BadRequest(
"As a non logged in user, you can only see jobs ran by anonymous users"
.to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &job_id).await?;
Ok(Json(JobUpdate {
running: Some(false),
completed: Some(true),
log_offset: record.log_offset,
new_logs: record.logs,
mem_peak: record.mem_peak,
progress,
flow_status: record
.flow_status
.map(|x: sqlx::types::Json<Box<RawValue>>| x.0),
}))
} else {
Err(error::Error::NotFound(format!("Job not found: {}", job_id)))
}
if opt_authed.is_none() && record.created_by != "anonymous" {
return Err(Error::BadRequest(
"As a non logged in user, you can only see jobs ran by anonymous users".to_string(),
));
}
log_job_view(&db, opt_authed.as_ref(), &w_id, &job_id).await?;
Ok(Json(JobUpdate {
running: record.running,
completed: record.completed,
log_offset: record.log_offset,
new_logs: record.logs,
mem_peak: record.mem_peak,
progress: record.progress,
flow_status: record
.flow_status
.map(|x: sqlx::types::Json<Box<RawValue>>| x.0),
}))
}
pub fn filter_list_completed_query(
+1
View File
@@ -32,6 +32,7 @@ pub async fn workspaces_list_available_teams_channels() -> Result<StatusCode, Er
));
}
#[cfg(feature = "enterprise")]
pub fn teams_service() -> Router {
Router::new()
}
+1 -1
View File
@@ -353,7 +353,7 @@ pub enum JobPayload {
Noop,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct RawCode {
pub content: String,
pub path: Option<String>,
+2 -1
View File
@@ -24,11 +24,12 @@ use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize};
use crate::utils::StripPath;
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Hash, Eq, sqlx::Type)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Hash, Eq, sqlx::Type, Default)]
#[sqlx(type_name = "SCRIPT_LANG", rename_all = "lowercase")]
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
pub enum ScriptLang {
Nativets,
#[default]
Deno,
Python3,
Go,
+28 -40
View File
@@ -591,28 +591,28 @@ pub async fn add_completed_job<T: Serialize + Send + Sync + ValidableJson>(
, canceled_by
, canceled_reason
, flow_status
, workflow_as_code_status
, memory_peak
, status
)
VALUES ($1, $2, $3, COALESCE($12::bigint, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($3, now()))))*1000), $5, $13, $7, $8, $9,\
$11, CASE WHEN $6::BOOL THEN 'canceled'::job_status
WHEN $10::BOOL THEN 'skipped'::job_status
WHEN $4::BOOL THEN 'success'::job_status
ELSE 'failure'::job_status END)
ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, result = $5 RETURNING duration_ms AS \"duration_ms!\"",
/* $1 */ queued_job.workspace_id,
/* $2 */ queued_job.id,
/* $3 */ queued_job.started_at,
/* $4 */ success,
/* $5 */ result as Json<&T>,
/* $6 */ canceled_by.is_some(),
/* $7 */ canceled_by.clone().map(|cb| cb.username).flatten(),
/* $8 */ canceled_by.clone().map(|cb| cb.reason).flatten(),
/* $9 */ &queued_job.flow_status as &Option<Json<Box<RawValue>>>,
/* $10 */ skipped,
/* $11 */ if mem_peak > 0 { Some(mem_peak) } else { None },
/* $12 */ duration,
/* $13 */ result_columns as Option<&Vec<String>>,
SELECT q.workspace_id, q.id, started_at, COALESCE($9::bigint, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE(started_at, now()))))*1000), $3, $10, $5, $6,
flow_status, workflow_as_code_status,
$8, CASE WHEN $4::BOOL THEN 'canceled'::job_status
WHEN $7::BOOL THEN 'skipped'::job_status
WHEN $2::BOOL THEN 'success'::job_status
ELSE 'failure'::job_status END AS status
FROM v2_job_queue q LEFT JOIN v2_job_status USING (id) WHERE q.id = $1
ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, result = $3 RETURNING duration_ms AS \"duration_ms!\"",
/* $1 */ queued_job.id,
/* $2 */ success,
/* $3 */ result as Json<&T>,
/* $4 */ canceled_by.is_some(),
/* $5 */ canceled_by.clone().map(|cb| cb.username).flatten(),
/* $6 */ canceled_by.clone().map(|cb| cb.reason).flatten(),
/* $7 */ skipped,
/* $8 */ if mem_peak > 0 { Some(mem_peak) } else { None },
/* $9 */ duration,
/* $10 */ result_columns as Option<&Vec<String>>,
)
.fetch_one(&mut *tx)
.await
@@ -633,28 +633,14 @@ pub async fn add_completed_job<T: Serialize + Send + Sync + ValidableJson>(
}
if !queued_job.is_flow_step {
if _duration > 500
&& (queued_job.job_kind == JobKind::Script
|| queued_job.job_kind == JobKind::Preview)
{
if let Err(e) = sqlx::query!(
"UPDATE v2_job_completed SET flow_status = f.flow_status FROM v2_job_status f WHERE v2_job_completed.id = $1 AND f.id = $1 AND v2_job_completed.workspace_id = $2",
&queued_job.id,
&queued_job.workspace_id
)
.execute(&mut *tx)
.await {
tracing::error!("Could not update job duration: {}", e);
}
}
if let Some(parent_job) = queued_job.parent_job {
if let Err(e) = sqlx::query_scalar!(
let _ = sqlx::query_scalar!(
"UPDATE v2_job_status SET
flow_status = jsonb_set(
workflow_as_code_status = jsonb_set(
jsonb_set(
COALESCE(flow_status, '{}'::jsonb),
COALESCE(workflow_as_code_status, '{}'::jsonb),
array[$1],
COALESCE(flow_status->$1, '{}'::jsonb)
COALESCE(workflow_as_code_status->$1, '{}'::jsonb)
),
array[$1, 'duration_ms'],
to_jsonb($2::bigint)
@@ -665,9 +651,11 @@ pub async fn add_completed_job<T: Serialize + Send + Sync + ValidableJson>(
parent_job
)
.execute(&mut *tx)
.await {
tracing::error!("Could not update parent job flow_status: {}", e);
}
.await
.inspect_err(|e| tracing::error!(
"Could not update parent job `duration_ms` in workflow as code status: {}",
e,
));
}
}
// tracing::error!("Added completed job {:#?}", queued_job);
+10 -7
View File
@@ -2006,13 +2006,13 @@ async fn handle_queued_job(
.warn_after_seconds(5)
.await?;
} else if let Some(parent_job) = job.parent_job {
if let Err(e) = sqlx::query_scalar!(
let _ = sqlx::query_scalar!(
"UPDATE v2_job_status SET
flow_status = jsonb_set(
workflow_as_code_status = jsonb_set(
jsonb_set(
COALESCE(flow_status, '{}'::jsonb),
COALESCE(workflow_as_code_status, '{}'::jsonb),
array[$1],
COALESCE(flow_status->$1, '{}'::jsonb)
COALESCE(workflow_as_code_status->$1, '{}'::jsonb)
),
array[$1, 'started_at'],
to_jsonb(now()::text)
@@ -2024,9 +2024,12 @@ async fn handle_queued_job(
.execute(db)
.warn_after_seconds(5)
.await
{
tracing::error!("Could not update parent job started_at flow_status: {}", e);
}
.inspect_err(|e| {
tracing::error!(
"Could not update parent job `started_at` in workflow as code status: {}",
e
)
});
}
let started = Instant::now();
+1 -1
View File
@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.457.1";
export const VERSION = "v1.458.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+1 -1
View File
@@ -60,7 +60,7 @@ export {
// }
// });
export const VERSION = "1.457.1";
export const VERSION = "1.458.0";
const command = new Command()
.name("wmill")
+2 -2
View File
@@ -1,7 +1,7 @@
import process from "node:process";
import { colors, Confirm, log, yamlParseFile, yamlStringify } from "./deps.ts";
import * as wmill from "./gen/services.gen.ts";
import { AiResource, Config, GlobalSetting } from "./gen/types.gen.ts";
import { AIResource, Config, GlobalSetting } from "./gen/types.gen.ts";
import { compareInstanceObjects, InstanceSyncOptions } from "./instance.ts";
import { isSuperset } from "./types.ts";
import { deepEqual } from "./utils.ts";
@@ -20,7 +20,7 @@ export interface SimplifiedSettings {
error_handler?: string;
error_handler_extra_args?: any;
error_handler_muted_on_cancel?: boolean;
ai_resource?: AiResource;
ai_resource?: AIResource;
code_completion_model?: string;
ai_models: string[];
large_file_storage?: any;
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.457.1",
"version": "1.458.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.457.1",
"version": "1.458.0",
"license": "AGPL-3.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.457.1",
"version": "1.458.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.457.1"
wmill_pg = ">=1.457.1"
wmill = ">=1.458.0"
wmill_pg = ">=1.458.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.457.1
version: 1.458.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.457.1'
ModuleVersion = '1.458.0'
# Supported PSEditions
# CompatiblePSEditions = @()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.457.1"
version = "1.458.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.457.1"
version = "1.458.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.457.1",
"version": "1.458.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.457.1",
"version": "1.458.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.457.1
1.458.0