From 3e658669bad7b35c8a300eba1fa156a3b09c1e3f Mon Sep 17 00:00:00 2001 From: dieriba Date: Wed, 18 Jun 2025 14:28:26 +0200 Subject: [PATCH] fix openapi export duplicate issue and nits (#5971) * nits and fix duplicate issue * update .sqlx * nits --- ...d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json | 35 +++++++++++++ ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...117eda99a6f482a073ccd36a6431ef689a3fd.json | 35 +++++++++++++ backend/windmill-api/src/openapi.rs | 52 +++++++++---------- .../triggers/http/OpenAPISpecGenerator.svelte | 26 ++++++++-- 5 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 backend/.sqlx/query-33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json create mode 100644 backend/.sqlx/query-dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd.json diff --git a/backend/.sqlx/query-33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json b/backend/.sqlx/query-33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json new file mode 100644 index 0000000000..01dfc9ebe3 --- /dev/null +++ b/backend/.sqlx/query-33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n path,\n summary,\n description\n FROM\n flow\n WHERE\n path ~ ANY($1) AND\n workspace_id = $2 AND\n archived is FALSE\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "summary", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "TextArray", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "33367c42e87e78ae987c0966dc4d445c5eff75b2e2843ffd7a46b03cbaea9ae8" +} diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 713ccb9dd3..36ddb8ab9f 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - null + true ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/.sqlx/query-dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd.json b/backend/.sqlx/query-dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd.json new file mode 100644 index 0000000000..57f859d416 --- /dev/null +++ b/backend/.sqlx/query-dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n path,\n summary,\n description\n FROM\n script\n WHERE\n path ~ ANY($1) AND\n workspace_id = $2 AND\n archived is FALSE\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "summary", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "TextArray", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "dc36b46b9eb80cb7c92fa72519d117eda99a6f482a073ccd36a6431ef689a3fd" +} diff --git a/backend/windmill-api/src/openapi.rs b/backend/windmill-api/src/openapi.rs index c0e1d046b6..95f73f95ba 100644 --- a/backend/windmill-api/src/openapi.rs +++ b/backend/windmill-api/src/openapi.rs @@ -9,7 +9,6 @@ use axum::{ }; use http::{header, HeaderValue, Method, StatusCode}; use indexmap::IndexMap; -use itertools::Itertools; use serde::{Deserialize, Serialize}; use serde_json::{to_value, Map, Value}; use sqlx::PgConnection; @@ -24,9 +23,12 @@ use windmill_common::{ use crate::db::ApiAuthed; #[cfg(feature = "http_trigger")] -use crate::{ - http_trigger_args::HttpMethod, http_trigger_auth::ApiKeyAuthentication, - http_triggers::AuthenticationMethod, resources::try_get_resource_from_db_as, +use { + crate::{ + http_trigger_args::HttpMethod, http_trigger_auth::ApiKeyAuthentication, + http_triggers::AuthenticationMethod, resources::try_get_resource_from_db_as, + }, + itertools::Itertools, }; lazy_static::lazy_static! { @@ -311,9 +313,15 @@ fn generate_paths( } }; - let mut duplicate_webhooks = HashSet::new(); + let mut webhooks = HashSet::new(); for path in paths { + if let Kind::Webhook(WebhookConfig { runnable_kind }) = &path.kind { + if !webhooks.insert((path.route_path.clone(), runnable_kind.to_owned())) { + continue; + } + } + let (route_paths, parameters) = from_route_path_to_openapi_path(&path.route_path, &path.kind)?; @@ -340,9 +348,6 @@ fn generate_paths( let (methods, is_webhook) = match &path.kind { Kind::Webhook(_) => { - if !duplicate_webhooks.insert(route_path.clone()) { - return Err(anyhow!("Found duplicate webhook: {}", path.route_path).into()); - } is_async = route_path.starts_with("/run/"); let methods = if is_async { vec![Method::POST] @@ -354,7 +359,12 @@ fn generate_paths( } Kind::HttpRoute(HttpRouteConfig { method }) => { if path_object.get(&method.to_string()).is_some() { - return Err(anyhow!("Found duplicate route: {}", path.route_path).into()); + return Err(anyhow!( + "Found duplicate {} method, for route at path: {}", + method, + path.route_path + ) + .into()); } is_async = path.is_async.unwrap_or(true); (vec![method.to_owned()], false) @@ -809,14 +819,6 @@ async fn webhook_to_future_paths( summary: Option, } - impl PartialEq for MinifiedWebhook { - fn eq(&self, other: &Self) -> bool { - self.path == other.path - } - } - - impl Eq for MinifiedWebhook {} - let webhook_scripts = sqlx::query_as!( MinifiedWebhook, r#"SELECT @@ -827,16 +829,14 @@ async fn webhook_to_future_paths( script WHERE path ~ ANY($1) AND - workspace_id = $2 + workspace_id = $2 AND + archived is FALSE "#, &script_webhook_filter, &w_id ) .fetch_all(&mut *pg_pool) - .await? - .into_iter() - .unique() - .collect_vec(); + .await?; let webhook_flows = sqlx::query_as!( MinifiedWebhook, @@ -848,16 +848,14 @@ async fn webhook_to_future_paths( flow WHERE path ~ ANY($1) AND - workspace_id = $2 + workspace_id = $2 AND + archived is FALSE "#, &flow_webhook_filter, &w_id ) .fetch_all(&mut *pg_pool) - .await? - .into_iter() - .unique() - .collect_vec(); + .await?; openapi_future_paths.reserve_exact(webhook_scripts.len() + webhook_flows.len()); diff --git a/frontend/src/lib/components/triggers/http/OpenAPISpecGenerator.svelte b/frontend/src/lib/components/triggers/http/OpenAPISpecGenerator.svelte index bcb83e97db..a596d73d5e 100644 --- a/frontend/src/lib/components/triggers/http/OpenAPISpecGenerator.svelte +++ b/frontend/src/lib/components/triggers/http/OpenAPISpecGenerator.svelte @@ -47,11 +47,31 @@ let licenseName = $state('') let licenseUrl = $state('') let isGeneratingOpenapiSpec = $state(false) - let openapiDocument = $state('') + let openapiDocument = $state( + '# Click "Generate OpenAPI document" to generate your OpenAPI spec.' + ) let lang: OpenapiSpecFormat = $state('yaml') let editor: SimpleEditor | undefined = $state() let generateCurlCommandDrawer: Drawer | undefined = $state() - let webhookAndHttpRouteFilter: HttpRouteAndWebhook[] = $state([]) + let webhookAndHttpRouteFilter: HttpRouteAndWebhook[] = $state([ + { + user_or_folder_regex: '*', + user_or_folder_regex_value: '*', + path: '*', + runnable_kind: 'script' + }, + { + user_or_folder_regex: '*', + user_or_folder_regex_value: '*', + path: '*', + runnable_kind: 'flow' + }, + { + folder_regex: '*', + path_regex: '*', + route_path_regex: '*' + } + ]) let disabled = $derived(webhookAndHttpRouteFilter.length === 0) let token = $state('') let obj: Record = $state({}) @@ -556,8 +576,6 @@ curl -X POST "${window.location.origin}${base}/api/w/${$workspaceStore!}/openapi