diff --git a/backend/Cargo.lock b/backend/Cargo.lock index dfee10ac35..125e1a2647 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -7265,6 +7265,17 @@ dependencies = [ "windmill-parser", ] +[[package]] +name = "windmill-parser-graphql" +version = "1.143.0" +dependencies = [ + "anyhow", + "lazy_static", + "regex", + "serde_json", + "windmill-parser", +] + [[package]] name = "windmill-parser-py" version = "1.143.0" @@ -7331,6 +7342,7 @@ dependencies = [ "windmill-parser", "windmill-parser-bash", "windmill-parser-go", + "windmill-parser-graphql", "windmill-parser-py", "windmill-parser-sql", "windmill-parser-ts", @@ -7413,6 +7425,7 @@ dependencies = [ "windmill-parser", "windmill-parser-bash", "windmill-parser-go", + "windmill-parser-graphql", "windmill-parser-py", "windmill-parser-py-imports", "windmill-parser-sql", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3dbdd5f6d1..a45a6c3165 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -85,6 +85,7 @@ windmill-parser-py-imports = { path = "./parsers/windmill-parser-py-imports" } windmill-parser-go = { path = "./parsers/windmill-parser-go" } windmill-parser-bash = { path = "./parsers/windmill-parser-bash" } windmill-parser-sql = { path = "./parsers/windmill-parser-sql" } +windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" } axum = { version = "^0", features = ["headers"] } headers = "^0" diff --git a/backend/migrations/20230807132313_add_graphql_lang.down.sql b/backend/migrations/20230807132313_add_graphql_lang.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20230807132313_add_graphql_lang.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20230807132313_add_graphql_lang.up.sql b/backend/migrations/20230807132313_add_graphql_lang.up.sql new file mode 100644 index 0000000000..c6ee654574 --- /dev/null +++ b/backend/migrations/20230807132313_add_graphql_lang.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'graphql'; \ No newline at end of file diff --git a/backend/parsers/windmill-parser-graphql/Cargo.toml b/backend/parsers/windmill-parser-graphql/Cargo.toml new file mode 100644 index 0000000000..da1a8bf9c6 --- /dev/null +++ b/backend/parsers/windmill-parser-graphql/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "windmill-parser-graphql" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[lib] +name = "windmill_parser_graphql" +path = "./src/lib.rs" + +[dependencies] +windmill-parser.workspace = true +anyhow.workspace = true +regex.workspace = true +lazy_static.workspace = true +serde_json.workspace = true \ No newline at end of file diff --git a/backend/parsers/windmill-parser-graphql/src/lib.rs b/backend/parsers/windmill-parser-graphql/src/lib.rs new file mode 100644 index 0000000000..365a4e2074 --- /dev/null +++ b/backend/parsers/windmill-parser-graphql/src/lib.rs @@ -0,0 +1,111 @@ +#![allow(non_snake_case)] // TODO: switch to parse_* function naming + +use anyhow::anyhow; +use regex::Regex; +use serde_json::json; + +use windmill_parser::{Arg, MainArgSignature, Typ}; + +pub fn parse_graphql_sig(code: &str) -> anyhow::Result { + let parsed = parse_graphql_file(&code)?; + if let Some(x) = parsed { + let args = x; + Ok(MainArgSignature { star_args: false, star_kwargs: false, args }) + } else { + Err(anyhow!("Error parsing sql".to_string())) + } +} + +lazy_static::lazy_static! { + static ref RE_ARG_GRAPHQL: Regex = Regex::new(r#"\$(\w+)\s*:\s*(?:(\w+)!?|\[(\w+)!?\])!?\s*(?:=\s*(\w+)\s*)?"#).unwrap(); + static ref RE_ARG_GRAPHQL_ARRAY: Regex = Regex::new(r#"^\[(\w+)!?\]!?$"#).unwrap(); +} + +fn parse_graphql_file(code: &str) -> anyhow::Result>> { + let mut args: Vec = vec![]; + + for cap in RE_ARG_GRAPHQL.captures_iter(code) { + let name = cap.get(1).map(|x| x.as_str().to_string()).unwrap(); + let mut typ = cap.get(2).map(|x| x.as_str().to_string()); + + let parsed_typ = if typ.is_none() { + let inner_typ = cap.get(3).map(|x| x.as_str().to_string()); + typ = inner_typ.clone().map(|x| format!("[{}]", x.to_string())); + Typ::List(Box::new(parse_graphql_typ(inner_typ.unwrap().as_str()))) + } else { + parse_graphql_typ(typ.clone().unwrap().as_str()) + }; + + let default = cap.get(4).map(|x| x.as_str().to_string()); + + let has_default = default.is_some(); + + let parsed_default = default.and_then(|x| match parsed_typ { + Typ::Int => x.parse::().ok().map(|x| json!(x)), + Typ::Float => x.parse::().ok().map(|x| json!(x)), + _ => Some(json!(x)), + }); + args.push(Arg { + name, + typ: parsed_typ, + default: parsed_default, + otyp: Some(typ.unwrap()), + has_default, + }); + } + + Ok(Some(args)) +} + +pub fn parse_graphql_typ(typ: &str) -> Typ { + match typ { + "String" | "ID" => Typ::Str(None), + "Int" => Typ::Int, + "Boolean" => Typ::Bool, + "Float" => Typ::Float, + _ => Typ::Object(vec![]), + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_parse_graphql_sig() -> anyhow::Result<()> { + let code = r#" +query($s: String, $arr: [String]) { + books { + title + } +} +"#; + //println!("{}", serde_json::to_string()?); + assert_eq!( + parse_graphql_sig(code)?, + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![ + Arg { + otyp: Some("String".to_string()), + name: "s".to_string(), + typ: Typ::Str(None), + default: None, + has_default: false + }, + Arg { + otyp: Some("[String]".to_string()), + name: "arr".to_string(), + typ: Typ::List(Box::new(Typ::Str(None))), + default: None, + has_default: false + }, + ] + } + ); + + Ok(()) + } +} diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml index 033e961461..ff86fc3a97 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.toml +++ b/backend/parsers/windmill-parser-wasm/Cargo.toml @@ -21,6 +21,7 @@ windmill-parser-bash.workspace = true windmill-parser-sql.workspace = true windmill-parser-py.workspace = true windmill-parser-ts.workspace = true +windmill-parser-graphql.workspace = true wasm-bindgen.workspace = true serde_json.workspace = true getrandom = { workspace = true, features = ["js"] } diff --git a/backend/parsers/windmill-parser-wasm/pkg/package.json b/backend/parsers/windmill-parser-wasm/pkg/package.json index e9d5d2709a..9baba9bf40 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.138.1", + "version": "1.141.0", "files": [ "windmill_parser_wasm_bg.wasm", "windmill_parser_wasm.js", diff --git a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.d.ts b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.d.ts index 40d3f8e5ac..4a8e39017b 100644 --- a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.d.ts +++ b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.d.ts @@ -40,6 +40,11 @@ export function parse_bigquery(code: string): string; * @returns {string} */ export function parse_snowflake(code: string): string; +/** +* @param {string} code +* @returns {string} +*/ +export function parse_graphql(code: string): string; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -53,6 +58,7 @@ export interface InitOutput { readonly parse_mysql: (a: number, b: number, c: number) => void; readonly parse_bigquery: (a: number, b: number, c: number) => void; readonly parse_snowflake: (a: number, b: number, c: number) => void; + readonly parse_graphql: (a: number, b: number, c: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; 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 66f8267a18..7eac3e39af 100644 --- a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js +++ b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm.js @@ -381,6 +381,29 @@ export function parse_snowflake(code) { } } +/** +* @param {string} code +* @returns {string} +*/ +export function parse_graphql(code) { + let deferred2_0; + let deferred2_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + wasm.parse_graphql(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + deferred2_0 = r0; + deferred2_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + function handleError(f, args) { try { return f.apply(this, args); @@ -423,7 +446,7 @@ async function __wbg_load(module, imports) { function __wbg_get_imports() { const imports = {}; imports.wbg = {}; - imports.wbg.__wbg_eval_d972bbef37d2cd5a = function(arg0, arg1) { + imports.wbg.__wbg_eval_9331e2bd3095f3d6 = function(arg0, arg1) { const ret = eval(getStringFromWasm0(arg0, arg1)); return addHeapObject(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 e6966037fb..bc52747ad5 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/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm.d.ts b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm.d.ts index fb8cc99df7..ed02311c3e 100644 --- a/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm.d.ts +++ b/backend/parsers/windmill-parser-wasm/pkg/windmill_parser_wasm_bg.wasm.d.ts @@ -9,6 +9,7 @@ export function parse_sql(a: number, b: number, c: number): void; export function parse_mysql(a: number, b: number, c: number): void; export function parse_bigquery(a: number, b: number, c: number): void; export function parse_snowflake(a: number, b: number, c: number): void; +export function parse_graphql(a: number, b: number, c: number): void; export function __wbindgen_malloc(a: number, b: number): number; export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; export function __wbindgen_add_to_stack_pointer(a: number): number; diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index e22812a34c..fb05db3d36 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -49,3 +49,8 @@ pub fn parse_bigquery(code: &str) -> String { pub fn parse_snowflake(code: &str) -> String { wrap_sig(windmill_parser_sql::parse_snowflake_sig(code)) } + +#[wasm_bindgen] +pub fn parse_graphql(code: &str) -> String { + wrap_sig(windmill_parser_graphql::parse_graphql_sig(code)) +} diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 4563b20e2a..987ddcffb8 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -1478,6 +1478,7 @@ async fn tarball_workspace( ScriptLang::Mysql => "my.sql", ScriptLang::Bigquery => "bq.sql", ScriptLang::Snowflake => "sf.sql", + ScriptLang::Graphql => "gql", ScriptLang::Nativets => "fetch.ts", ScriptLang::Bun => "bun.ts", }; diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 7f3a40828c..9739940c32 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -35,6 +35,7 @@ pub enum ScriptLang { Mysql, Bigquery, Snowflake, + Graphql, } impl ScriptLang { @@ -50,6 +51,7 @@ impl ScriptLang { ScriptLang::Mysql => "mysql", ScriptLang::Bigquery => "bigquery", ScriptLang::Snowflake => "snowflake", + ScriptLang::Graphql => "graphql", } } } diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 091a383ea4..6ec512cbb8 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -76,6 +76,7 @@ lazy_static::lazy_static! { "postgresql".to_string(), "bigquery".to_string(), "snowflake".to_string(), + "graphql".to_string(), "dependency".to_string(), "flow".to_string(), "hub".to_string(), diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index 9bb1c7cad1..f90313f58e 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -30,6 +30,7 @@ windmill-parser-py.workspace = true windmill-parser-py-imports.workspace = true windmill-parser-bash.workspace = true windmill-parser-sql.workspace = true +windmill-parser-graphql.workspace = true sqlx.workspace = true uuid.workspace = true tracing.workspace = true diff --git a/backend/windmill-worker/src/bigquery_executor.rs b/backend/windmill-worker/src/bigquery_executor.rs index bd79dfc83c..88cb93d86d 100644 --- a/backend/windmill-worker/src/bigquery_executor.rs +++ b/backend/windmill-worker/src/bigquery_executor.rs @@ -6,7 +6,7 @@ use windmill_queue::HTTP_CLIENT; use serde::Deserialize; -use crate::{get_content, transform_json_value, AuthedClient, JobCompleted}; +use crate::{transform_json_value, AuthedClient, JobCompleted}; use gcp_auth::{AuthenticationManager, CustomServiceAccount}; @@ -54,7 +54,7 @@ struct BigqueryError { pub async fn do_bigquery( job: QueuedJob, client: &AuthedClient, - db: &sqlx::Pool, + query: &str, ) -> windmill_common::error::Result { let args = if let Some(args) = &job.args { Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?) @@ -94,8 +94,6 @@ pub async fn do_bigquery( let mut statement_values: Vec = vec![]; - let query = get_content(&job, db).await?; - let sig = parse_bigquery_sig(&query) .map_err(|x| Error::ExecutionErr(x.to_string()))? .args; diff --git a/backend/windmill-worker/src/graphql_executor.rs b/backend/windmill-worker/src/graphql_executor.rs new file mode 100644 index 0000000000..c46b6591a6 --- /dev/null +++ b/backend/windmill-worker/src/graphql_executor.rs @@ -0,0 +1,89 @@ +use serde_json::{json, Value}; +use windmill_common::error::Error; +use windmill_common::jobs::QueuedJob; +use windmill_queue::HTTP_CLIENT; + +use serde::Deserialize; + +use crate::{transform_json_value, AuthedClient, JobCompleted}; + +#[derive(Deserialize)] +struct GraphqlDatabase { + bearer_token: Option, + base_url: String, +} + +#[derive(Deserialize)] +struct GraphqlResponse { + data: Option, + errors: Option>, +} + +#[derive(Deserialize)] +struct GraphqlError { + message: String, +} + +pub async fn do_graphql( + job: QueuedJob, + client: &AuthedClient, + query: &str, +) -> windmill_common::error::Result { + let args = if let Some(args) = &job.args { + Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?) + } else { + None + }; + + let graphql_args: serde_json::Value = serde_json::from_value(args.unwrap_or_else(|| json!({}))) + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + let database = serde_json::from_value::( + graphql_args.get("database").unwrap_or(&json!({})).clone(), + ) + .map_err(|e: serde_json::Error| Error::ExecutionErr(e.to_string()))?; + + let args = &job + .args + .clone() + .unwrap_or_else(|| json!({})) + .as_object() + .map(|x| x.to_owned()) + .unwrap_or_else(|| json!({}).as_object().unwrap().to_owned()); + + let mut request = HTTP_CLIENT.post(database.base_url).json(&json!({ + "query": query, + "variables": args + })); + + if let Some(token) = &database.bearer_token { + request = request.bearer_auth(token.as_str()); + } + + let response = request + .send() + .await + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + + let result = response + .json::() + .await + .map_err(|e| Error::ExecutionErr(e.to_string()))?; + + if let Some(errors) = result.errors { + return Err(Error::ExecutionErr( + errors + .into_iter() + .map(|x| x.message) + .collect::>() + .join("\n"), + )); + } + + // And then check that we got back the same string we sent over. + return Ok(JobCompleted { + job: job, + result: result.data.unwrap_or(json!({})), + logs: "".to_string(), + success: true, + }); +} diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index a172374861..4cac3fce4a 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -6,6 +6,7 @@ mod snowflake_executor; mod common; mod global_cache; mod go_executor; +mod graphql_executor; mod js_eval; mod mysql_executor; mod pg_executor; diff --git a/backend/windmill-worker/src/snowflake_executor.rs b/backend/windmill-worker/src/snowflake_executor.rs index d2a5328345..7802184e5b 100644 --- a/backend/windmill-worker/src/snowflake_executor.rs +++ b/backend/windmill-worker/src/snowflake_executor.rs @@ -12,7 +12,7 @@ use windmill_queue::HTTP_CLIENT; use serde::{Deserialize, Serialize}; -use crate::{get_content, transform_json_value, AuthedClient, JobCompleted}; +use crate::{transform_json_value, AuthedClient, JobCompleted}; #[derive(Serialize)] struct Claims { @@ -63,7 +63,7 @@ struct SnowflakeError { pub async fn do_snowflake( job: QueuedJob, client: &AuthedClient, - db: &sqlx::Pool, + query: &str, ) -> windmill_common::error::Result { let args = if let Some(args) = &job.args { Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?) @@ -114,8 +114,6 @@ pub async fn do_snowflake( .map(|x| x.to_owned()) .unwrap_or_else(|| json!({}).as_object().unwrap().to_owned()); - let query = get_content(&job, db).await?; - let mut bindings = serde_json::Map::new(); let sig = parse_snowflake_sig(&query) .map_err(|x| Error::ExecutionErr(x.to_string()))? diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 536486f3d0..16360c7db8 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -66,7 +66,7 @@ use windmill_queue::{add_completed_job, add_completed_job_error,IDLE_WORKERS}; use crate::{ worker_flow::{ handle_flow, update_flow_status_after_job_completion, update_flow_status_in_progress, - }, python_executor::{create_dependencies_dir, pip_compile, handle_python_job, handle_python_reqs}, common::{read_result, set_logs}, go_executor::{handle_go_job, install_go_dependencies}, js_eval::{transpile_ts, eval_fetch_timeout}, pg_executor::do_postgresql, mysql_executor::do_mysql, + }, python_executor::{create_dependencies_dir, pip_compile, handle_python_job, handle_python_reqs}, common::{read_result, set_logs}, go_executor::{handle_go_job, install_go_dependencies}, js_eval::{transpile_ts, eval_fetch_timeout}, pg_executor::do_postgresql, mysql_executor::do_mysql, graphql_executor::do_graphql, }; #[cfg(feature = "enterprise")] @@ -361,6 +361,7 @@ pub async fn run_worker = all_langs.clone().into_iter().map(|x| (x.clone(), prometheus::register_histogram!( @@ -1343,7 +1344,7 @@ async fn handle_code_execution_job( #[cfg(feature = "enterprise")] { - let jc = do_bigquery(job.clone(), &client.get_authed().await, &db).await?; + let jc = do_bigquery(job.clone(), &client.get_authed().await, &inner_content).await?; return Ok(jc.result) } } else if language == Some(ScriptLang::Snowflake) { @@ -1354,9 +1355,12 @@ async fn handle_code_execution_job( #[cfg(feature = "enterprise")] { - let jc = do_snowflake(job.clone(), &client.get_authed().await, &db).await?; + let jc = do_snowflake(job.clone(), &client.get_authed().await, &inner_content).await?; return Ok(jc.result) } + } else if language == Some(ScriptLang::Graphql) { + let jc = do_graphql(job.clone(), &client.get_authed().await, &inner_content).await?; + return Ok(jc.result) } else if language == Some(ScriptLang::Nativets) { logs.push_str("\n--- FETCH TS EXECUTION ---\n"); let code = format!("const BASE_URL = '{base_internal_url}';\nconst WM_TOKEN = '{}';\n{}", &client.get_token().await, inner_content); @@ -2244,6 +2248,7 @@ async fn capture_dependency_job( ScriptLang::Mysql => Ok("".to_owned()), ScriptLang::Bigquery => Ok("".to_owned()), ScriptLang::Snowflake => Ok("".to_owned()), + ScriptLang::Graphql => Ok("".to_owned()), ScriptLang::Bash => Ok("".to_owned()), ScriptLang::Nativets => Ok("".to_owned()), diff --git a/cli/dev.ts b/cli/dev.ts index c98c72f998..02c6df8795 100644 --- a/cli/dev.ts +++ b/cli/dev.ts @@ -64,6 +64,8 @@ async function dev(opts: GlobalOptions & { filter?: string }) { : splitted.length > 2 && splitted[splitted.length - 2] == "sf" ? "snowflake" : "postgresql" + : ext == "gql" + ? "graphql" : "unknown"; currentLastEdit = { content, diff --git a/cli/sync.ts b/cli/sync.ts index b63b8d920d..5b13aa1de1 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -137,6 +137,7 @@ function ZipFSElement(zip: JSZip, useYaml: boolean): DynFSElement { else if (language == "mysql") ext = "my.sql"; else if (language == "bigquery") ext = "bq.sql"; else if (language == "snowflake") ext = "sf.sql"; + else if (language == "graphql") ext = "gql"; else if (language == "bun") ext = "bun.ts"; else if (language == "nativets") ext = "native.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 42900f98e7..f77d35c906 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,9 +26,11 @@ "diff": "^5.1.0", "esm-env": "^1.0.0", "fast-equals": "^5.0.1", + "graphql": "^16.7.1", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lucide-svelte": "^0.246.0", + "monaco-graphql": "^1.3.0", "monaco-languageclient": "~6.0.3", "openai": "^4.0.0-beta.4", "quill": "^1.3.7", @@ -41,7 +43,7 @@ "svelte-timezone-picker": "^2.0.3", "tailwind-merge": "^1.13.2", "vscode-ws-jsonrpc": "3.0.0", - "windmill-parser-wasm": "^1.138.1", + "windmill-parser-wasm": "^1.141.0", "y-monaco": "^0.1.4", "y-websocket": "^1.5.0", "yjs": "^13.6.7" @@ -99,6 +101,7 @@ "tslib": "^2.6.0", "typescript": "^5.1.3", "vite": "^4.4.7", + "vite-plugin-monaco-editor": "^1.1.0", "yootils": "^0.3.1" }, "peerDependencies": { @@ -4342,6 +4345,29 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphql": { + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-language-service": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.7.tgz", + "integrity": "sha512-xkawYMJeoNYGhT+SpSH3c2qf6HpGHQ/duDmrseVHBpVCrXAiGnliXGSCC4jyMGgZQ05GytsZ12p0nUo7s6lSSw==", + "dependencies": { + "nullthrows": "^1.0.0", + "vscode-languageserver-types": "^3.17.1" + }, + "bin": { + "graphql": "dist/temp-bin.js" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0" + } + }, "node_modules/hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", @@ -6315,6 +6341,20 @@ "integrity": "sha512-zhbZ2Nx93tLR8aJmL2zI1mhJpsl87HMebNBM6R8z4pLfs8pj604pIVIVwyF1TivcfNtIPpMXL+nb3DsBmE/x6Q==", "dev": true }, + "node_modules/monaco-graphql": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/monaco-graphql/-/monaco-graphql-1.3.0.tgz", + "integrity": "sha512-Ilz7kUKYfO5KGwandjR+eQbN+fj1fQY9OECIQlzgeWOkLXMZqXTsxf01iQfVw08mMsBz7qa6fZKsESouDcBN2g==", + "dependencies": { + "graphql-language-service": "^5.1.7", + "picomatch-browser": "^2.2.6" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0", + "monaco-editor": ">= 0.20.0 < 1", + "prettier": "^2.8.0 || ^3.0.0" + } + }, "node_modules/monaco-languageclient": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-6.0.3.tgz", @@ -6564,6 +6604,11 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -6911,6 +6956,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/picomatch-browser": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz", + "integrity": "sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -7549,7 +7605,6 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, "bin": { "prettier": "bin-prettier.js" }, @@ -9616,6 +9671,15 @@ } } }, + "node_modules/vite-plugin-monaco-editor": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz", + "integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==", + "dev": true, + "peerDependencies": { + "monaco-editor": ">=0.33.0" + } + }, "node_modules/vitefu": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", @@ -9777,9 +9841,9 @@ } }, "node_modules/windmill-parser-wasm": { - "version": "1.138.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.138.1.tgz", - "integrity": "sha512-KGbiKKk7i8xRV+kcGXNRChIVXRQRAQ6eAGVJo7c2qjq+Sft3cGteWbkgeh8gjU5+KdmnP866jGwsRtw+zW2+5A==" + "version": "1.141.0", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.141.0.tgz", + "integrity": "sha512-2U4wAyKOMa7YTcWGUSCCqhNEfI+dVF9JvlXzPneOUO9nD7zm5uXRB+xaJLHKtYB5gmzLz1cQElp/FpbJaLLZsw==" }, "node_modules/wordwrap": { "version": "1.0.0", @@ -13005,6 +13069,20 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "graphql": { + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==" + }, + "graphql-language-service": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.7.tgz", + "integrity": "sha512-xkawYMJeoNYGhT+SpSH3c2qf6HpGHQ/duDmrseVHBpVCrXAiGnliXGSCC4jyMGgZQ05GytsZ12p0nUo7s6lSSw==", + "requires": { + "nullthrows": "^1.0.0", + "vscode-languageserver-types": "^3.17.1" + } + }, "hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", @@ -14357,6 +14435,15 @@ } } }, + "monaco-graphql": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/monaco-graphql/-/monaco-graphql-1.3.0.tgz", + "integrity": "sha512-Ilz7kUKYfO5KGwandjR+eQbN+fj1fQY9OECIQlzgeWOkLXMZqXTsxf01iQfVw08mMsBz7qa6fZKsESouDcBN2g==", + "requires": { + "graphql-language-service": "^5.1.7", + "picomatch-browser": "^2.2.6" + } + }, "monaco-languageclient": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-6.0.3.tgz", @@ -14524,6 +14611,11 @@ "boolbase": "^1.0.0" } }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -14794,6 +14886,11 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "picomatch-browser": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz", + "integrity": "sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -15183,8 +15280,7 @@ "prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==" }, "prettier-plugin-svelte": { "version": "2.10.1", @@ -16641,6 +16737,13 @@ "rollup": "^3.25.2" } }, + "vite-plugin-monaco-editor": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz", + "integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==", + "dev": true, + "requires": {} + }, "vitefu": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", @@ -16766,9 +16869,9 @@ } }, "windmill-parser-wasm": { - "version": "1.138.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.138.1.tgz", - "integrity": "sha512-KGbiKKk7i8xRV+kcGXNRChIVXRQRAQ6eAGVJo7c2qjq+Sft3cGteWbkgeh8gjU5+KdmnP866jGwsRtw+zW2+5A==" + "version": "1.141.0", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.141.0.tgz", + "integrity": "sha512-2U4wAyKOMa7YTcWGUSCCqhNEfI+dVF9JvlXzPneOUO9nD7zm5uXRB+xaJLHKtYB5gmzLz1cQElp/FpbJaLLZsw==" }, "wordwrap": { "version": "1.0.0", diff --git a/frontend/package.json b/frontend/package.json index a9ac3372bb..5306694d3b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,6 +68,7 @@ "tslib": "^2.6.0", "typescript": "^5.1.3", "vite": "^4.4.7", + "vite-plugin-monaco-editor": "^1.1.0", "yootils": "^0.3.1" }, "type": "module", @@ -89,9 +90,11 @@ "diff": "^5.1.0", "esm-env": "^1.0.0", "fast-equals": "^5.0.1", + "graphql": "^16.7.1", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lucide-svelte": "^0.246.0", + "monaco-graphql": "^1.3.0", "monaco-languageclient": "~6.0.3", "openai": "^4.0.0-beta.4", "quill": "^1.3.7", @@ -104,7 +107,7 @@ "svelte-timezone-picker": "^2.0.3", "tailwind-merge": "^1.13.2", "vscode-ws-jsonrpc": "3.0.0", - "windmill-parser-wasm": "^1.138.1", + "windmill-parser-wasm": "^1.141.0", "y-monaco": "^0.1.4", "y-websocket": "^1.5.0", "yjs": "^13.6.7" diff --git a/frontend/src/lib/components/DBSchemaExplorer.svelte b/frontend/src/lib/components/DBSchemaExplorer.svelte index 098a705519..c1920bda6c 100644 --- a/frontend/src/lib/components/DBSchemaExplorer.svelte +++ b/frontend/src/lib/components/DBSchemaExplorer.svelte @@ -1,6 +1,6 @@ diff --git a/frontend/src/lib/components/GraphqlSchemaViewer.svelte b/frontend/src/lib/components/GraphqlSchemaViewer.svelte new file mode 100644 index 0000000000..2bd524fdea --- /dev/null +++ b/frontend/src/lib/components/GraphqlSchemaViewer.svelte @@ -0,0 +1,40 @@ + + +
diff --git a/frontend/src/lib/components/ResourcePicker.svelte b/frontend/src/lib/components/ResourcePicker.svelte index dbf5d1d07e..103072b39e 100644 --- a/frontend/src/lib/components/ResourcePicker.svelte +++ b/frontend/src/lib/components/ResourcePicker.svelte @@ -9,7 +9,7 @@ import AppConnect from './AppConnect.svelte' import { Button } from './common' import ResourceEditor from './ResourceEditor.svelte' - import DbSchemaExplorer from './DBSchemaExplorer.svelte' + import DBSchemaExplorer from './DBSchemaExplorer.svelte' import DarkModeObserver from './DarkModeObserver.svelte' const dispatch = createEventDispatcher() @@ -143,5 +143,5 @@
- + diff --git a/frontend/src/lib/components/ResultJobLoader.svelte b/frontend/src/lib/components/ResultJobLoader.svelte index 052327f123..231a219607 100644 --- a/frontend/src/lib/components/ResultJobLoader.svelte +++ b/frontend/src/lib/components/ResultJobLoader.svelte @@ -4,6 +4,7 @@ import { onDestroy, tick } from 'svelte' import type { Preview } from '$lib/gen/models/Preview' import { createEventDispatcher } from 'svelte' + import type { SupportedLanguage } from '$lib/common' export let isLoading = false export let job: { completed: boolean; result: any; id: string } | undefined = undefined @@ -84,7 +85,7 @@ export async function runPreview( path: string | undefined, code: string, - lang: 'deno' | 'go' | 'python3' | 'bash' | 'nativets', + lang: SupportedLanguage, args: Record, tag: string | undefined ): Promise { diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index c3fc8b95f6..263ddedbc7 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -71,6 +71,7 @@ langs.push(['MySQL', Script.language.MYSQL]) langs.push(['BigQuery', Script.language.BIGQUERY]) langs.push(['Snowflake', Script.language.SNOWFLAKE]) + langs.push(['GraphQL', Script.language.GRAPHQL]) if (SCRIPT_SHOW_GO) { langs.push(['Go', Script.language.GO]) } diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index aeda58b769..4319c8893f 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -15,6 +15,7 @@ import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution' import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution' import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution' + import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution' import 'monaco-editor/esm/vs/language/json/monaco.contribution' import 'monaco-editor/esm/vs/language/typescript/monaco.contribution' diff --git a/frontend/src/lib/components/TestConnection.svelte b/frontend/src/lib/components/TestConnection.svelte index e0eea78ea6..b413620564 100644 --- a/frontend/src/lib/components/TestConnection.svelte +++ b/frontend/src/lib/components/TestConnection.svelte @@ -53,6 +53,10 @@ export async function main(database: any) { snowflake: { code: `select 1`, lang: 'snowflake' + }, + graphql: { + code: '{ __typename }', + lang: 'graphql' } } diff --git a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte index 2bb5b4961c..2d7bd9a40d 100644 --- a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte +++ b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte @@ -103,6 +103,7 @@ ['mysql', 'MySQL'], ['bigquery', 'BigQuery'], ['snowflake', 'Snowflake'], + ['graphql', 'GraphQL'], ['bun', 'TypeScript (Bun)'] ] as [Script.language, string][] diff --git a/frontend/src/lib/components/build_workers.ts b/frontend/src/lib/components/build_workers.ts index be784a674f..a8a4decdf5 100644 --- a/frontend/src/lib/components/build_workers.ts +++ b/frontend/src/lib/components/build_workers.ts @@ -74,6 +74,13 @@ export function buildWorkerDefinition( return buildWorker(workerOverrideGlobals, label, 'cssWorker', 'CSS Worker') case 'json': return buildWorker(workerOverrideGlobals, label, 'jsonWorker', 'JSON Worker') + case 'graphql': + const workerFilename = `graphql.worker.bundle.js` + const workerPathLocal = `${workerOverrideGlobals.workerPath}/${workerFilename}` + const workerUrl = new URL(workerPathLocal, workerOverrideGlobals.basePath) + return new Worker(workerUrl.href, { + name: label + }) default: return buildWorker(workerOverrideGlobals, label, 'editorWorker', 'Editor Worker') } diff --git a/frontend/src/lib/components/codeGen/ScriptFix.svelte b/frontend/src/lib/components/codeGen/ScriptFix.svelte index 287b89e85d..9c500dc864 100644 --- a/frontend/src/lib/components/codeGen/ScriptFix.svelte +++ b/frontend/src/lib/components/codeGen/ScriptFix.svelte @@ -6,7 +6,7 @@ import { sendUserToast } from '$lib/toast' import type Editor from '../Editor.svelte' import { faCheck, faClose, faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons' - import { dbSchema, dbSchemaPublicOnly, existsOpenaiResourcePath } from '$lib/stores' + import { dbSchema, existsOpenaiResourcePath } from '$lib/stores' import type DiffEditor from '../DiffEditor.svelte' import { scriptLangToEditorLang } from '$lib/scripts' import Popover from '../Popover.svelte' @@ -38,8 +38,7 @@ language: lang, code: editor?.getCode() || '', error, - dbSchema: $dbSchema, - dbSchemaPublicOnly: $dbSchemaPublicOnly + dbSchema: $dbSchema }) generatedCode = result.code explanation = result.explanation diff --git a/frontend/src/lib/components/codeGen/ScriptGen.svelte b/frontend/src/lib/components/codeGen/ScriptGen.svelte index ec075870ab..59f2e0aedb 100644 --- a/frontend/src/lib/components/codeGen/ScriptGen.svelte +++ b/frontend/src/lib/components/codeGen/ScriptGen.svelte @@ -8,7 +8,7 @@ import { faCheck, faClose, faMagicWandSparkles } from '@fortawesome/free-solid-svg-icons' import Popup from '../common/popup/Popup.svelte' import { Icon } from 'svelte-awesome' - import { dbSchema, dbSchemaPublicOnly, existsOpenaiResourcePath } from '$lib/stores' + import { dbSchema, existsOpenaiResourcePath } from '$lib/stores' import type DiffEditor from '../DiffEditor.svelte' import { scriptLangToEditorLang } from '$lib/scripts' import type { Selection } from 'monaco-editor/esm/vs/editor/editor.api' @@ -46,16 +46,14 @@ language: lang, description: funcDesc, selectedCode, - dbSchema: $dbSchema, - dbSchemaPublicOnly: $dbSchemaPublicOnly + dbSchema: $dbSchema }) generatedCode = originalCode.replace(selectedCode, result.code + '\n') } else { const result = await generateScript({ language: lang, description: funcDesc, - dbSchema: $dbSchema, - dbSchemaPublicOnly: $dbSchemaPublicOnly + dbSchema: $dbSchema }) generatedCode = result.code } @@ -229,8 +227,8 @@ In order to better generate the script, we pass the selected DB schema to GPT-4.

- {#if lang === 'postgresql'} - + {#if $dbSchema.lang === 'postgresql'} + diff --git a/frontend/src/lib/components/codeGen/lib.ts b/frontend/src/lib/components/codeGen/lib.ts index e4a5631855..86da0f5d34 100644 --- a/frontend/src/lib/components/codeGen/lib.ts +++ b/frontend/src/lib/components/codeGen/lib.ts @@ -41,7 +41,6 @@ workspaceStore.subscribe(async (value) => { interface BaseOptions { language: Script.language | 'frontend' dbSchema: DBSchema | undefined - dbSchemaPublicOnly: boolean } interface ScriptGenerationOptions extends BaseOptions { @@ -71,19 +70,24 @@ async function addResourceTypes(scriptOptions: BaseOptions, workspace: string, p } function addDBSChema(scriptOptions: BaseOptions, prompt: string) { - if (['mysql', 'postgresql'].includes(scriptOptions.language) && scriptOptions.dbSchema) { - const { dbSchema, dbSchemaPublicOnly, language } = scriptOptions + const { dbSchema, language } = scriptOptions + if ( + dbSchema && + ['postgresql', 'mysql'].includes(language) && // make sure we are using a SQL language + (dbSchema.lang === 'postgresql' || dbSchema.lang === 'mysql') // make sure we have a SQL schema + ) { + const { schema, lang } = dbSchema let smallerSchema: { [schemaKey: string]: { [tableKey: string]: Array<[string, string, boolean, string?]> } } = {} - for (const schemaKey in dbSchema) { + for (const schemaKey in schema) { smallerSchema[schemaKey] = {} - for (const tableKey in dbSchema[schemaKey]) { + for (const tableKey in schema[schemaKey]) { smallerSchema[schemaKey][tableKey] = [] - for (const colKey in dbSchema[schemaKey][tableKey]) { - const col = dbSchema[schemaKey][tableKey][colKey] + for (const colKey in schema[schemaKey][tableKey]) { + const col = schema[schemaKey][tableKey][colKey] const p: [string, string, boolean, string?] = [colKey, col.type, col.required] if (col.default) { p.push(col.default) @@ -98,9 +102,9 @@ function addDBSChema(scriptOptions: BaseOptions, prompt: string) { | { [tableKey: string]: Array<[string, string, boolean, string?]> } = smallerSchema - if (language === 'postgresql' && dbSchemaPublicOnly) { + if (lang === 'postgresql' && dbSchema.publicOnly) { finalSchema = smallerSchema.public || smallerSchema - } else if (language === 'mysql' && Object.keys(smallerSchema).length === 1) { + } else if (lang === 'mysql' && Object.keys(smallerSchema).length === 1) { finalSchema = smallerSchema[Object.keys(smallerSchema)[0]] } prompt = diff --git a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte index 89b40b255c..b41eec947e 100644 --- a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte +++ b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte @@ -12,6 +12,7 @@ import PowershellIcon from '$lib/components/icons/PowershellIcon.svelte' import BigQueryIcon from '$lib/components/icons/BigQueryIcon.svelte' import SnowflakeIcon from '$lib/components/icons/SnowflakeIcon.svelte' + import GraphqlIcon from '$lib/components/icons/GraphqlIcon.svelte' export let lang: | SupportedLanguage @@ -32,8 +33,10 @@ [Script.language.GO]: 'Go', [Script.language.BASH]: 'Bash', [Script.language.NATIVETS]: 'HTTP', - // [Script.language.GRAPHQL]: 'HTTP', - [Script.language.POSTGRESQL]: 'Postgresql' + [Script.language.GRAPHQL]: 'GraphQL', + [Script.language.POSTGRESQL]: 'Postgresql', + [Script.language.BIGQUERY]: 'BigQuery', + [Script.language.SNOWFLAKE]: 'Snowflake' } const langToComponent: Record< @@ -56,7 +59,7 @@ powershell: PowershellIcon, postgresql: PostgresIcon, nativets: RestIcon, - graphql: RestIcon + graphql: GraphqlIcon } diff --git a/frontend/src/lib/components/flows/content/FlowInputs.svelte b/frontend/src/lib/components/flows/content/FlowInputs.svelte index 129481d0ef..a4903a2801 100644 --- a/frontend/src/lib/components/flows/content/FlowInputs.svelte +++ b/frontend/src/lib/components/flows/content/FlowInputs.svelte @@ -188,6 +188,18 @@ }} /> + { + dispatch('new', { + language: RawScript.language.GRAPHQL, + kind, + subkind: 'flow' + }) + }} + /> + + export let height = '24px' + export let width = '24px' + + + diff --git a/frontend/src/lib/editorUtils.ts b/frontend/src/lib/editorUtils.ts index 0e4f945863..fb0a9cd24d 100644 --- a/frontend/src/lib/editorUtils.ts +++ b/frontend/src/lib/editorUtils.ts @@ -63,6 +63,8 @@ export function langToExt(lang: string): string { return 'ts' case 'nativets': return 'ts' + case 'graphql': + return 'gql' default: return 'unknown' diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index 882d7d0805..41396650ea 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -11,7 +11,8 @@ import init, { parse_sql, parse_mysql, parse_bigquery, - parse_snowflake + parse_snowflake, + parse_graphql } from 'windmill-parser-wasm' import wasmUrl from 'windmill-parser-wasm/windmill_parser_wasm_bg.wasm?url' import { workspaceStore } from './stores.js' @@ -63,6 +64,12 @@ export async function inferArgs( { name: 'database', typ: { resource: 'snowflake' } }, ...inferedSchema.args ] + } else if (language == 'graphql') { + inferedSchema = JSON.parse(parse_graphql(code)) + inferedSchema.args = [ + { name: 'database', typ: { resource: 'graphql' } }, + ...inferedSchema.args + ] } else if (language == 'go') { inferedSchema = JSON.parse(parse_go(code)) } else if (language == 'bash') { diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index a73fd0cc19..f39660349e 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -138,7 +138,8 @@ INSERT INTO demo VALUES (?, ?) export const BIGQUERY_INIT_CODE = `-- @name1 (string) = default arg -- @name2 (integer) -INSERT INTO \`demodb.demo\` VALUES (@name1, @name2) +-- @name3 (string[]) +INSERT INTO \`demodb.demo\` VALUES (@name1, @name2, @name3) ` export const SNOWFLAKE_INIT_CODE = `-- ? name1 (varchar) = default arg @@ -146,6 +147,15 @@ export const SNOWFLAKE_INIT_CODE = `-- ? name1 (varchar) = default arg INSERT INTO demo VALUES (?, ?) ` +export const GRAPHQL_INIT_CODE = `query($name1: String, $name2: Int, $name3: [String]) { + demo(name1: $name1, name2: $name2, name3: $name3) { + name1, + name2, + name3 + } +} +` + export const FETCH_INIT_CODE = `export async function main( url: string | undefined, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' = 'GET', @@ -270,6 +280,7 @@ const ALL_INITIAL_CODE = [ MYSQL_INIT_CODE, BIGQUERY_INIT_CODE, SNOWFLAKE_INIT_CODE, + GRAPHQL_INIT_CODE, DENO_INIT_CODE_TRIGGER, DENO_INIT_CODE_CLEAR, PYTHON_INIT_CODE_CLEAR, @@ -344,6 +355,8 @@ export function initialCode( return BIGQUERY_INIT_CODE } else if (language == 'snowflake') { return SNOWFLAKE_INIT_CODE + } else if (language == 'graphql') { + return GRAPHQL_INIT_CODE } else if (language == 'bun') { if (subkind === 'flow') { return BUN_INIT_CODE_CLEAR diff --git a/frontend/src/lib/scripts.ts b/frontend/src/lib/scripts.ts index 766827b63c..4c5cbe33a5 100644 --- a/frontend/src/lib/scripts.ts +++ b/frontend/src/lib/scripts.ts @@ -18,7 +18,7 @@ export function scriptLangToEditorLang(lang: Script.language) { return 'sql' } else if (lang == 'bigquery') { return 'sql' - } else if (lang == "snowflake") { + } else if (lang == 'snowflake') { return 'sql' } else if (lang == 'python3') { return 'python' diff --git a/frontend/src/lib/stores.ts b/frontend/src/lib/stores.ts index e0de369418..0b4d3e5656 100644 --- a/frontend/src/lib/stores.ts +++ b/frontend/src/lib/stores.ts @@ -2,6 +2,7 @@ import { BROWSER } from 'esm-env' import { derived, type Readable, writable } from 'svelte/store' import type { UserWorkspaceList } from '$lib/gen/models/UserWorkspaceList.js' import type { TokenResponse } from './gen' +import type { IntrospectionQuery } from 'graphql' export interface UserExt { email: string @@ -66,7 +67,8 @@ export const hubScripts = writable< | undefined >(undefined) export const existsOpenaiResourcePath = writable(false) -export type DBSchema = { + +type SQLBaseSchema = { [schemaKey: string]: { [tableKey: string]: { [columnKey: string]: { @@ -78,9 +80,25 @@ export type DBSchema = { } } -export const dbSchema = writable(undefined) +export interface MysqlSchema { + lang: 'mysql' + schema: SQLBaseSchema +} -export const dbSchemaPublicOnly = writable(true) +export interface PostgresqlSchema { + lang: 'postgresql' + schema: SQLBaseSchema + publicOnly: boolean +} + +export interface GraphqlSchema { + lang: 'graphql' + schema: IntrospectionQuery +} + +export type DBSchema = MysqlSchema | PostgresqlSchema | GraphqlSchema + +export const dbSchema = writable(undefined) export function switchWorkspace(workspace: string | undefined) { localStorage.removeItem('flow') diff --git a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte index a7f1df2b2d..c0c3010344 100644 --- a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte @@ -291,7 +291,7 @@ {job.job_kind} {/if} - {#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'nativets', 'bash', 'other', 'dependency'].includes(job.tag)} + {#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'graphql', 'nativets', 'bash', 'other', 'dependency'].includes(job.tag)}
Worker group: {job.tag}
diff --git a/frontend/vite.config.js b/frontend/vite.config.js index d553b1f168..41cc6443df 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -2,6 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite' import { readFileSync } from 'fs' import { fileURLToPath } from 'url' import ViteYaml from '@modyfi/vite-plugin-yaml' +import monacoEditorPlugin from 'vite-plugin-monaco-editor' const file = fileURLToPath(new URL('package.json', import.meta.url)) const json = readFileSync(file, 'utf8') @@ -32,7 +33,20 @@ const config = { preview: { port: 3000 }, - plugins: [sveltekit(), ViteYaml()], + plugins: [ + sveltekit(), + ViteYaml(), + monacoEditorPlugin.default({ + publicPath: 'workers', + languageWorkers: [], + customWorkers: [ + { + label: 'graphql', + entry: 'monaco-graphql/esm/graphql.worker' + } + ] + }) + ], define: { __pkg__: version },