From bef6bb826f1c72d91130fc6886bf062ebf809c0c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 12 Sep 2025 13:16:00 +0000 Subject: [PATCH] fix: custom tag helper --- ...8409185d273c022c4971329f5bce85097bc22.json | 24 +++++ backend/windmill-api/src/folders.rs | 34 ++++++- .../lib/components/AssignableTagsInner.svelte | 93 ++++++++++++++++++- 3 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 backend/.sqlx/query-6a0b04a34032ae0e28bbb4895ad8409185d273c022c4971329f5bce85097bc22.json diff --git a/backend/.sqlx/query-6a0b04a34032ae0e28bbb4895ad8409185d273c022c4971329f5bce85097bc22.json b/backend/.sqlx/query-6a0b04a34032ae0e28bbb4895ad8409185d273c022c4971329f5bce85097bc22.json new file mode 100644 index 0000000000..575f738537 --- /dev/null +++ b/backend/.sqlx/query-6a0b04a34032ae0e28bbb4895ad8409185d273c022c4971329f5bce85097bc22.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(SELECT 1 FROM folder WHERE name = $1 AND workspace_id = $2 AND $3 = ANY(owners))", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "6a0b04a34032ae0e28bbb4895ad8409185d273c022c4971329f5bce85097bc22" +} diff --git a/backend/windmill-api/src/folders.rs b/backend/windmill-api/src/folders.rs index db9fa72c91..f24026c580 100644 --- a/backend/windmill-api/src/folders.rs +++ b/backend/windmill-api/src/folders.rs @@ -202,6 +202,7 @@ async fn create_folder( )); } + if let Err(e) = sqlx::query_as!( Folder, "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, now())", @@ -214,7 +215,38 @@ async fn create_folder( authed.username ) .execute(&mut *tx) - .await?; + .await { + let exists_for_user = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM folder WHERE name = $1 AND workspace_id = $2 AND $3 = ANY(owners))", + ng.name, + w_id, + authed.username + ) + .fetch_one(&mut *tx) + .await? + .unwrap_or(false); + let exists = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM folder WHERE name = $1 AND workspace_id = $2)", + ng.name, + w_id + ) + .fetch_one(&db) + .await? + .unwrap_or(false); + if !exists_for_user && exists { + return Err(windmill_common::error::Error::BadRequest(format!( + "Folder '{}' already exists in workspace '{}' but you do not have permission to read to it", ng.name, w_id + ))); + } else if exists { + return Err(windmill_common::error::Error::BadRequest(format!( + "Folder '{}' already exists in workspace '{}'", ng.name, w_id + ))); + } else { + return Err(windmill_common::error::Error::InternalErr(format!( + "Failed to create folder: {}", e + ))); + } + } audit_log( &mut *tx, diff --git a/frontend/src/lib/components/AssignableTagsInner.svelte b/frontend/src/lib/components/AssignableTagsInner.svelte index af77125b87..eacd384d5c 100644 --- a/frontend/src/lib/components/AssignableTagsInner.svelte +++ b/frontend/src/lib/components/AssignableTagsInner.svelte @@ -32,6 +32,34 @@ const dispatch = createEventDispatcher() + const customTagRegex = /^([\w-]+)\(((?:[\w-]+\+)*[\w-]+|(?:\^[\w-]+)+)\)$/ + const dynamicTagRegex = /\$args\[((?:\w+\.)*\w+)\]/ + + let dynamicTag = $derived.by(() => { + let r = newTag.trim() + if (r == '') return undefined + let matched = r.match(dynamicTagRegex) + return matched?.[1] + }) + + let extractedCustomTag = $derived.by(() => { + let r = newTag.trim() + if (r == '') return undefined + let matched = r.match(customTagRegex) + console.log(matched) + let tag = matched?.[1] + let workspaces_raw = matched?.[2] + let tag_type = workspaces_raw?.includes('^') ? 'exclude' : 'include' + if (tag_type == 'exclude') { + workspaces_raw = workspaces_raw?.slice(1) + } + let workspaces = workspaces_raw?.split(tag_type == 'include' ? '+' : '^') + if (!workspaces_raw || workspaces_raw?.length == 0) { + return undefined + } + return { tag, workspaces, tag_type } + }) + loadCustomTags() @@ -64,6 +92,53 @@ {/each} + {#if extractedCustomTag} +
+
Workspace specific tag
+
+ Tag: + {extractedCustomTag.tag} +
+
+ Workspaces: + {#if extractedCustomTag.tag_type == 'include'} + {extractedCustomTag.workspaces?.join(', ')} + {:else} + All workspaces except {extractedCustomTag.workspaces?.join(', ')} + {/if} +
+
+ {:else if newTag.trim()} + {#if newTag.includes('(') || newTag.includes(')') || newTag.includes('+') || newTag.includes('^') || ((newTag.includes('.') || newTag.includes('$args[')) && !dynamicTag)} +
+
Invalid tag
+
+ Tag: + {newTag.trim()} +
+
+ {:else} +
+
+ {#if newTag.includes('$workspace') || newTag.includes('$args')} + Dynamic tag + {:else} + Simple tag + {/if} +
+
+ Tag: + {newTag.trim()} +
+ {#if newTag.includes('$workspace') && !dynamicTag} +
Interpolated tag based on workspace id the job was created in
+ {/if} + {#if dynamicTag} +
Interpolated tag based on args input of {dynamicTag}
+ {/if} +
+ {/if} + {/if}