diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 0ee5367875..0ddce5f58f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -24,7 +24,7 @@ jobs: DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }} WORKER_GROUP: main - WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow + WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets options: >- --pull always --health-interval 10s --health-timeout 5s --health-retries 5 --health-cmd "curl diff --git a/backend/parsers/windmill-parser-bash/src/lib.rs b/backend/parsers/windmill-parser-bash/src/lib.rs index 4634006e26..815f3ee298 100644 --- a/backend/parsers/windmill-parser-bash/src/lib.rs +++ b/backend/parsers/windmill-parser-bash/src/lib.rs @@ -57,7 +57,7 @@ fn parse_bash_file(code: &str) -> anyhow::Result>> { typ: Typ::Str(None), default: default.clone().map(|x| json!(x)), otyp: None, - has_default: false, + has_default: default.is_some(), }); } else { break; @@ -113,6 +113,8 @@ token="$1" image="$2" digest="${3:-latest with spaces}" text="$4" # with comment +non_required="${5:-}" + "#; //println!("{}", serde_json::to_string()?); @@ -141,7 +143,7 @@ text="$4" # with comment name: "digest".to_string(), typ: Typ::Str(None), default: Some(json!("latest with spaces")), - has_default: false + has_default: true }, Arg { otyp: None, @@ -149,6 +151,13 @@ text="$4" # with comment typ: Typ::Str(None), default: None, has_default: false + }, + Arg { + otyp: None, + name: "non_required".to_string(), + typ: Typ::Str(None), + default: Some(json!("")), + has_default: true } ] } diff --git a/backend/parsers/windmill-parser-wasm/pkg/package.json b/backend/parsers/windmill-parser-wasm/pkg/package.json index b34706e892..995262e043 100644 --- a/backend/parsers/windmill-parser-wasm/pkg/package.json +++ b/backend/parsers/windmill-parser-wasm/pkg/package.json @@ -3,7 +3,7 @@ "collaborators": [ "Ruben Fiszel " ], - "version": "1.180.0", + "version": "1.186.0", "files": [ "windmill_parser_wasm_bg.wasm", "windmill_parser_wasm.js", diff --git a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js index bc20b435c8..1f40312a79 100644 --- a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js +++ b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js @@ -97,15 +97,6 @@ function getInt32Memory0() { return cachedInt32Memory0; } -const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); - -if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; - -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} - function addHeapObject(obj) { if (heap_next === heap.length) heap.push(heap.length + 1); const idx = heap_next; @@ -124,6 +115,15 @@ function getFloat64Memory0() { return cachedFloat64Memory0; } +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + let cachedBigInt64Memory0 = null; function getBigInt64Memory0() { @@ -495,7 +495,7 @@ function __wbg_get_imports() { imports.wbg.__wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); }; - imports.wbg.__wbg_eval_2e5c54091872c19c = function(arg0, arg1) { + imports.wbg.__wbg_eval_4cf9c172060c8b3d = function(arg0, arg1) { const ret = eval(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }; @@ -507,10 +507,6 @@ function __wbg_get_imports() { getInt32Memory0()[arg0 / 4 + 1] = len1; getInt32Memory0()[arg0 / 4 + 0] = ptr1; }; - imports.wbg.__wbindgen_error_new = function(arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }; imports.wbg.__wbindgen_boolean_get = function(arg0) { const v = getObject(arg0); const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; @@ -528,6 +524,10 @@ function __wbg_get_imports() { const ret = getObject(arg0) === getObject(arg1); return ret; }; + imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) { + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }; imports.wbg.__wbindgen_number_get = function(arg0, arg1) { const obj = getObject(arg1); const ret = typeof(obj) === 'number' ? obj : undefined; @@ -543,8 +543,8 @@ function __wbg_get_imports() { const ret = getObject(arg0) in getObject(arg1); return ret; }; - imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) { - const ret = BigInt.asUintN(64, arg0); + imports.wbg.__wbindgen_error_new = function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }; imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) { diff --git a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm index eb4e14223b..63db1be65f 100644 Binary files a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm and b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm differ diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 1613c6a097..f2faf9d01a 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -1757,12 +1757,17 @@ impl Drop for Guard { } } +#[derive(Deserialize)] +pub struct WindmillStatusCode { + windmill_status_code: Option, + result: Option>, +} async fn run_wait_result( authed: ApiAuthed, Extension(user_db): Extension, uuid: Uuid, Path((w_id, _)): Path<(String, T)>, -) -> error::JsonResult { +) -> error::Result { let mut result; let timeout = SERVER_CONFIG.read().await.timeout_wait_result.clone(); let timeout_ms = if timeout <= 0 { @@ -1781,16 +1786,16 @@ async fn run_wait_result( let fast_poll_duration = *WAIT_RESULT_FAST_POLL_DURATION_SECS as u64 * 1000; let mut accumulated_delay = 0 as u64; + loop { let mut tx = user_db.clone().begin(&authed).await?; - result = sqlx::query_scalar!( - "SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2", - uuid, - &w_id - ) - .fetch_optional(&mut *tx) - .await? - .flatten(); + result = + sqlx::query("SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2") + .bind(uuid) + .bind(&w_id) + .fetch_optional(&mut *tx) + .await?; + drop(tx); if result.is_some() { @@ -1810,14 +1815,18 @@ async fn run_wait_result( } if let Some(result) = result { g.done = true; - let status_code = result - .get("windmill_status_code") - .and_then(|x| x.as_i64()) - .and_then(|x| StatusCode::from_u16(x as u16).ok()); - if let Some(status_code) = status_code { - return Err(Error::CustomStatusCode(status_code, result)); + let result = RawResult::from_row(&result)?.result; + + let status_code = serde_json::from_str::(result.get()); + match status_code { + Ok(WindmillStatusCode { windmill_status_code: Some(status_code), result }) => { + Err(Error::CustomStatusCode( + StatusCode::from_u16(status_code).unwrap_or_else(|_| StatusCode::IM_A_TEAPOT), + result, + )) + } + _ => Ok(Json(result).into_response()), } - Ok(Json(result)) } else { Err(Error::ExecutionErr(format!("timeout after {}s", timeout))) } @@ -1870,12 +1879,12 @@ pub async fn run_wait_result_job_by_path_get( Extension(db): Extension, Path((w_id, script_path)): Path<(String, StripPath)>, Query(run_query): Query, -) -> error::JsonResult { +) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; if method == http::Method::HEAD { - return Ok(Json(serde_json::json!(""))); + return Ok(Json(serde_json::json!("")).into_response()); } let payload_r = run_query .payload @@ -1935,12 +1944,12 @@ pub async fn run_wait_result_flow_by_path_get( Path((w_id, flow_path)): Path<(String, StripPath)>, Query(run_query): Query, -) -> error::JsonResult { +) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; if method == http::Method::HEAD { - return Ok(Json(serde_json::json!(""))); + return Ok(Json(serde_json::json!("")).into_response()); } let payload_r = run_query .payload @@ -1970,7 +1979,7 @@ pub async fn run_wait_result_script_by_path( Path((w_id, script_path)): Path<(String, StripPath)>, Query(run_query): Query, args: PushArgs>>, -) -> error::JsonResult { +) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -1996,7 +2005,7 @@ async fn run_wait_result_script_by_path_internal( user_db: UserDB, w_id: String, args: PushArgs>>, -) -> Result, Error> { +) -> error::Result { check_queue_too_long(&db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?; let script_path = script_path.to_path(); check_scopes(&authed, || format!("run:script/{script_path}"))?; @@ -2042,7 +2051,7 @@ pub async fn run_wait_result_script_by_hash( Path((w_id, script_hash)): Path<(String, ScriptHash)>, Query(run_query): Query, args: PushArgs>>, -) -> error::JsonResult { +) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -2107,7 +2116,7 @@ pub async fn run_wait_result_flow_by_path( Path((w_id, flow_path)): Path<(String, StripPath)>, Query(run_query): Query, args: PushArgs>>, -) -> error::JsonResult { +) -> error::Result { #[cfg(feature = "enterprise")] check_license_key_valid().await?; @@ -2126,7 +2135,7 @@ async fn run_wait_result_flow_by_path_internal( user_db: UserDB, args: PushArgs>>, w_id: String, -) -> Result, Error> { +) -> error::Result { check_queue_too_long(&db, run_query.queue_limit).await?; let flow_path = flow_path.to_path(); diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index a567a13236..a17a47c5e7 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -15,6 +15,7 @@ use axum::{ }; use hyper::StatusCode; +use serde_json::value::RawValue; #[cfg(feature = "sqlx")] use sqlx::migrate::MigrateError; use thiserror::Error; @@ -65,7 +66,7 @@ pub enum Error { #[error("Error: {0:#?}")] JsonErr(serde_json::Value), #[error("Custom Status Code: {0:#?}")] - CustomStatusCode(StatusCode, serde_json::Value), + CustomStatusCode(StatusCode, Option>), #[error("{0}")] OpenAIError(String), } @@ -84,30 +85,41 @@ pub fn to_anyhow(e: T) -> anyhow:: #[cfg(feature = "axum")] impl IntoResponse for Error { fn into_response(self) -> axum::response::Response { - let e = &self; - let body = body::boxed(body::Full::from(e.to_string())); - let status = match self { - Self::NotFound(_) => axum::http::StatusCode::NOT_FOUND, - Self::NotAuthorized(_) => axum::http::StatusCode::UNAUTHORIZED, - Self::RequireAdmin(_) => axum::http::StatusCode::FORBIDDEN, - Self::CustomStatusCode(code, _) => code, - Self::SqlErr(_) | Self::BadRequest(_) | Self::OpenAIError(_) => { - axum::http::StatusCode::BAD_REQUEST + match self { + Self::CustomStatusCode(code, result) => { + let mut res = Json(result).into_response(); + let status_mut = res.status_mut(); + *status_mut = code; + res } - _ => axum::http::StatusCode::INTERNAL_SERVER_ERROR, - }; + _ => { + let e = &self; + let body = body::boxed(body::Full::from(e.to_string())); - if matches!(status, axum::http::StatusCode::NOT_FOUND) { - tracing::warn!(not_found = e.to_string()); - } else { - tracing::error!(error = e.to_string()); - }; + let status = match self { + Self::NotFound(_) => axum::http::StatusCode::NOT_FOUND, + Self::NotAuthorized(_) => axum::http::StatusCode::UNAUTHORIZED, + Self::RequireAdmin(_) => axum::http::StatusCode::FORBIDDEN, + Self::CustomStatusCode(code, _) => code, + Self::SqlErr(_) | Self::BadRequest(_) | Self::OpenAIError(_) => { + axum::http::StatusCode::BAD_REQUEST + } + _ => axum::http::StatusCode::INTERNAL_SERVER_ERROR, + }; - axum::response::Response::builder() - .header("Content-Type", "text/plain") - .status(status) - .body(body) - .unwrap() + if matches!(status, axum::http::StatusCode::NOT_FOUND) { + tracing::warn!(not_found = e.to_string()); + } else { + tracing::error!(error = e.to_string()); + }; + + axum::response::Response::builder() + .header("Content-Type", "text/plain") + .status(status) + .body(body) + .unwrap() + } + } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0f9c226382..c8e8329b15 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -47,7 +47,7 @@ "svelte-tiny-virtual-list": "^2.0.5", "tailwind-merge": "^1.13.2", "vscode-ws-jsonrpc": "3.0.0", - "windmill-parser-wasm": "^1.180.0", + "windmill-parser-wasm": "^1.186.0", "y-monaco": "^0.1.4", "y-websocket": "^1.5.0", "yjs": "^13.6.7" @@ -9946,9 +9946,9 @@ } }, "node_modules/windmill-parser-wasm": { - "version": "1.180.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.180.0.tgz", - "integrity": "sha512-ikkyfiAdILGUZT0g+eiZ9dgPYFHCjtZVFCRUAkw7PlbK+8bT6ROa+mv1ZRxkc3LZLGsWEwteojdcjfopPFTTAQ==" + "version": "1.186.0", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.186.0.tgz", + "integrity": "sha512-xoeGloTQHl54WWp97rmy94Us6bzY4ckU+Y7+f4NcR4l9DG9XpzsHQrjwzZH1Ak9zM91oIQDdgTBZkrAsZ5N97w==" }, "node_modules/wordwrap": { "version": "1.0.0", @@ -17012,9 +17012,9 @@ } }, "windmill-parser-wasm": { - "version": "1.180.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.180.0.tgz", - "integrity": "sha512-ikkyfiAdILGUZT0g+eiZ9dgPYFHCjtZVFCRUAkw7PlbK+8bT6ROa+mv1ZRxkc3LZLGsWEwteojdcjfopPFTTAQ==" + "version": "1.186.0", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.186.0.tgz", + "integrity": "sha512-xoeGloTQHl54WWp97rmy94Us6bzY4ckU+Y7+f4NcR4l9DG9XpzsHQrjwzZH1Ak9zM91oIQDdgTBZkrAsZ5N97w==" }, "wordwrap": { "version": "1.0.0", diff --git a/frontend/package.json b/frontend/package.json index 1f61d8aefa..e4c6ba2d0a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -124,7 +124,7 @@ "svelte-tiny-virtual-list": "^2.0.5", "tailwind-merge": "^1.13.2", "vscode-ws-jsonrpc": "3.0.0", - "windmill-parser-wasm": "^1.180.0", + "windmill-parser-wasm": "^1.186.0", "y-monaco": "^0.1.4", "y-websocket": "^1.5.0", "yjs": "^13.6.7"