From 3dcad57481a2dcc97694692d83806d009167d4ac Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 9 Nov 2025 10:53:37 +0000 Subject: [PATCH 01/34] add debug_sse_stream --- backend/windmill-worker/src/ai/sse.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/windmill-worker/src/ai/sse.rs b/backend/windmill-worker/src/ai/sse.rs index fcafc9cd7e..42c5d73603 100644 --- a/backend/windmill-worker/src/ai/sse.rs +++ b/backend/windmill-worker/src/ai/sse.rs @@ -41,6 +41,12 @@ pub struct OpenAISSEEvent { pub choices: Option>, } +lazy_static::lazy_static! { + static ref DEBUG_SSE_STREAM: bool = std::env::var("DEBUG_SSE_STREAM") + .unwrap_or("false".to_string()) + .parse::() + .unwrap_or(false); +} pub trait SSEParser { async fn parse_event_data(&mut self, data: &str) -> Result<(), Error>; @@ -54,6 +60,9 @@ pub trait SSEParser { // Convert chunk to string and add to buffer let chunk_str = String::from_utf8_lossy(&chunk); + if *DEBUG_SSE_STREAM { + tracing::info!("SSE chunk: {}", chunk_str); + } buffer.push_str(&chunk_str); // Process complete lines from buffer From e047c3b2b1d4d6d76690c49fcc0616740d81d31d Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 10 Nov 2025 09:51:30 +0000 Subject: [PATCH 02/34] add require non-empty array --- frontend/src/lib/components/ArgInput.svelte | 14 ++++++++++++++ .../src/lib/components/ArrayTypeNarrowing.svelte | 10 +++++++++- .../src/lib/components/EditableSchemaForm.svelte | 1 + .../lib/components/schema/PropertyEditor.svelte | 3 +++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 3aa15b98f8..b63d3798e5 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -400,6 +400,7 @@ const UUID_PATTERN = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' const IPV6_PATTERN = '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$' + function validateInput(pattern: string | undefined, v: any, required: boolean): void { if (nullable && emptyString(v)) { error = '' @@ -407,6 +408,18 @@ } else if (required && (v == undefined || v == null || v === '') && inputCat != 'object') { error = 'Required' valid && (valid = false) + } else if ( + required && + inputCat == 'list' && + extra?.['nonEmpty'] == true && + Array.isArray(v) && + v.length === 0 + ) { + error = 'Required' + valid && (valid = false) + } else if (inputCat == 'list' && !Array.isArray(v)) { + error = 'Expected an array, got ' + typeof v + ' instead' + valid && (valid = false) } else { if (inputCat == 'number' && typeof v === 'number') { let min = extra['min'] @@ -523,6 +536,7 @@ }) $effect(() => { + extra?.['nonEmpty'] let args = [pattern, value, required] as const untrack(() => validateInput(...args)) }) diff --git a/frontend/src/lib/components/ArrayTypeNarrowing.svelte b/frontend/src/lib/components/ArrayTypeNarrowing.svelte index 064faa6950..7feec29713 100644 --- a/frontend/src/lib/components/ArrayTypeNarrowing.svelte +++ b/frontend/src/lib/components/ArrayTypeNarrowing.svelte @@ -24,12 +24,14 @@ properties?: { [name: string]: SchemaProperty } } | undefined + nonEmpty?: boolean | undefined } let { canEditResourceType = false, originalType = undefined, - itemsType = $bindable() + itemsType = $bindable(), + nonEmpty = $bindable() }: Props = $props() let selected: @@ -143,6 +145,12 @@ {/each} + {#if canEditResourceType || originalType == 'string[]' || originalType == 'object[]'}
diff --git a/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte b/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte index a748d40e96..b22e1ceeac 100644 --- a/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte +++ b/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte @@ -268,6 +268,7 @@ variant="border" wrapperClasses="p-0 whitespace-nowrap w-fit" btnClasses={twMerge( + 'hover:bg-surface', 'font-mono h-4 py-1 text-2xs', 'font-thin px-1 rounded-[0.275rem]', metaData ? 'rounded-r-none border-r-0.5' : '' From 806a168e185cdaf82356ae1aa0fef77dc1558c8a Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:32:36 +0100 Subject: [PATCH 24/34] Add link to job + update git sync script tip if applicable on fork fail (#7117) * Add link to job + update git sync script tip if applicable on fork fail * Format --- .../workspaceSettings/CreateWorkspace.svelte | 95 +++++++++++++++++-- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/components/workspaceSettings/CreateWorkspace.svelte b/frontend/src/lib/components/workspaceSettings/CreateWorkspace.svelte index b230658e36..6f6041cc4b 100644 --- a/frontend/src/lib/components/workspaceSettings/CreateWorkspace.svelte +++ b/frontend/src/lib/components/workspaceSettings/CreateWorkspace.svelte @@ -4,12 +4,14 @@ import { goto } from '$lib/navigation' import { base } from '$lib/base' import { + JobService, ResourceService, SettingService, UserService, VariableService, WorkspaceService, - type AIProvider + type AIProvider, + type CompletedJob } from '$lib/gen' import { validateUsername } from '$lib/utils' import { logoutWithRedirect } from '$lib/logout' @@ -79,8 +81,41 @@ const WM_FORK_PREFIX = 'wm-fork-' let forkCreationLoading = $state(false) - let forkCreationError = $state("") + let forkCreationError = $state('') let errorMsgs: string[] = $state([]) + let failedSyncJobs: string[] = $state([]) + + async function fetchFailedSyncJobs(jobs: string[]): Promise { + let ret: CompletedJob[] = [] + for (const job of jobs) { + let j = await JobService.getCompletedJob({ + id: job, + workspace: $workspaceStore! + }) + ret.push(j) + } + return ret + } + + function isPathVersionLessThan(path: string | undefined, version: number): boolean { + if (!path || !path.startsWith('hub/')) { + return false + } + + const parts = path.split('/') + + if (parts.length < 2) { + return false + } + + const embeddedVersion = parseInt(parts[1], 10) + + if (isNaN(embeddedVersion)) { + return false + } + + return embeddedVersion < version + } async function createOrForkWorkspace() { const prefixed_id = `${WM_FORK_PREFIX}${id}` @@ -88,7 +123,8 @@ if ($workspaceStore) { forkCreationLoading = true errorMsgs = [] - forkCreationError = "" + failedSyncJobs = [] + forkCreationError = '' let gitSyncJobIds = await WorkspaceService.createWorkspaceForkGitBranch({ workspace: $workspaceStore!, @@ -109,6 +145,7 @@ onProgress: (status) => { if (status.status === 'failure') { errorMsgs.push(status.error ?? 'Deploy fork job failed') + failedSyncJobs.push(jobId) } } }) @@ -123,7 +160,7 @@ return } if (errorMsgs.length != 0) { - forkCreationError = "Failed to create a branch for this fork on the git sync repo(s)" + forkCreationError = 'Failed to create a branch for this fork on the git sync repo(s)' forkCreationLoading = false sendUserToast( `Could not fork workspace ${$workspaceStore} because branch creation failed: ${errorMsgs}`, @@ -143,7 +180,7 @@ }) } catch (e) { forkCreationError = `Failed to create fork '${prefixed_id}'` - errorMsgs.push(e?.body ?? e ?? "Unknown error") + errorMsgs.push(e?.body ?? e ?? 'Unknown error') forkCreationLoading = false sendUserToast(`Could not create fork '${prefixed_id}' ${e}`, true) return @@ -319,11 +356,57 @@ {/if} {#if errorMsgs.length != 0} -
    +
      {#each errorMsgs as errorMsg}
    • - {errorMsg}
    • {/each}
    + {#if failedSyncJobs.length != 0} + More details on the jobs that failed: + {#await fetchFailedSyncJobs(failedSyncJobs)} + + {:then failedJobs} +
      + {#each failedJobs as job} +
    • + - + + {job.id} + +
    • + + {#if isPathVersionLessThan(job.script_path, 28073)} +
      + This job was not running the latest version of the git sync script available on + the hub. You might be able to solve this issue by going to `Workspace Settings` + -> `Git Sync` and updating the script. +
      + {/if} + {/each} +
    + {:catch error} + Tried to fetch jobs to get more information, but failed: {error}. Here are the failed + job ids: +
      + {#each failedSyncJobs as jobId} +
    • + - + + {jobId} + +
    • + {/each} +
    + {/await} + {/if} {/if}