diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index c95af782e0..8f15fe051a 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -101,7 +101,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=enterprise,enterprise_saml + features=enterprise,enterprise_saml,stripe nsjail=true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev diff --git a/backend/Cargo.lock b/backend/Cargo.lock index b8506ec816..fdf9c84227 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -9533,6 +9533,7 @@ version = "1.237.0" dependencies = [ "anyhow", "convert_case 0.6.0", + "lazy_static", "regex", "serde-wasm-bindgen", "serde_json", diff --git a/backend/parsers/windmill-parser-py/Cargo.toml b/backend/parsers/windmill-parser-py/Cargo.toml index 73a797ef79..9111534406 100644 --- a/backend/parsers/windmill-parser-py/Cargo.toml +++ b/backend/parsers/windmill-parser-py/Cargo.toml @@ -13,4 +13,4 @@ windmill-parser.workspace = true rustpython-parser.workspace = true itertools.workspace = true serde_json.workspace = true -anyhow.workspace = true +anyhow.workspace = true \ No newline at end of file diff --git a/backend/parsers/windmill-parser-py/src/lib.rs b/backend/parsers/windmill-parser-py/src/lib.rs index 88eee2ce45..4c523df0b2 100644 --- a/backend/parsers/windmill-parser-py/src/lib.rs +++ b/backend/parsers/windmill-parser-py/src/lib.rs @@ -93,22 +93,7 @@ pub fn parse_python_signature(code: &str) -> anyhow::Result { .as_arg() .annotation .as_ref() - .map_or(Typ::Unknown, |e| match e.as_ref() { - Expr::Name(ExprName { id, .. }) => match id.as_ref() { - "str" => Typ::Str(None), - "float" => Typ::Float, - "int" => Typ::Int, - "bool" => Typ::Bool, - "dict" => Typ::Object(vec![]), - "list" => Typ::List(Box::new(Typ::Str(None))), - "bytes" => Typ::Bytes, - "datetime" => Typ::Datetime, - "datetime.datetime" => Typ::Datetime, - "Sql" | "sql" => Typ::Sql, - _ => Typ::Resource(id.to_string()), - }, - _ => Typ::Unknown, - }); + .map_or(Typ::Unknown, |e| parse_expr(e)); if typ == Typ::Unknown && default.is_some() @@ -134,6 +119,58 @@ pub fn parse_python_signature(code: &str) -> anyhow::Result { } } +fn parse_expr(e: &Box) -> Typ { + match e.as_ref() { + Expr::Name(ExprName { id, .. }) => parse_typ(id.as_ref()), + Expr::Subscript(x) => match x.value.as_ref() { + Expr::Name(ExprName { id, .. }) => match id.as_str() { + "Literal" => { + let values = match x.slice.as_ref() { + Expr::Tuple(elts) => { + let v: Vec = elts + .elts + .iter() + .map(|x| match x { + Expr::Constant(c) => c.value.as_str().map(|x| x.to_string()), + _ => None, + }) + .filter_map(|x| x) + .collect(); + if v.is_empty() { + None + } else { + Some(v) + } + } + _ => None, + }; + Typ::Str(values) + } + "List" => Typ::List(Box::new(parse_expr(&x.slice))), + _ => Typ::Unknown, + }, + _ => Typ::Unknown, + }, + _ => Typ::Unknown, + } +} + +fn parse_typ(id: &str) -> Typ { + match id { + "str" => Typ::Str(None), + "float" => Typ::Float, + "int" => Typ::Int, + "bool" => Typ::Bool, + "dict" => Typ::Object(vec![]), + "list" => Typ::List(Box::new(Typ::Str(None))), + "bytes" => Typ::Bytes, + "datetime" => Typ::Datetime, + "datetime.datetime" => Typ::Datetime, + "Sql" | "sql" => Typ::Sql, + _ => Typ::Resource(id.to_string()), + } +} + fn to_value(et: &Expr) -> Option { match et { Expr::Constant(ExprConstant { value, .. }) => Some(constant_to_value(value)), @@ -366,4 +403,44 @@ def main(test1: str, Ok(()) } + + #[test] + fn test_parse_python_sig_4() -> anyhow::Result<()> { + let code = r#" + +import os + +def main(test1: Literal["foo", "bar"], test2: List[Literal["foo", "bar"]]): return + +"#; + //println!("{}", serde_json::to_string()?); + assert_eq!( + parse_python_signature(code)?, + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + otyp: None, + name: "test1".to_string(), + typ: Typ::Str(Some(vec!["foo".to_string(), "bar".to_string()])), + default: None, + has_default: false + }, + Arg { + otyp: None, + name: "test2".to_string(), + typ: Typ::List(Box::new(Typ::Str(Some(vec![ + "foo".to_string(), + "bar".to_string() + ])))), + default: None, + has_default: false + } + ] + } + ); + + Ok(()) + } } diff --git a/backend/parsers/windmill-parser-ts/Cargo.toml b/backend/parsers/windmill-parser-ts/Cargo.toml index 90282e2d87..915f03b0ac 100644 --- a/backend/parsers/windmill-parser-ts/Cargo.toml +++ b/backend/parsers/windmill-parser-ts/Cargo.toml @@ -22,4 +22,5 @@ swc_ecma_visit.workspace = true serde_json.workspace = true anyhow.workspace = true convert_case.workspace = true -regex.workspace = true \ No newline at end of file +regex.workspace = true +lazy_static.workspace = true diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs index 48ee7298b1..de4ff80095 100644 --- a/backend/parsers/windmill-parser-ts/src/lib.rs +++ b/backend/parsers/windmill-parser-ts/src/lib.rs @@ -284,12 +284,15 @@ fn binding_ident_to_arg(BindingIdent { id, type_ann }: &BindingIdent) -> (String (id.sym.to_string(), typ, nullable) } +lazy_static::lazy_static! { + static ref RE_SNK_CASE: Regex = Regex::new(r"_(\d)").unwrap(); +} + fn to_snake_case(s: &str) -> String { let r = s.to_case(Case::Snake); // s_3 => s3 - let re = Regex::new(r"_(\d)").unwrap(); - re.replace_all(&r, "$1").to_string() + RE_SNK_CASE.replace_all(&r, "$1").to_string() } fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) { diff --git a/backend/parsers/windmill-parser-wasm/pkg/package.json b/backend/parsers/windmill-parser-wasm/pkg/package.json index 7eda77ef23..1cf537d852 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.226.9", + "version": "1.237.0", "files": [ "windmill_parser_wasm_bg.wasm", "windmill_parser_wasm.js", @@ -14,4 +14,4 @@ "sideEffects": [ "./snippets/*" ] -} +} \ No newline at end of file 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 c7d22ad032..c145179c12 100644 --- a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js +++ b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js @@ -538,6 +538,10 @@ async function __wbg_load(module, imports) { function __wbg_get_imports() { const imports = {}; imports.wbg = {}; + imports.wbg.__wbg_eval_ff4183ac1495b791 = function(arg0, arg1) { + const ret = eval(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; imports.wbg.__wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); }; @@ -589,10 +593,6 @@ function __wbg_get_imports() { const ret = getObject(arg0) in getObject(arg1); return ret; }; - imports.wbg.__wbg_eval_596393dc5ae50a1b = function(arg0, arg1) { - const ret = eval(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }; imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) { const ret = getObject(arg0) == getObject(arg1); return ret; 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 6d833fa19a..9ca2d1c831 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/Cargo.toml b/backend/windmill-api/Cargo.toml index a3a3033d95..e0734c4394 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -9,7 +9,8 @@ name = "windmill_api" path = "src/lib.rs" [features] -enterprise = ["windmill-queue/enterprise", "async-stripe", "windmill-audit/enterprise", "windmill-git-sync/enterprise"] +enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise"] +stripe = ["async-stripe"] enterprise_saml = ["samael"] benchmark = [] diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index b1e0ba4a88..fd9bd9bd41 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -6,7 +6,7 @@ * LICENSE-AGPL for a copy of the license. */ -#[cfg(feature = "enterprise")] +#[cfg(feature = "stripe")] use std::str::FromStr; use crate::db::ApiAuthed; @@ -21,7 +21,7 @@ use crate::{ variables::build_crypt, webhook_util::{InstanceEvent, WebhookShared}, }; -#[cfg(feature = "enterprise")] +#[cfg(feature = "stripe")] use axum::response::Redirect; use axum::{ body::StreamBody, @@ -32,10 +32,10 @@ use axum::{ Json, Router, }; use chrono::Utc; -#[cfg(feature = "enterprise")] +#[cfg(feature = "stripe")] use chrono::{Datelike, TimeZone, Timelike}; use magic_crypt::MagicCryptTrait; -#[cfg(feature = "enterprise")] +#[cfg(feature = "stripe")] use stripe::CustomerId; use uuid::Uuid; use windmill_audit::{audit_log, ActionKind}; @@ -83,7 +83,6 @@ pub fn workspaced_service() -> Router { .route("/edit_deploy_to", post(edit_deploy_to)) .route("/tarball", get(tarball_workspace)) .route("/is_premium", get(is_premium)) - .route("/premium_info", get(premium_info)) .route("/edit_copilot_config", post(edit_copilot_config)) .route("/get_copilot_info", get(get_copilot_info)) .route("/edit_error_handler", post(edit_error_handler)) @@ -94,7 +93,7 @@ pub fn workspaced_service() -> Router { .route("/edit_git_sync_config", post(edit_git_sync_config)) .route("/leave", post(leave_workspace)); - #[cfg(feature = "enterprise")] + #[cfg(feature = "stripe")] { if STRIPE_KEY.is_none() { return router; @@ -102,12 +101,13 @@ pub fn workspaced_service() -> Router { tracing::info!("stripe enabled"); return router + .route("/premium_info", get(premium_info)) .route("/checkout", get(stripe_checkout)) .route("/billing_portal", get(stripe_portal)); } } - #[cfg(not(feature = "enterprise"))] + #[cfg(not(feature = "stripe"))] router } pub fn global_service() -> Router { @@ -324,6 +324,7 @@ pub struct PremiumWorkspaceInfo { pub usage: Option, pub seats: Option, } +#[cfg(feature = "stripe")] async fn premium_info( authed: ApiAuthed, Extension(db): Extension, @@ -395,14 +396,14 @@ async fn premium_info( Ok(Json(result)) } -#[cfg(feature = "enterprise")] +#[cfg(feature = "stripe")] #[derive(Deserialize)] struct PlanQuery { plan: String, seats: Option, } -#[cfg(feature = "enterprise")] +#[cfg(feature = "stripe")] async fn stripe_checkout( authed: ApiAuthed, Path(w_id): Path, @@ -491,7 +492,7 @@ async fn stripe_checkout( } } -#[cfg(feature = "enterprise")] +#[cfg(feature = "stripe")] async fn stripe_portal( authed: ApiAuthed, Path(w_id): Path, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c421651cb0..e11100e4f0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -48,7 +48,7 @@ "vscode-languageclient": "~9.0.1", "vscode-uri": "~3.0.8", "vscode-ws-jsonrpc": "~3.1.0", - "windmill-parser-wasm": "^1.226.9", + "windmill-parser-wasm": "^1.237.0", "y-monaco": "^0.1.4", "y-websocket": "^1.5.0", "yaml": "^2.3.4", @@ -9585,9 +9585,9 @@ } }, "node_modules/windmill-parser-wasm": { - "version": "1.226.9", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.226.9.tgz", - "integrity": "sha512-yOlLjUF4NlRutZhHrRa4fhxuxLslMaVXBojY3QyIVLlSq+7SGIUTYpEb04XrwpcCNyrgMIL1cNYaq4qzGDKsxA==" + "version": "1.237.0", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.237.0.tgz", + "integrity": "sha512-MGO9rkSnkFMOkkkEr/whFzkbyeHX4E6JwYLazlDgyprNNibpEi7GXcOeuz+QgbzDNrTLo/sYN6bYqcHt4Jk6dQ==" }, "node_modules/wordwrap": { "version": "1.0.0", diff --git a/frontend/package.json b/frontend/package.json index f2925a4615..f9b27f63ad 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -131,7 +131,7 @@ "vscode-languageclient": "~9.0.1", "vscode-uri": "~3.0.8", "vscode-ws-jsonrpc": "~3.1.0", - "windmill-parser-wasm": "^1.226.9", + "windmill-parser-wasm": "^1.237.0", "y-monaco": "^0.1.4", "y-websocket": "^1.5.0", "yaml": "^2.3.4", diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 66ea1563b8..421eb1ca69 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -27,7 +27,6 @@ import DateTimeInput from './DateTimeInput.svelte' import S3FilePicker from './S3FilePicker.svelte' import CurrencyInput from './apps/components/inputs/currency/CurrencyInput.svelte' - import Label from './Label.svelte' export let label: string = '' export let value: any @@ -234,13 +233,6 @@ {#if type == 'array'} - {:else if type == 'string' || ['number', 'integer', 'object'].includes(type ?? '')}
@@ -356,7 +348,7 @@ {/if} {:else if inputCat == 'list'}
- {#if Array.isArray(itemsType?.multiselect)} + {#if Array.isArray(itemsType?.multiselect) && Array.isArray(value)}
- {:else if extra.multiselect && itemsType?.enum != undefined} + {:else if itemsType?.enum != undefined && Array.isArray(itemsType?.enum) && Array.isArray(value)}
+ import { Plus, X } from 'lucide-svelte' import { Button } from './common' + import { fade } from 'svelte/transition' export let itemsType: | { @@ -48,22 +50,27 @@ Enums
{#each itemsType?.enum || [] as e} -
+
- +
+ +
{/each}
-
+
+ +