From 2b03133b2245bd42f3c64b915d72dd3f62eb65a4 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 26 Aug 2025 15:25:17 +0200 Subject: [PATCH 01/40] fix(frontend): ai agent step nits (#6469) * fix(frontend): ai agent step nits * fix provider select * nits * nit --- frontend/src/lib/components/ArgInput.svelte | 16 +++++++++++++--- .../lib/components/copilot/MetadataGen.svelte | 17 ++++++++++++++--- .../flows/common/FlowCardHeader.svelte | 2 +- frontend/src/lib/components/flows/flowInfers.ts | 4 +++- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index a15c461c12..5e8b189a09 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -937,11 +937,21 @@ selected={oneOfSelected} on:selected={({ detail }) => { oneOfSelected = detail - const prevValueKeys = Object.keys( + const selectedObjProperties = oneOf?.find((o) => o.title == detail)?.properties ?? {} - ) + const newValueKeys = Object.keys(selectedObjProperties) const toKeep = {} - for (const key of prevValueKeys) { + for (const key of newValueKeys) { + // Check if there is a select (enum) in the newly selected oneOf and if the current value is not in the enum, skip it + if ( + !['kind', 'label'].includes(key) && + selectedObjProperties[key]?.enum && + value && + value[key] !== undefined && + !selectedObjProperties[key].enum.includes(value[key]) + ) { + continue + } toKeep[key] = value[key] } const tagKey = oneOf.find((o) => Object.keys(o.properties ?? {}).includes('kind')) diff --git a/frontend/src/lib/components/copilot/MetadataGen.svelte b/frontend/src/lib/components/copilot/MetadataGen.svelte index f9e5b0c9a6..91b6c43cbf 100644 --- a/frontend/src/lib/components/copilot/MetadataGen.svelte +++ b/frontend/src/lib/components/copilot/MetadataGen.svelte @@ -32,6 +32,7 @@ You are a helpful AI assistant. You generate very brief summaries from scripts. The summaries need to be as short as possible (maximum 8 words) and only give a global idea. Do not specify the programming language. Do not use any punctation. Avoid using prepositions and articles. Examples: List the commits of a GitHub repository, Divide a number by 16, etc.. +**Return only the summary, no other text.** `, user: ` Generate a very short summary for the script below: @@ -48,6 +49,7 @@ These descriptions are used to explain to other users what the script does and h Be as short as possible to give a global idea, maximum 3-4 sentences. All scripts export an asynchronous function called main, do not include it in the description. Do not describe how to call it either. +**Return only the description, no other text.** `, user: ` Generate a description for the script below: @@ -61,6 +63,7 @@ Generate a description for the script below: system: ` You are a helpful AI assistant. You generate very brief summaries from scripts. The summaries need to be as short as possible (maximum 8 words) and only give a global idea. Do not use any punctation. Avoid using prepositions and articles. +**Return only the summary, no other text.** `, user: ` Summarize the flow below in one very short sentence without punctation: @@ -73,6 +76,7 @@ You are a helpful AI assistant. You generate descriptions from flow. These descriptions are used to explain to other users what the flow does and how to use it. Be as short as possible to give a global idea, maximum 3-4 sentences. Do not include line breaks. +**Return only the description, no other text.** `, user: ` Generate a description for the flow below: @@ -81,13 +85,15 @@ Generate a description for the flow below: }, agentToolFunctionName: { system: ` -You are a helpful AI assistant. You generate function names from scripts. -These function names will be used by an AI agent to call this tool. +You are a helpful AI assistant. You generate tool names from scripts. +These tool names will be used by an AI agent to call this tool. +It has to be based on the script code content not on the main function name. It has to respect the following regex: /[a-zA-Z0-9_]+/ Examples: generate_image, classify_image, summarize_text, etc. +**Return only the tool name, no other text.** `, user: ` -Generate a function name for the script below: +Generate a tool name for the script below: {code}`, placeholderName: 'code' } @@ -317,6 +323,11 @@ Generate a function name for the script below: on:focus={() => (focused = true)} on:blur={() => (focused = false)} /> + {#if promptConfigName === 'agentToolFunctionName' && !validateToolName(content ?? '')} +
+ Invalid tool name, should only contain letters, numbers and underscores +
+ {/if} {/if} diff --git a/frontend/src/lib/components/flows/common/FlowCardHeader.svelte b/frontend/src/lib/components/flows/common/FlowCardHeader.svelte index 39e3f615fd..780be61a18 100644 --- a/frontend/src/lib/components/flows/common/FlowCardHeader.svelte +++ b/frontend/src/lib/components/flows/common/FlowCardHeader.svelte @@ -72,7 +72,7 @@
{#if flowModuleValue} diff --git a/frontend/src/lib/components/flows/flowInfers.ts b/frontend/src/lib/components/flows/flowInfers.ts index 887fe6e418..9bf57a06ee 100644 --- a/frontend/src/lib/components/flows/flowInfers.ts +++ b/frontend/src/lib/components/flows/flowInfers.ts @@ -106,7 +106,9 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{ type: 'number' }, temperature: { - type: 'number' + type: 'number', + description: + 'Controls randomness in text generation. Range: 0.0 (deterministic) to 2.0 (random).' } }, required: ['provider', 'model', 'user_message'], From b26cea9d3e2f9a0acae335aad12206da491ac733 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:08:29 +0200 Subject: [PATCH 02/40] feat(aichat): give advanced options tools to flow mode (#6463) * add tool to set for loop options * add tool to set skip and early break * draft ui intents * fix option tool * clean ui intents * add other ui intents + fixes * cleaning * fix * hide diff mode on apply * clean * fix * fix * fix typo * add precision for js expressions --- .../copilot/chat/ToolContentDisplay.svelte | 4 +- .../copilot/chat/flow/FlowAIChat.svelte | 80 ++++++++- .../lib/components/copilot/chat/flow/core.ts | 157 +++++++++++++++++- .../components/copilot/chat/flow/uiIntents.ts | 9 + .../copilot/chat/flow/useUiIntent.ts | 17 ++ .../content/FlowBranchesAllWrapper.svelte | 7 + .../content/FlowBranchesOneWrapper.svelte | 7 + .../components/flows/content/FlowLoop.svelte | 8 + .../flows/content/FlowModuleComponent.svelte | 8 + .../flows/content/FlowWhileLoop.svelte | 7 + 10 files changed, 299 insertions(+), 5 deletions(-) create mode 100644 frontend/src/lib/components/copilot/chat/flow/uiIntents.ts create mode 100644 frontend/src/lib/components/copilot/chat/flow/useUiIntent.ts diff --git a/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte b/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte index 95b19cd6bd..7558c40dd8 100644 --- a/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte +++ b/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte @@ -29,9 +29,7 @@ try { const parsed = JSON.parse(obj[key]) obj[key] = parsed - } catch (e) { - console.error('Failed to parse JSON:', e) - } + } catch {} } return JSON.stringify(obj, null, 2) } catch { diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index 9b7f7f96ba..e17c521f3c 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -98,6 +98,14 @@ if (affectedModule.action === 'removed') { deleteStep(id) } + // Hide diff editor if the module is a rawscript + if ( + affectedModule.action === 'modified' && + $currentEditor?.type === 'script' && + $currentEditor.stepId === id + ) { + $currentEditor.hideDiffMode() + } } affectedModules = {} }, @@ -185,7 +193,8 @@ $currentEditor.hideDiffMode() } - newModule.value = oldModule.value + Object.keys(newModule).forEach((k) => delete newModule[k]) + Object.assign(newModule, $state.snapshot(oldModule)) } refreshStateStore(flowStore) @@ -486,6 +495,75 @@ refreshStateStore(flowStore) } + setModuleStatus(id, 'modified') + }, + setForLoopOptions: async (id, opts) => { + const module = getModule(id) + if (!module) { + throw new Error('Module not found') + } + if (module.value.type !== 'forloopflow') { + throw new Error('Module is not a forloopflow') + } + + // Apply skip_failures if provided + if (typeof opts.skip_failures === 'boolean') { + module.value.skip_failures = opts.skip_failures + } + + // Apply parallel if provided + if (typeof opts.parallel === 'boolean') { + module.value.parallel = opts.parallel + } + + // Handle parallelism + if (opts.parallel === false) { + // If parallel is disabled, clear parallelism + module.value.parallelism = undefined + } else if (opts.parallelism !== undefined) { + if (opts.parallelism === null) { + // Explicitly clear parallelism + module.value.parallelism = undefined + } else if (module.value.parallel || opts.parallel === true) { + // Only set parallelism if parallel is enabled + const n = Math.max(1, Math.floor(Math.abs(opts.parallelism))) + module.value.parallelism = n + } + } + + refreshStateStore(flowStore) + setModuleStatus(id, 'modified') + }, + setModuleControlOptions: async (id, opts) => { + const module = getModule(id) + if (!module) { + throw new Error('Module not found') + } + + // Handle stop_after_if + if (typeof opts.stop_after_if === 'boolean') { + if (opts.stop_after_if === false) { + module.stop_after_if = undefined + } else { + module.stop_after_if = { + expr: opts.stop_after_if_expr ?? '', + skip_if_stopped: opts.stop_after_if + } + } + } + + // Handle skip_if + if (typeof opts.skip_if === 'boolean') { + if (opts.skip_if === false) { + module.skip_if = undefined + } else { + module.skip_if = { + expr: opts.skip_if_expr ?? '' + } + } + } + + refreshStateStore(flowStore) setModuleStatus(id, 'modified') } } diff --git a/frontend/src/lib/components/copilot/chat/flow/core.ts b/frontend/src/lib/components/copilot/chat/flow/core.ts index 91756c8353..d0a908ea7b 100644 --- a/frontend/src/lib/components/copilot/chat/flow/core.ts +++ b/frontend/src/lib/components/copilot/chat/flow/core.ts @@ -1,4 +1,5 @@ import { ScriptService, type FlowModule, type RawScript, type Script, JobService } from '$lib/gen' +import { emitUiIntent } from './uiIntents' import type { ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam @@ -57,6 +58,23 @@ export interface FlowAIChatHelpers { addBranch: (id: string) => Promise removeBranch: (id: string, branchIndex: number) => Promise setForLoopIteratorExpression: (id: string, expression: string) => Promise + setForLoopOptions: ( + id: string, + opts: { + skip_failures?: boolean | null + parallel?: boolean | null + parallelism?: number | null + } + ) => Promise + setModuleControlOptions: ( + id: string, + opts: { + stop_after_if?: boolean | null + stop_after_if_expr?: string | null + skip_if?: boolean | null + skip_if_expr?: string | null + } + ) => Promise setCode: (id: string, code: string) => Promise } @@ -196,6 +214,67 @@ const setForLoopIteratorExpressionToolDef = createToolDef( 'Set the iterator JavaScript expression for the given forloop step' ) +const setForLoopOptionsSchema = z.object({ + id: z.string().describe('The id of the forloop step to configure'), + skip_failures: z + .boolean() + .nullable() + .optional() + .describe('Whether to skip failures in the loop (null to not change)'), + parallel: z + .boolean() + .nullable() + .optional() + .describe('Whether to run iterations in parallel (null to not change)'), + parallelism: z + .number() + .int() + .min(1) + .nullable() + .optional() + .describe('Maximum number of parallel iterations (null to not change)') +}) + +const setForLoopOptionsToolDef = createToolDef( + setForLoopOptionsSchema, + 'set_forloop_options', + 'Set advanced options for a forloop step: skip_failures, parallel, and parallelism' +) + +const setModuleControlOptionsSchema = z.object({ + id: z.string().describe('The id of the module to configure'), + stop_after_if: z + .boolean() + .nullable() + .optional() + .describe('Early stop condition (true to set, false to clear, null to not change)'), + stop_after_if_expr: z + .string() + .nullable() + .optional() + .describe( + 'JavaScript expression for early stop condition. Can use `flow_input` or `result`. `result` is the result of the step. `results.` is not supported, do not use it. Only used if stop_after_if is true. Example: `flow_input.x > 10` or `result === "failure"`' + ), + skip_if: z + .boolean() + .nullable() + .optional() + .describe('Skip condition (true to set, false to clear, null to not change)'), + skip_if_expr: z + .string() + .nullable() + .optional() + .describe( + 'JavaScript expression for skip condition. Can use `flow_input` or `results.`. Only used if skip_if is true. Example: `flow_input.x > 10` or `results.a === "failure"`' + ) +}) + +const setModuleControlOptionsToolDef = createToolDef( + setModuleControlOptionsSchema, + 'set_module_control_options', + 'Set control options for any module: stop_after_if (early stop) and skip_if (conditional skip)' +) + const setBranchPredicateSchema = z.object({ id: z.string().describe('The id of the branchone step to set the predicates for'), branchIndex: z @@ -558,6 +637,68 @@ export const flowTools: Tool[] = [ return `Forloop '${parsedArgs.id}' iterator expression set` } }, + { + def: setForLoopOptionsToolDef, + fn: async ({ args, helpers, toolId, toolCallbacks }) => { + const parsedArgs = setForLoopOptionsSchema.parse(args) + await helpers.setForLoopOptions(parsedArgs.id, { + skip_failures: parsedArgs.skip_failures, + parallel: parsedArgs.parallel, + parallelism: parsedArgs.parallelism + }) + helpers.selectStep(parsedArgs.id) + + const message = `Set forloop '${parsedArgs.id}' options` + toolCallbacks.setToolStatus(toolId, { + content: message + }) + return `${message}: ${JSON.stringify(parsedArgs)}` + } + }, + { + def: setModuleControlOptionsToolDef, + fn: async ({ args, helpers, toolId, toolCallbacks }) => { + const parsedArgs = setModuleControlOptionsSchema.parse(args) + await helpers.setModuleControlOptions(parsedArgs.id, { + stop_after_if: parsedArgs.stop_after_if, + stop_after_if_expr: parsedArgs.stop_after_if_expr, + skip_if: parsedArgs.skip_if, + skip_if_expr: parsedArgs.skip_if_expr + }) + helpers.selectStep(parsedArgs.id) + + // Emit UI intent to show early-stop tab when stop_after_if is configured + const modules = helpers.getModules() + const module = findModuleById(modules, parsedArgs.id) + if (!module) { + throw new Error(`Module with id '${parsedArgs.id}' not found in flow.`) + } + const moduleType = module?.value.type + const hasSpecificComponents = ['forloopflow', 'whileloopflow', 'branchall', 'branchone'] + const prefix = hasSpecificComponents.includes(moduleType) ? `${moduleType}` : 'flow' + if (typeof parsedArgs.stop_after_if === 'boolean') { + emitUiIntent({ + kind: 'open_module_tab', + componentId: `${prefix}-${parsedArgs.id}`, + tab: 'early-stop' + }) + } + + if (typeof parsedArgs.skip_if === 'boolean') { + emitUiIntent({ + kind: 'open_module_tab', + componentId: `${prefix}-${parsedArgs.id}`, + tab: 'skip' + }) + } + + const message = `Set module '${parsedArgs.id}' control options` + toolCallbacks.setToolStatus(toolId, { + content: message + }) + return `${message}: ${JSON.stringify(parsedArgs)}` + } + }, { def: resourceTypeToolDef, fn: async ({ args, toolId, workspace, toolCallbacks }) => { @@ -790,10 +931,17 @@ When creating new steps, follow this process for EACH step: ### Special Step Types For special step types, follow these additional steps: -- For forloop steps: Set the iterator expression using set_forloop_iterator_expression +- For forloop steps: + - Set the iterator expression using set_forloop_iterator_expression + - Set advanced options (parallel, parallelism, skip_failures) using set_forloop_options - For branchone steps: Set the predicates for each branch using set_branch_predicate - For branchall steps: No additional setup needed +### Module Control Options +For any module type, you can set control flow options using set_module_control_options: +- **stop_after_if**: Early stop condition - stops the module if expression evaluates to true. Can use "flow_input" or "result". "result" is the result of the step. "results." is not supported, do not use it. Example: "flow_input.x > 10" or "result === "failure"" +- **skip_if**: Skip condition - skips the module entirely if expression evaluates to true. Can use "flow_input" or "results.". Example: "flow_input.x > 10" or "results.a === "failure"" + ### Step Insertion Rules When adding steps, carefully consider the execution order: 1. Steps are executed in the order they appear in the flow definition, not in the order they were added @@ -814,6 +962,7 @@ When adding steps, carefully consider the execution order: ### JavaScript Expressions For step inputs, forloop iterator expressions and branch predicates, use JavaScript expressions with these variables: - Step results: results.stepid or results.stepid.property_name +- Break condition (stop_after_if) in for loops: result (contains the result of the last iteration) - Loop iterator: flow_input.iter.value (inside loops) - Flow inputs: flow_input.property_name - Static values: Use JavaScript syntax (e.g., "hello", true, 3) @@ -822,6 +971,12 @@ Note: These variables are only accessible in step inputs, forloop iterator expre For truly static values in step inputs (those not linked to previous steps or loop iterations), prefer using flow inputs by default unless explicitly specified otherwise. This makes the flow more configurable and reusable. For example, instead of hardcoding an email address in a step input, create a flow input for it. +### For Loop Advanced Options +When configuring for-loop steps, consider these options: +- **parallel: true** - Run iterations in parallel for independent operations (significantly faster for I/O bound tasks) +- **parallelism: N** - Limit concurrent iterations (only applies when parallel=true). Use to prevent overwhelming external APIs +- **skip_failures: true** - Continue processing remaining iterations even if some fail. Failed iterations return error objects as results + ### Special Modules - Preprocessor: Runs before the first step when triggered externally - ID: 'preprocessor' diff --git a/frontend/src/lib/components/copilot/chat/flow/uiIntents.ts b/frontend/src/lib/components/copilot/chat/flow/uiIntents.ts new file mode 100644 index 0000000000..45af45204c --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/flow/uiIntents.ts @@ -0,0 +1,9 @@ +import { writable, type Writable } from 'svelte/store' + +export type UiIntent = { kind: 'open_module_tab'; componentId: string; tab: string } + +export const uiIntentStore: Writable = writable(null) + +export function emitUiIntent(intent: UiIntent) { + uiIntentStore.set(intent) +} diff --git a/frontend/src/lib/components/copilot/chat/flow/useUiIntent.ts b/frontend/src/lib/components/copilot/chat/flow/useUiIntent.ts new file mode 100644 index 0000000000..17f841adfa --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/flow/useUiIntent.ts @@ -0,0 +1,17 @@ +import { uiIntentStore, type UiIntent } from './uiIntents' +import { onDestroy } from 'svelte' + +type Handlers = { + openTab?: (tab: string) => void +} + +export function useUiIntent(componentId: string, handlers: Handlers) { + const unsub = uiIntentStore.subscribe((intent: UiIntent | null) => { + if (!intent || intent.componentId !== componentId) return + + if (intent.kind === 'open_module_tab') handlers.openTab?.(intent.tab) + + uiIntentStore.set(null) + }) + onDestroy(() => unsub()) +} diff --git a/frontend/src/lib/components/flows/content/FlowBranchesAllWrapper.svelte b/frontend/src/lib/components/flows/content/FlowBranchesAllWrapper.svelte index ec51626d08..d14dd000f1 100644 --- a/frontend/src/lib/components/flows/content/FlowBranchesAllWrapper.svelte +++ b/frontend/src/lib/components/flows/content/FlowBranchesAllWrapper.svelte @@ -15,6 +15,7 @@ import { enterpriseLicense } from '$lib/stores' import FlowModuleSkip from './FlowModuleSkip.svelte' import TabsV2 from '$lib/components/common/tabs/TabsV2.svelte' + import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent' interface Props { noEditor: boolean @@ -31,6 +32,12 @@ }) let selected = $state('early-stop') + + useUiIntent(`branchall-${flowModule.id}`, { + openTab: (tab) => { + selected = tab + } + })
diff --git a/frontend/src/lib/components/flows/content/FlowBranchesOneWrapper.svelte b/frontend/src/lib/components/flows/content/FlowBranchesOneWrapper.svelte index 38c1406d30..0c26769e27 100644 --- a/frontend/src/lib/components/flows/content/FlowBranchesOneWrapper.svelte +++ b/frontend/src/lib/components/flows/content/FlowBranchesOneWrapper.svelte @@ -15,6 +15,7 @@ import FlowModuleMock from './FlowModuleMock.svelte' import { enterpriseLicense } from '$lib/stores' import FlowModuleSkip from './FlowModuleSkip.svelte' + import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent' interface Props { // import FlowRetries from './FlowRetries.svelte' @@ -39,6 +40,12 @@ }) let selected = $state('early-stop') + + useUiIntent(`branchone-${flowModule.id}`, { + openTab: (tab) => { + selected = tab + } + })
diff --git a/frontend/src/lib/components/flows/content/FlowLoop.svelte b/frontend/src/lib/components/flows/content/FlowLoop.svelte index 038dce099c..bb6b4cd820 100644 --- a/frontend/src/lib/components/flows/content/FlowLoop.svelte +++ b/frontend/src/lib/components/flows/content/FlowLoop.svelte @@ -27,6 +27,7 @@ import PropPickerWrapper, { CONNECT } from '../propPicker/PropPickerWrapper.svelte' import type { PropPickerContext } from '$lib/components/prop_picker' import TabsV2 from '$lib/components/common/tabs/TabsV2.svelte' + import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent' const { previewArgs, flowStateStore, flowStore, currentEditor } = getContext('FlowEditorContext') @@ -50,6 +51,13 @@ let editor: SimpleEditor | undefined = $state(undefined) let selected: string = $state('early-stop') + // UI Intent handling for AI tool control + useUiIntent(`forloopflow-${mod.id}`, { + openTab: (tab) => { + selected = tab + } + }) + const { flowPropPickerConfig } = getContext('PropPickerContext') flowPropPickerConfig.set(undefined) diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 072139549f..bc857b1226 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -54,6 +54,7 @@ import { refreshStateStore } from '$lib/svelte5Utils.svelte' import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte' import AssetsDropdownButton from '$lib/components/assets/AssetsDropdownButton.svelte' + import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent' const { selectedId, @@ -135,6 +136,13 @@ let assets = $derived((flowModule.value.type === 'rawscript' && flowModule.value.assets) || []) + // UI Intent handling for AI tool control + useUiIntent(`flow-${flowModule.id}`, { + openTab: (tab) => { + selectAdvanced(tab) + } + }) + function onModulesChange(savedModule: FlowModule | undefined, flowModule: FlowModule) { // console.log('onModulesChange', savedModule, flowModule) return savedModule?.value?.type === 'rawscript' && diff --git a/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte b/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte index d6ed0740bb..edcfdca5f1 100644 --- a/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte +++ b/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte @@ -19,6 +19,7 @@ import FlowModuleDeleteAfterUse from './FlowModuleDeleteAfterUse.svelte' import FlowModuleSkip from './FlowModuleSkip.svelte' import TabsV2 from '$lib/components/common/tabs/TabsV2.svelte' + import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent' const { flowStateStore } = getContext('FlowEditorContext') @@ -38,6 +39,12 @@ let job: Job | undefined = $state(undefined) let previewIterationArgs = $derived(flowStateStore.val[mod.id]?.previewArgs ?? {}) + + useUiIntent(`whileloopflow-${mod.id}`, { + openTab: (tab) => { + selected = tab + } + }) From f90d44469e0e4b462a5fa5160b64e99eef95c317 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 26 Aug 2025 17:06:26 +0200 Subject: [PATCH 03/40] fix(frontend): nats config conditional fields (#6473) --- .../nats/NatsTriggerEditorInner.svelte | 48 +++++++++------- .../nats/NatsTriggersConfigSection.svelte | 56 +++++++------------ 2 files changed, 49 insertions(+), 55 deletions(-) diff --git a/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte index 0e0f7e43d5..86b948dd98 100644 --- a/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte @@ -74,11 +74,16 @@ let showLoading = $state(false) let defaultValues: Record | undefined = $state(undefined) let natsResourcePath = $state('') - let subjects = $state(['']) - let useJetstream = $state(false) - let streamName = $state('') - let consumerName = $state('') let initialConfig: Record | undefined = undefined + let natsCfg: { + subjects: string[] + use_jetstream: boolean + stream_name?: string + consumer_name?: string + } = $state({ + subjects: [], + use_jetstream: false + }) let deploymentLoading = $state(false) let isValid = $state(false) let optionTabSelected: 'error_handler' | 'retries' = $state('error_handler') @@ -140,10 +145,13 @@ edit = false itemKind = nis_flow ? 'flow' : 'script' natsResourcePath = nDefaultValues?.nats_resource_path ?? '' - subjects = nDefaultValues?.subjects ?? [''] - useJetstream = nDefaultValues?.use_jetstream ?? false - streamName = useJetstream ? (nDefaultValues?.stream_name ?? '') : undefined - consumerName = useJetstream ? (nDefaultValues?.consumer_name ?? '') : undefined + const useJetstream = nDefaultValues?.use_jetstream ?? false + natsCfg = { + subjects: nDefaultValues?.subjects ?? [''], + use_jetstream: useJetstream, + stream_name: useJetstream ? (nDefaultValues?.stream_name ?? '') : undefined, + consumer_name: useJetstream ? (nDefaultValues?.consumer_name ?? '') : undefined + } initialScriptPath = '' fixedScriptPath = fixedScriptPath_ ?? '' script_path = fixedScriptPath @@ -169,10 +177,13 @@ is_flow = cfg?.is_flow path = cfg?.path natsResourcePath = cfg?.nats_resource_path - streamName = cfg?.stream_name - consumerName = cfg?.consumer_name - subjects = cfg?.subjects || [''] - useJetstream = cfg?.use_jetstream || false + const useJetstream = cfg?.use_jetstream || false + natsCfg = { + subjects: cfg?.subjects || [''], + use_jetstream: useJetstream, + stream_name: useJetstream ? cfg?.stream_name || '' : undefined, + consumer_name: useJetstream ? cfg?.consumer_name || '' : undefined + } enabled = cfg?.enabled can_write = canWrite(cfg?.path, cfg?.extra_perms, $userStore) error_handler_path = cfg?.error_handler_path @@ -201,10 +212,10 @@ is_flow, enabled, nats_resource_path: natsResourcePath, - stream_name: streamName, - consumer_name: consumerName, - subjects, - use_jetstream: useJetstream, + stream_name: natsCfg.stream_name, + consumer_name: natsCfg.consumer_name, + subjects: natsCfg.subjects, + use_jetstream: natsCfg.use_jetstream, error_handler_path, error_handler_args, retry @@ -390,10 +401,7 @@ { isValid = detail }} diff --git a/frontend/src/lib/components/triggers/nats/NatsTriggersConfigSection.svelte b/frontend/src/lib/components/triggers/nats/NatsTriggersConfigSection.svelte index d925552ff0..22ffe22e64 100644 --- a/frontend/src/lib/components/triggers/nats/NatsTriggersConfigSection.svelte +++ b/frontend/src/lib/components/triggers/nats/NatsTriggersConfigSection.svelte @@ -6,16 +6,18 @@ import SchemaForm from '$lib/components/SchemaForm.svelte' import TestTriggerConnection from '../TestTriggerConnection.svelte' import TestingBadge from '../testingBadge.svelte' - import { createEventDispatcher } from 'svelte' + import { createEventDispatcher, untrack } from 'svelte' interface Props { defaultValues?: Record | undefined headless?: boolean natsResourcePath: string - subjects: string[] - useJetstream: boolean - streamName: string - consumerName: string + natsCfg: { + subjects: string[] + use_jetstream: boolean + stream_name?: string + consumer_name?: string + } path: string can_write?: boolean showTestingBadge?: boolean @@ -27,10 +29,7 @@ defaultValues = undefined, headless = false, natsResourcePath = $bindable(), - subjects = $bindable(), - useJetstream = $bindable(), - streamName = $bindable(), - consumerName = $bindable(), + natsCfg = $bindable(), path, can_write = true, showTestingBadge = false @@ -38,7 +37,7 @@ let otherArgsValid = $state(false) let globalError = $derived( - !useJetstream && subjects && subjects.length > 1 + !natsCfg.use_jetstream && natsCfg.subjects && natsCfg.subjects.length > 1 ? 'Only one subject is supported if not using JetStream.' : '' ) @@ -91,40 +90,27 @@ const valid = isConnectionValid && otherArgsValid && - !!subjects && - subjects.length > 0 && - subjects.every((b) => /^[a-zA-Z0-9-_.*>]+$/.test(b)) && + !!natsCfg.subjects && + natsCfg.subjects.length > 0 && + natsCfg.subjects.every((b) => /^[a-zA-Z0-9-_.*>]+$/.test(b)) && globalError === '' dispatch('valid-config', valid) }) function setStreamAndConsumerNames() { - if (!streamName) { - streamName = `windmill_stream-${$workspaceStore}-${path.replaceAll('/', '__')}` + if (!natsCfg.stream_name) { + natsCfg.stream_name = `windmill_stream-${$workspaceStore}-${path.replaceAll('/', '__')}` } - if (!consumerName) { - consumerName = `windmill_consumer-${$workspaceStore}-${path.replaceAll('/', '__')}` + if (!natsCfg.consumer_name) { + natsCfg.consumer_name = `windmill_consumer-${$workspaceStore}-${path.replaceAll('/', '__')}` } } - function setNewArgs(args: Record) { - subjects = args.subjects - useJetstream = args.use_jetstream - streamName = args.stream_name - consumerName = args.consumer_name - if (args.use_jetstream) { - setStreamAndConsumerNames() + $effect(() => { + if (natsCfg.use_jetstream) { + untrack(() => setStreamAndConsumerNames()) } - } - - function getNatsArgsCfg() { - return { - subjects, - use_jetstream: useJetstream, - stream_name: streamName, - consumer_name: consumerName - } - } + })
@@ -160,7 +146,7 @@ setNewArgs(args)} + bind:args={natsCfg} bind:isValid={otherArgsValid} lightHeader={true} disabled={!can_write} From 475f405d0626f1c22309ee6a1b630472a89dbb30 Mon Sep 17 00:00:00 2001 From: pyranota <92104930+pyranota@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:07:15 +0200 Subject: [PATCH 04/40] fix(go): exec: "git": executable file not found (#6475) --- backend/windmill-worker/src/go_executor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index 8fe187b8dd..dd5661be1a 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -571,6 +571,7 @@ pub async fn install_go_dependencies( child_cmd .current_dir(job_dir) .env_clear() + .env("PATH", PATH_ENV.as_str()) .env("GOPATH", { #[cfg(unix)] { From d9ca181b1d8d26c175ec2a05409c45daeab887a4 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:19:38 +0200 Subject: [PATCH 05/40] fix: save changes made in diff mode (#6477) * add read only to diff editor * save changes to editor instead * only add listener if oncodechange is specified * pass existing editor as modified model * remove effect * cleaning --- frontend/src/lib/components/DiffEditor.svelte | 32 +++++++++++-------- .../src/lib/components/ScriptEditor.svelte | 4 ++- .../flows/content/FlowModuleComponent.svelte | 4 ++- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/frontend/src/lib/components/DiffEditor.svelte b/frontend/src/lib/components/DiffEditor.svelte index 524c7e264c..186712f214 100644 --- a/frontend/src/lib/components/DiffEditor.svelte +++ b/frontend/src/lib/components/DiffEditor.svelte @@ -32,6 +32,7 @@ defaultModified?: string readOnly?: boolean buttons?: ButtonProp[] + modifiedModel?: meditor.ITextModel } let { @@ -44,7 +45,8 @@ defaultOriginal = undefined, defaultModified = undefined, readOnly = false, - buttons = [] + buttons = [], + modifiedModel }: Props = $props() let diffEditor: meditor.IStandaloneDiffEditor | undefined = $state(undefined) @@ -72,11 +74,8 @@ lineNumbersMinChars: 2, scrollbar: { alwaysConsumeMouseWheel: false } }) - if ( - defaultOriginal !== undefined && - defaultModified !== undefined && - defaultLang !== undefined - ) { + + if (defaultLang !== undefined) { setupModel(defaultLang, defaultOriginal, defaultModified, defaultModifiedLang) } } @@ -87,16 +86,12 @@ modified?: string, modifiedLang?: string ) { + const o = meditor.createModel(original ?? '', lang) + const m = modifiedModel ?? meditor.createModel(modified ?? '', modifiedLang ?? lang) diffEditor?.setModel({ - original: meditor.createModel('', lang), - modified: meditor.createModel('', modifiedLang ?? lang) + original: o, + modified: m }) - if (original) { - setOriginal(original) - } - if (modified) { - setModified(modified) - } } export function setOriginal(code: string) { @@ -113,6 +108,15 @@ defaultModified = code } + export function setModifiedModel(model: meditor.ITextModel) { + const curr = diffEditor?.getModel() + if (!curr) return + diffEditor?.setModel({ + original: curr.original, + modified: model + }) + } + export function getModified(): string { return diffEditor?.getModel()?.modified.getValue() ?? '' } diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index 677d69fa4c..e7ebe6c0d1 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -48,6 +48,7 @@ import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte' import AssetsDropdownButton from './assets/AssetsDropdownButton.svelte' import { assetEq, type AssetWithAltAccessType } from './assets/lib' + import { editor as meditor } from 'monaco-editor' interface Props { // Exported @@ -403,7 +404,7 @@ function showDiffMode() { diffMode = true diffEditor?.setOriginal(lastDeployedCode ?? '') - diffEditor?.setModified(editor?.getCode() ?? '') + diffEditor?.setModifiedModel(editor?.getModel() as meditor.ITextModel) diffEditor?.show() editor?.hide() } @@ -628,6 +629,7 @@ Date: Wed, 27 Aug 2025 13:11:54 +0200 Subject: [PATCH 06/40] fix(go): could not read Username for 'xyz': terminal prompts disabled (#6478) * fix(go): could not read Username for 'xyz': terminal prompts disabled Signed-off-by: pyranota * remove unused import Signed-off-by: pyranota --------- Signed-off-by: pyranota --- backend/windmill-worker/src/go_executor.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index dd5661be1a..201da5b959 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -571,6 +571,7 @@ pub async fn install_go_dependencies( child_cmd .current_dir(job_dir) .env_clear() + .env("HOME", HOME_ENV.as_str()) .env("PATH", PATH_ENV.as_str()) .env("GOPATH", { #[cfg(unix)] @@ -585,6 +586,18 @@ pub async fn install_go_dependencies( .args(vec!["mod", mod_command]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); + + if let Some(ref goprivate) = *GOPRIVATE { + child_cmd.env("GOPRIVATE", goprivate); + } + + // TODO: Remove if no incidents reported + if !std::env::var("WMDEBUG_NO_GOPROXY_ON_TIDY").ok().is_some() { + if let Some(ref goproxy) = *GOPROXY { + child_cmd.env("GOPROXY", goproxy); + } + } + // If annotation used we want to call tidy with special flag to pin go to 1.22 // The reason for this that at some point we had to jump from go 1.22 to 1.25 and this addds backward compatibility. if anns.go1_22_compat && mod_command == "tidy" { From ceb9150f43a0ae9f8579f1984e791f69e7a05366 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Wed, 27 Aug 2025 13:12:05 +0200 Subject: [PATCH 07/40] feat: email triggers extra args in 'to' header (#6476) * feat: email triggers extra args in 'to' header * ee-repo + script helpers --- backend/ee-repo-ref.txt | 2 +- frontend/src/lib/components/triggers/TriggersWrapper.svelte | 1 + frontend/src/lib/script_helpers.ts | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 7e96e8cdec..40f7e37b75 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -15a7592ca66b93b9760d49e58b23c090ead06fe2 +eabd52eaa454c37a5beb1f456c1ea649052eb58a diff --git a/frontend/src/lib/components/triggers/TriggersWrapper.svelte b/frontend/src/lib/components/triggers/TriggersWrapper.svelte index 3866e9343a..5932885494 100644 --- a/frontend/src/lib/components/triggers/TriggersWrapper.svelte +++ b/frontend/src/lib/components/triggers/TriggersWrapper.svelte @@ -77,6 +77,7 @@ scopes={isFlow ? [`jobs:run:flows:${currentPath}`] : [`jobs:run:scripts:${currentPath}`]} path={initialPath || fakeInitialPath} {isFlow} + {hash} on:email-domain /> {:else if selectedTrigger.type === 'schedule'} diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index 7c02bd6aea..89fdab1d3b 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -710,6 +710,7 @@ export const TS_PREPROCESSOR_MODULE_CODE = `export async function preprocessor( kind: "email"; parsed_email: any; raw_email: string; + email_extra_args?: Record; } | { kind: "websocket"; msg: string; url: string } | { @@ -863,6 +864,7 @@ class EmailEvent(TypedDict): kind: Literal["email"] parsed_email: dict raw_email: str + email_extra_args: Optional[dict[str, str]] class WebsocketEvent(TypedDict): From 2066a2ada2f3139474527f373dc505b7e61d5182 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Wed, 27 Aug 2025 14:49:46 +0200 Subject: [PATCH 08/40] fix(cli): specific items for file resource type (#6464) * cli file resource specific items * improvement * resource command + correct order of context * no dynamic imports * support trigger types for branch specific items * also update trigger cli function to be branch aware * hubscript path --- cli/src/commands/resource/resource.ts | 44 +++++++-- cli/src/commands/script/script.ts | 24 ++++- cli/src/commands/sync/sync.ts | 37 +++++++- cli/src/commands/trigger/trigger.ts | 35 ++++--- cli/src/core/conf.ts | 4 + cli/src/core/context.ts | 33 ++++--- cli/src/core/specific_items.ts | 128 ++++++++++++++++++++++---- cli/src/types.ts | 27 +++++- cli/src/utils/utils.ts | 6 +- frontend/src/lib/hubPaths.json | 3 +- 10 files changed, 278 insertions(+), 63 deletions(-) diff --git a/cli/src/commands/resource/resource.ts b/cli/src/commands/resource/resource.ts index 3c62665842..4a62a28b70 100644 --- a/cli/src/commands/resource/resource.ts +++ b/cli/src/commands/resource/resource.ts @@ -11,6 +11,8 @@ import { colors, Command, log, SEP, Table } from "../../../deps.ts"; import * as wmill from "../../../gen/services.gen.ts"; import { Resource } from "../../../gen/types.gen.ts"; import { readInlinePathSync } from "../../utils/utils.ts"; +import { isBranchSpecificFile } from "../../core/specific_items.ts"; +import { getCurrentGitBranch } from "../../utils/git.ts"; export interface ResourceFile { value: any; @@ -23,7 +25,8 @@ export async function pushResource( workspace: string, remotePath: string, resource: ResourceFile | Resource | undefined, - localResource: ResourceFile + localResource: ResourceFile, + originalLocalPath?: string ): Promise { remotePath = removeType(remotePath, "resource"); try { @@ -35,21 +38,49 @@ export async function pushResource( // flow doesn't exist } - if (localResource.value["content"]?.startsWith("!inline ")) { - const basePath = localResource.value["content"].split(" ")[1]; - localResource.value["content"] = readInlinePathSync(basePath); - } + // Helper function to resolve inline content + const resolveInlineContent = async () => { + if (localResource.value["content"]?.startsWith("!inline ")) { + const basePath = localResource.value["content"].split(" ")[1]; + + // If we're processing a branch-specific metadata file, read from branch-specific resource file + + let pathToRead = basePath; + + if (originalLocalPath && isBranchSpecificFile(originalLocalPath)) { + const currentBranch = getCurrentGitBranch(); + if (currentBranch) { + // Directly construct branch-specific resource file path + const resourcePathSegments = basePath.split("."); + if (resourcePathSegments.length >= 4 && resourcePathSegments[resourcePathSegments.length - 3] === "resource" && resourcePathSegments[resourcePathSegments.length - 2] === "file") { + const fileBaseParts = resourcePathSegments.slice(0, -3); + const fileExt = resourcePathSegments.slice(-3); + pathToRead = [...fileBaseParts, currentBranch, ...fileExt].join("."); + } + } + } + + localResource.value["content"] = readInlinePathSync(pathToRead); + } + }; + if (resource) { if (isSuperset(localResource, resource)) { return; } + // Only resolve inline content if we're actually updating + await resolveInlineContent(); + await wmill.updateResource({ workspace: workspace, path: remotePath.replaceAll(SEP, "/"), requestBody: { ...localResource }, }); } else { + // New resource - resolve inline content + await resolveInlineContent(); + if (localResource.is_oauth) { log.info( colors.yellow( @@ -89,7 +120,8 @@ async function push(opts: PushOptions, filePath: string, remotePath: string) { workspace.workspaceId, remotePath, undefined, - parseFromFile(filePath) + parseFromFile(filePath), + filePath // Pass the local file path for branch-specific inline content resolution ); log.info(colors.bold.underline.green(`Resource ${remotePath} pushed`)); } diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index a2240498ab..9c4171a97e 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -15,6 +15,8 @@ import { } from "../../../deps.ts"; import { deepEqual } from "../../utils/utils.ts"; import * as wmill from "../../../gen/services.gen.ts"; +import * as specificItems from "../../core/specific_items.ts"; +import { getCurrentGitBranch } from "../../utils/git.ts"; import { defaultScriptMetadata, @@ -102,12 +104,24 @@ async function push(opts: PushOptions, filePath: string) { export async function findResourceFile(path: string) { const splitPath = path.split("."); - const contentBasePathJSON = splitPath[0] + "." + splitPath[1] + ".json"; - const contentBasePathYAML = splitPath[0] + "." + splitPath[1] + ".yaml"; + let contentBasePathJSON = splitPath[0] + "." + splitPath[1] + ".json"; + let contentBasePathYAML = splitPath[0] + "." + splitPath[1] + ".yaml"; + + // Check for branch-specific metadata files first + const currentBranch = getCurrentGitBranch(); + + const candidates = [contentBasePathJSON, contentBasePathYAML]; + + if (currentBranch) { + // Add branch-specific candidates at the beginning (higher priority) + const branchSpecificJSON = specificItems.toBranchSpecificPath(contentBasePathJSON, currentBranch); + const branchSpecificYAML = specificItems.toBranchSpecificPath(contentBasePathYAML, currentBranch); + candidates.unshift(branchSpecificJSON, branchSpecificYAML); + } const validCandidates = ( await Promise.all( - [contentBasePathJSON, contentBasePathYAML].map((x) => { + candidates.map((x) => { return Deno.stat(x) .catch(() => undefined) .then((x) => x?.isFile) @@ -580,7 +594,7 @@ export function filePathExtensionFromContentType( return ".java"; } else if (language === "ruby") { return ".rb"; - // for related places search: ADD_NEW_LANG + // for related places search: ADD_NEW_LANG } else { throw new Error("Invalid language: " + language); } @@ -611,7 +625,7 @@ export const exts = [ ".playbook.yml", ".java", ".rb" - // for related places search: ADD_NEW_LANG + // for related places search: ADD_NEW_LANG ]; export function removeExtensionToPath(path: string): string { diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index f7c4833273..a7b2fd4660 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -1930,11 +1930,21 @@ export async function push( await Deno.readTextFile(resourceFilePath) ); + // For branch-specific resources, push to the base path on the workspace server + // This ensures branch-specific files are stored with their base names in the workspace + let serverPath = resourceFilePath; + const currentBranch = getCurrentGitBranch(); + + if (currentBranch && isBranchSpecificFile(resourceFilePath)) { + serverPath = fromBranchSpecificPath(resourceFilePath, currentBranch); + } + await pushResource( workspace.workspaceId, - resourceFilePath, + serverPath, undefined, - newObj + newObj, + resourceFilePath ); if (stateTarget) { await Deno.writeTextFile(stateTarget, change.after); @@ -1945,6 +1955,12 @@ export async function push( const oldObj = parseFromPath(change.path, change.before); const newObj = parseFromPath(change.path, change.after); + // Check if this is a branch-specific item and get the original branch-specific path + let originalBranchSpecificPath: string | undefined; + if (specificItems && isSpecificItem(change.path, specificItems)) { + originalBranchSpecificPath = getBranchSpecificPath(change.path, specificItems); + } + await pushObj( workspace.workspaceId, change.path, @@ -1952,7 +1968,8 @@ export async function push( newObj, opts.plainSecrets ?? false, alreadySynced, - opts.message + opts.message, + originalBranchSpecificPath ); if (stateTarget) { @@ -1986,6 +2003,17 @@ export async function push( ); } const obj = parseFromPath(change.path, change.content); + + // Determine the actual local file path for this change + // For branch-specific items, we read from branch-specific files but push to base server paths + let localFilePath = change.path; + if (specificItems && isSpecificItem(change.path, specificItems)) { + const branchSpecificPath = getBranchSpecificPath(change.path, specificItems); + if (branchSpecificPath) { + localFilePath = branchSpecificPath; + } + } + await pushObj( workspace.workspaceId, change.path, @@ -1993,7 +2021,8 @@ export async function push( obj, opts.plainSecrets ?? false, [], - opts.message + opts.message, + localFilePath // Pass the actual local file path ); if (stateTarget) { diff --git a/cli/src/commands/trigger/trigger.ts b/cli/src/commands/trigger/trigger.ts index c9cd0e013d..7e3afd9288 100644 --- a/cli/src/commands/trigger/trigger.ts +++ b/cli/src/commands/trigger/trigger.ts @@ -15,7 +15,10 @@ import { isSuperset, parseFromFile, removeType, + TRIGGER_TYPES, } from "../../types.ts"; +import { fromBranchSpecificPath, isBranchSpecificFile } from "../../core/specific_items.ts"; +import { getCurrentGitBranch } from "../../utils/git.ts"; import { requireLogin } from "../../core/auth.ts"; import { validatePath, resolveWorkspace } from "../../core/context.ts"; @@ -222,25 +225,29 @@ async function list(opts: GlobalOptions) { } function checkIfValidTrigger(kind: string | undefined): kind is TriggerType { - if ( - kind && - [ - "http", - "websocket", - "kafka", - "nats", - "postgres", - "mqtt", - "sqs", - "gcp", - ].includes(kind) - ) { + if (kind && (TRIGGER_TYPES as readonly string[]).includes(kind)) { return true; } else { return false; } } +function extractTriggerKindFromPath(filePath: string): string | undefined { + let pathToAnalyze = filePath; + + // If this is a branch-specific file, convert it to the base path first + if (isBranchSpecificFile(filePath)) { + const currentBranch = getCurrentGitBranch(); + if (currentBranch) { + pathToAnalyze = fromBranchSpecificPath(filePath, currentBranch); + } + } + + // Now extract trigger type from the base path: "something.kafka_trigger.yaml" -> "kafka" + const triggerMatch = pathToAnalyze.match(/\.(\w+)_trigger\.yaml$/); + return triggerMatch ? triggerMatch[1] : undefined; +} + async function push(opts: GlobalOptions, filePath: string, remotePath: string) { const workspace = await resolveWorkspace(opts); await requireLogin(opts); @@ -256,7 +263,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) { console.log(colors.bold.yellow("Pushing trigger...")); - const triggerKind = filePath.split(".")[1].split("_")[0]; + const triggerKind = extractTriggerKindFromPath(filePath); if (!checkIfValidTrigger(triggerKind)) { throw new Error("Invalid trigger kind: " + triggerKind); } diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index 5f5c352862..b64d48b915 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -44,6 +44,7 @@ export interface SyncOptions { commonSpecificItems?: { variables?: string[]; resources?: string[]; + triggers?: string[]; }; } & { [branchName: string]: SyncOptions & { @@ -54,6 +55,7 @@ export interface SyncOptions { specificItems?: { variables?: string[]; resources?: string[]; + triggers?: string[]; }; }; }; @@ -62,6 +64,7 @@ export interface SyncOptions { commonSpecificItems?: { variables?: string[]; resources?: string[]; + triggers?: string[]; }; } & { [branchName: string]: SyncOptions & { @@ -72,6 +75,7 @@ export interface SyncOptions { specificItems?: { variables?: string[]; resources?: string[]; + triggers?: string[]; }; }; }; diff --git a/cli/src/core/context.ts b/cli/src/core/context.ts index 3fcfdc6510..92a6276aa3 100644 --- a/cli/src/core/context.ts +++ b/cli/src/core/context.ts @@ -109,15 +109,11 @@ async function tryResolveWorkspace( return { isError: false, value: e }; } - const defaultWorkspace = await getActiveWorkspace(opts); - if (!defaultWorkspace) { - return { - isError: true, - error: colors.red.underline("No workspace given and no default set."), - }; - } - - return { isError: false, value: defaultWorkspace }; + // Only check for explicit workspace, don't fallback to active workspace here + return { + isError: true, + error: colors.red.underline("No explicit workspace given."), + }; } async function tryResolveBranchWorkspace( @@ -259,6 +255,9 @@ async function tryResolveBranchWorkspace( export async function resolveWorkspace( opts: GlobalOptions ): Promise { + const cache = (opts as any).__secret_workspace; + if (cache) return cache; + if (opts.baseUrl) { if (opts.workspace && opts.token) { let normalizedBaseUrl: string; @@ -328,20 +327,28 @@ export async function resolveWorkspace( } } - // Try explicit workspace flag first (should override branch-based resolution) + // Try explicit workspace flag first (highest priority) const res = await tryResolveWorkspace(opts); if (!res.isError) { return res.value; } - // Fall back to branch-based resolution if no explicit workspace + // Try branch-based resolution (medium priority) const branchWorkspace = await tryResolveBranchWorkspace(opts); if (branchWorkspace) { + (opts as any).__secret_workspace = branchWorkspace; return branchWorkspace; } - // If both failed, show the original error from explicit workspace resolution - log.info(colors.red.bold(res.error)); + // Fall back to active workspace (lowest priority) + const activeWorkspace = await getActiveWorkspace(opts); + if (activeWorkspace) { + (opts as any).__secret_workspace = activeWorkspace; + return activeWorkspace; + } + + // If everything failed, show error + log.info(colors.red.bold("No workspace given and no default set.")); return Deno.exit(-1); } diff --git a/cli/src/core/specific_items.ts b/cli/src/core/specific_items.ts index ac0696ccf0..2e9b5052f0 100644 --- a/cli/src/core/specific_items.ts +++ b/cli/src/core/specific_items.ts @@ -1,10 +1,59 @@ import { minimatch } from "../../deps.ts"; import { getCurrentGitBranch, isGitRepository } from "../utils/git.ts"; +import { isFileResource } from "../utils/utils.ts"; import { SyncOptions } from "./conf.ts"; +import { TRIGGER_TYPES } from "../types.ts"; export interface SpecificItemsConfig { variables?: string[]; resources?: string[]; + triggers?: string[]; +} + +// Define all branch-specific file types (computed lazily) +function getBranchSpecificTypes() { + return { + variable: '.variable.yaml', + resource: '.resource.yaml', + // Generate trigger patterns from the list + ...Object.fromEntries( + TRIGGER_TYPES.map(t => [`${t}_trigger`, `.${t}_trigger.yaml`]) + ) + } as const; +} + +/** + * Check if a path ends with any trigger type + */ +function isTriggerFile(path: string): boolean { + return TRIGGER_TYPES.some(type => path.endsWith(`.${type}_trigger.yaml`)); +} + +/** + * Extract the file type suffix from a path + */ +function getFileTypeSuffix(path: string): string | null { + for (const [_, suffix] of Object.entries(getBranchSpecificTypes())) { + if (path.endsWith(suffix)) { + return suffix; + } + } + + const resourceFileMatch = path.match(/(\\.resource\\.file\\..+)$/); + if (resourceFileMatch) { + return resourceFileMatch[1]; + } + + return null; +} + +/** + * Build regex pattern for all supported yaml file types + */ +function buildYamlTypePattern(): string { + const basicTypes = ['variable', 'resource']; + const triggerTypes = TRIGGER_TYPES.map(t => `${t}_trigger`); + return `((${basicTypes.join('|')})|(${triggerTypes.join('|')}))`; } /** @@ -39,6 +88,9 @@ export function getSpecificItemsForCurrentBranch(config: SyncOptions): SpecificI if (commonItems?.resources) { merged.resources = [...commonItems.resources]; } + if (commonItems?.triggers) { + merged.triggers = [...commonItems.triggers]; + } // Add branch-specific items (extending common items) if (branchItems?.variables) { @@ -47,6 +99,9 @@ export function getSpecificItemsForCurrentBranch(config: SyncOptions): SpecificI if (branchItems?.resources) { merged.resources = [...(merged.resources || []), ...branchItems.resources]; } + if (branchItems?.triggers) { + merged.triggers = [...(merged.triggers || []), ...branchItems.triggers]; + } return merged; } @@ -75,6 +130,21 @@ export function isSpecificItem(path: string, specificItems: SpecificItemsConfig return specificItems.resources ? matchesPatterns(path, specificItems.resources) : false; } + // Check for any trigger type + if (isTriggerFile(path)) { + return specificItems.triggers ? matchesPatterns(path, specificItems.triggers) : false; + } + + // Check for resource files using the standard detection function + if (isFileResource(path)) { + // Extract the base path without the file extension to match against patterns + const basePathMatch = path.match(/^(.+?)\.resource\.file\./); + if (basePathMatch && specificItems.resources) { + const basePath = basePathMatch[1] + '.resource.yaml'; + return matchesPatterns(basePath, specificItems.resources); + } + } + return false; } @@ -82,14 +152,24 @@ export function isSpecificItem(path: string, specificItems: SpecificItemsConfig * Convert a base path to a branch-specific path */ export function toBranchSpecificPath(basePath: string, branchName: string): string { - // Extract the extension (e.g., ".variable.yaml" or ".resource.yaml") - const extensionMatch = basePath.match(/(\.(variable|resource)\.yaml)$/); - if (!extensionMatch) { - return basePath; // Return unchanged if no recognized extension - } + // Check for resource file pattern (e.g., .resource.file.ini) + const resourceFileMatch = basePath.match(/^(.+?)(\.resource\.file\..+)$/); - const extension = extensionMatch[1]; - const pathWithoutExtension = basePath.substring(0, basePath.length - extension.length); + let extension: string; + let pathWithoutExtension: string; + + if (resourceFileMatch) { + // Handle resource files + extension = resourceFileMatch[2]; + pathWithoutExtension = resourceFileMatch[1]; + } else { + const suffix = getFileTypeSuffix(basePath); + if (!suffix) { + return basePath; + } + extension = suffix; + pathWithoutExtension = basePath.substring(0, basePath.length - extension.length); + } // Sanitize branch name to be filesystem-safe const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_'); @@ -108,17 +188,29 @@ export function toBranchSpecificPath(basePath: string, branchName: string): stri export function fromBranchSpecificPath(branchSpecificPath: string, branchName: string): string { // Sanitize branch name the same way as in toBranchSpecificPath const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_'); - - // Pattern: path.sanitizedBranchName.extension const escapedBranchName = sanitizedBranchName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const pattern = new RegExp(`\\.${escapedBranchName}(\\.(variable|resource)\\.yaml)$`); - const match = branchSpecificPath.match(pattern); - if (!match) { + // Check for resource file pattern first + const resourceFilePattern = new RegExp(`\\.${escapedBranchName}(\\.resource\\.file\\..+)$`); + const resourceFileMatch = branchSpecificPath.match(resourceFilePattern); + + if (resourceFileMatch) { + const extension = resourceFileMatch[1]; + const pathWithoutBranchAndExtension = branchSpecificPath.substring( + 0, + branchSpecificPath.length - `.${sanitizedBranchName}${extension}`.length + ); + return `${pathWithoutBranchAndExtension}${extension}`; + } + + const yamlPattern = new RegExp(`\\.${escapedBranchName}(\\.${buildYamlTypePattern()}\\.yaml)$`); + const yamlMatch = branchSpecificPath.match(yamlPattern); + + if (!yamlMatch) { return branchSpecificPath; // Return unchanged if not a branch-specific path } - const extension = match[1]; + const extension = yamlMatch[1]; const pathWithoutBranchAndExtension = branchSpecificPath.substring( 0, branchSpecificPath.length - `.${sanitizedBranchName}${extension}`.length @@ -166,10 +258,14 @@ export function isCurrentBranchFile(path: string): boolean { return false; } + // Sanitize branch name to match what would be used in file naming + const sanitizedBranchName = currentBranch.replace(/[\/\\:*?"<>|.]/g, '_'); + const escapedBranchName = sanitizedBranchName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // Use cached pattern or create and cache new one let pattern = branchPatternCache.get(currentBranch); if (!pattern) { - pattern = new RegExp(`\\.${currentBranch}\\.(variable|resource)\\.yaml$`); + pattern = new RegExp(`\\.${escapedBranchName}\\.${buildYamlTypePattern()}\\.yaml$|\\.${escapedBranchName}\\.resource\\.file\\..+$`); branchPatternCache.set(currentBranch, pattern); } @@ -181,6 +277,6 @@ export function isCurrentBranchFile(path: string): boolean { * Used to identify and skip files from other branches during sync operations */ export function isBranchSpecificFile(path: string): boolean { - // Pattern: *.branchName.variable.yaml or *.branchName.resource.yaml - return /\.[^.]+\.(variable|resource)\.yaml$/.test(path); + const yamlTypePattern = buildYamlTypePattern(); + return new RegExp(`\\.[^.]+\\.${yamlTypePattern}\\.yaml$|\\.[^.]+\\.resource\\.file\\..+$`).test(path); } diff --git a/cli/src/types.ts b/cli/src/types.ts index 57c1c9b8e7..123f132662 100644 --- a/cli/src/types.ts +++ b/cli/src/types.ts @@ -45,6 +45,17 @@ export interface DifferenceChange { export type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange; +export const TRIGGER_TYPES = [ + 'http', + 'websocket', + 'kafka', + 'nats', + 'postgres', + 'mqtt', + 'sqs', + 'gcp' +] as const; + export type GlobalOptions = { baseUrl: string | undefined; workspace: string | undefined; @@ -111,6 +122,17 @@ export function showConflict(path: string, local: string, remote: string) { log.info("\n"); } +/** + * Pushes an object to the workspace server based on its type + * @param workspace - The workspace ID to push to + * @param p - The server path (base path for branch-specific items) + * @param befObj - The previous object state (for updates) + * @param newObj - The new object state to push + * @param plainSecrets - Whether to store secrets in plain text + * @param alreadySynced - Array to track already synced items + * @param message - Optional commit/update message + * @param originalLocalPath - The original local file path (used for branch-specific resource file resolution) + */ export async function pushObj( workspace: string, p: string, @@ -118,7 +140,8 @@ export async function pushObj( newObj: any, plainSecrets: boolean, alreadySynced: string[], - message?: string + message?: string, + originalLocalPath?: string ) { const typeEnding = getTypeStrFromPath(p); @@ -135,7 +158,7 @@ export async function pushObj( } else if (typeEnding === "resource") { if (!alreadySynced.includes(p)) { alreadySynced.push(p); - await pushResource(workspace, p, befObj, newObj); + await pushResource(workspace, p, befObj, newObj, originalLocalPath || p); } } else if (typeEnding === "resource-type") { await pushResourceType(workspace, p, befObj, newObj); diff --git a/cli/src/utils/utils.ts b/cli/src/utils/utils.ts index 96bdfcebb0..cb8b808725 100644 --- a/cli/src/utils/utils.ts +++ b/cli/src/utils/utils.ts @@ -136,10 +136,12 @@ export function sleep(ms: number) { export function isFileResource(path: string): boolean { const splitPath = path.split("."); + + // Check for pattern: *.resource.file.* (handles both base and branch-specific) return ( splitPath.length >= 4 && - splitPath[1] == "resource" && - splitPath[2] == "file" + splitPath[splitPath.length - 3] == "resource" && + splitPath[splitPath.length - 2] == "file" ); } diff --git a/frontend/src/lib/hubPaths.json b/frontend/src/lib/hubPaths.json index b7d21a24ab..72ad185d0d 100644 --- a/frontend/src/lib/hubPaths.json +++ b/frontend/src/lib/hubPaths.json @@ -12,7 +12,8 @@ "gitSync_10": "hub/19785/sync-script-to-git-repo-windmill", "gitSync_11": "hub/19789/sync-script-to-git-repo-windmill", "gitSync_12": "hub/19798/sync-script-to-git-repo-windmill", - "gitSync": "hub/19801/sync-script-to-git-repo-windmill", + "gitSync_13": "hub/19801/sync-script-to-git-repo-windmill", + "gitSync": "hub/19803/sync-script-to-git-repo-windmill", "gitSyncTest_0": "hub/9073/git-repo-test-read-write-windmill", "gitSyncTest_1": "hub/11499/git-repo-test-read-write-windmill", "gitSyncTest_2": "hub/11667/git-repo-test-read-write-windmill", From a245f701799141229d71fef16a3ff4032a958836 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 27 Aug 2025 13:08:52 +0000 Subject: [PATCH 09/40] not_found_if_none displays location --- backend/windmill-common/src/utils.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 6486819e61..771678bb2f 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -265,14 +265,18 @@ pub fn create_directory_sync(directory_path: &str) { .expect("could not create dir"); } +#[track_caller] pub fn not_found_if_none>(opt: Option, kind: &str, name: U) -> Result { if let Some(o) = opt { Ok(o) } else { + let loc = Location::caller(); Err(Error::NotFound(format!( - "{} not found at name {}", + "{} not found at name {} ({}:{})", kind, - name.as_ref() + name.as_ref(), + loc.file().split("/").last().unwrap_or_default(), + loc.line() ))) } } From 41a872725282ba4b78e8f9912bb1ac929b8557f7 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 27 Aug 2025 13:31:23 +0000 Subject: [PATCH 10/40] fix: do not require locked for scheduled jobs --- backend/windmill-queue/src/schedule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/windmill-queue/src/schedule.rs b/backend/windmill-queue/src/schedule.rs index 7f07b6f65e..ae3653881a 100644 --- a/backend/windmill-queue/src/schedule.rs +++ b/backend/windmill-queue/src/schedule.rs @@ -156,7 +156,7 @@ pub async fn push_scheduled_job<'c>( &mut *tx, &schedule.workspace_id, &schedule.script_path, - true, + false, ) .await?; From eceab931afa83c5b47d8d90e26f65f1854297e0c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 27 Aug 2025 14:43:03 +0100 Subject: [PATCH 11/40] chore(main): release 1.536.0 (#6471) * chore(main): release 1.536.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 19 ++++++ backend/Cargo.lock | 64 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 68 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98335f7532..be301a9f36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [1.536.0](https://github.com/windmill-labs/windmill/compare/v1.535.0...v1.536.0) (2025-08-27) + + +### Features + +* **aichat:** give advanced options tools to flow mode ([#6463](https://github.com/windmill-labs/windmill/issues/6463)) ([b26cea9](https://github.com/windmill-labs/windmill/commit/b26cea9d3e2f9a0acae335aad12206da491ac733)) +* email triggers extra args in 'to' header ([#6476](https://github.com/windmill-labs/windmill/issues/6476)) ([ceb9150](https://github.com/windmill-labs/windmill/commit/ceb9150f43a0ae9f8579f1984e791f69e7a05366)) + + +### Bug Fixes + +* **cli:** specific items for file resource type ([#6464](https://github.com/windmill-labs/windmill/issues/6464)) ([2066a2a](https://github.com/windmill-labs/windmill/commit/2066a2ada2f3139474527f373dc505b7e61d5182)) +* do not require locked for scheduled jobs ([41a8727](https://github.com/windmill-labs/windmill/commit/41a872725282ba4b78e8f9912bb1ac929b8557f7)) +* **frontend:** ai agent step nits ([#6469](https://github.com/windmill-labs/windmill/issues/6469)) ([2b03133](https://github.com/windmill-labs/windmill/commit/2b03133b2245bd42f3c64b915d72dd3f62eb65a4)) +* **frontend:** nats config conditional fields ([#6473](https://github.com/windmill-labs/windmill/issues/6473)) ([f90d444](https://github.com/windmill-labs/windmill/commit/f90d44469e0e4b462a5fa5160b64e99eef95c317)) +* **go:** could not read Username for 'xyz': terminal prompts disabled ([#6478](https://github.com/windmill-labs/windmill/issues/6478)) ([5808840](https://github.com/windmill-labs/windmill/commit/5808840b78e94a0b39614f37161f9def347a5352)) +* **go:** exec: "git": executable file not found ([#6475](https://github.com/windmill-labs/windmill/issues/6475)) ([475f405](https://github.com/windmill-labs/windmill/commit/475f405d0626f1c22309ee6a1b630472a89dbb30)) +* save changes made in diff mode ([#6477](https://github.com/windmill-labs/windmill/issues/6477)) ([d9ca181](https://github.com/windmill-labs/windmill/commit/d9ca181b1d8d26c175ec2a05409c45daeab887a4)) + ## [1.535.0](https://github.com/windmill-labs/windmill/compare/v1.534.1...v1.535.0) (2025-08-25) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5866575aa6..7b7bc45206 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2004,9 +2004,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -2014,9 +2014,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -15129,7 +15129,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "axum", @@ -15183,7 +15183,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "argon2", @@ -15300,7 +15300,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.535.0" +version = "1.536.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15315,7 +15315,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.535.0" +version = "1.536.0" dependencies = [ "chrono", "serde", @@ -15328,7 +15328,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "axum", @@ -15347,7 +15347,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "async-recursion", @@ -15427,7 +15427,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.535.0" +version = "1.536.0" dependencies = [ "regex", "serde", @@ -15442,7 +15442,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "bytes", @@ -15466,7 +15466,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.535.0" +version = "1.536.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15478,7 +15478,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.535.0" +version = "1.536.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15487,7 +15487,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "lazy_static", @@ -15499,7 +15499,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "serde_json", @@ -15511,7 +15511,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "gosyn", @@ -15523,7 +15523,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "lazy_static", @@ -15535,7 +15535,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "serde_json", @@ -15547,7 +15547,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "nu-parser", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15569,7 +15569,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15581,7 +15581,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "async-recursion", @@ -15604,7 +15604,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "lazy_static", @@ -15618,7 +15618,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15635,7 +15635,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "lazy_static", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "lazy_static", @@ -15667,7 +15667,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15692,7 +15692,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "serde_json", @@ -15702,7 +15702,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "async-recursion", @@ -15735,7 +15735,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.535.0" +version = "1.536.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15745,7 +15745,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.535.0" +version = "1.536.0" dependencies = [ "anyhow", "async-recursion", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 7168cade28..3f770576bf 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.535.0" +version = "1.536.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ ] [workspace.package] -version = "1.535.0" +version = "1.536.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 29597b13c7..c2471cebfb 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.535.0 + version: 1.536.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index aa2dbfd3d3..cdebe91692 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.535.0"; +export const VERSION = "v1.536.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index ef8c27d7c1..8f98e43c0c 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.535.0"; +export const VERSION = "1.536.0"; const command = new Command() .name("wmill") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 28b8ba6c03..6401b0b925 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.535.0", + "version": "1.536.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.535.0", + "version": "1.536.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index e13e9c41fb..1f0841eea9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.535.0", + "version": "1.536.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index a8eecc08a4..5fe27bbf38 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.535.0" -wmill_pg = ">=1.535.0" +wmill = ">=1.536.0" +wmill_pg = ">=1.536.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 8e80d0d590..653590ef27 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.535.0 + version: 1.536.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index ffb3b68db2..68a5fd2cc3 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.535.0' + ModuleVersion = '1.536.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 678cbfbc9e..459257aee7 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.535.0" +version = "1.536.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 41bc27efcc..a515d6d8ae 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.535.0" +version = "1.536.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 0d1db552c2..49fd1c2814 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.535.0", + "version": "1.536.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index a718d87200..6018f1935e 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.535.0", + "version": "1.536.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index e609c600dd..3fe765ee77 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.535.0 +1.536.0 From 86f41ffcdea5a256980f43cf213c32175f501155 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 27 Aug 2025 15:08:58 +0000 Subject: [PATCH 12/40] minor nits fix --- ...e0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json} | 4 ++-- ...9e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json} | 4 ++-- ...8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json} | 4 ++-- backend/ee-repo-ref.txt | 2 +- backend/windmill-api/src/scripts.rs | 3 +++ backend/windmill-api/src/variables.rs | 6 +++++- backend/windmill-common/src/variables.rs | 2 ++ 7 files changed, 17 insertions(+), 8 deletions(-) rename backend/.sqlx/{query-434d8dfbc25cf7e92de51d763d3a2904ccc2e95ecc3d90b43a6394a7bb4d26ab.json => query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json} (69%) rename backend/.sqlx/{query-e77fcf4e0d58855542605d13177df61671334418820ca942b442adfab413cbae.json => query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json} (70%) rename backend/.sqlx/{query-f04632c3a8e0d7c5b48cdd26a99bb1dc5bd12df221f82405d663b8f15f5c0c3a.json => query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json} (69%) diff --git a/backend/.sqlx/query-434d8dfbc25cf7e92de51d763d3a2904ccc2e95ecc3d90b43a6394a7bb4d26ab.json b/backend/.sqlx/query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json similarity index 69% rename from backend/.sqlx/query-434d8dfbc25cf7e92de51d763d3a2904ccc2e95ecc3d90b43a6394a7bb4d26ab.json rename to backend/.sqlx/query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json index 0c657d6911..d643e4b8f8 100644 --- a/backend/.sqlx/query-434d8dfbc25cf7e92de51d763d3a2904ccc2e95ecc3d90b43a6394a7bb4d26ab.json +++ b/backend/.sqlx/query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", + "query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "434d8dfbc25cf7e92de51d763d3a2904ccc2e95ecc3d90b43a6394a7bb4d26ab" + "hash": "2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d" } diff --git a/backend/.sqlx/query-e77fcf4e0d58855542605d13177df61671334418820ca942b442adfab413cbae.json b/backend/.sqlx/query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json similarity index 70% rename from backend/.sqlx/query-e77fcf4e0d58855542605d13177df61671334418820ca942b442adfab413cbae.json rename to backend/.sqlx/query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json index de37a2fc04..1c2f9a9f33 100644 --- a/backend/.sqlx/query-e77fcf4e0d58855542605d13177df61671334418820ca942b442adfab413cbae.json +++ b/backend/.sqlx/query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", + "query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "e77fcf4e0d58855542605d13177df61671334418820ca942b442adfab413cbae" + "hash": "8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29" } diff --git a/backend/.sqlx/query-f04632c3a8e0d7c5b48cdd26a99bb1dc5bd12df221f82405d663b8f15f5c0c3a.json b/backend/.sqlx/query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json similarity index 69% rename from backend/.sqlx/query-f04632c3a8e0d7c5b48cdd26a99bb1dc5bd12df221f82405d663b8f15f5c0c3a.json rename to backend/.sqlx/query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json index aa8d76b04f..ace3edec94 100644 --- a/backend/.sqlx/query-f04632c3a8e0d7c5b48cdd26a99bb1dc5bd12df221f82405d663b8f15f5c0c3a.json +++ b/backend/.sqlx/query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", + "query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "f04632c3a8e0d7c5b48cdd26a99bb1dc5bd12df221f82405d663b8f15f5c0c3a" + "hash": "f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981" } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 40f7e37b75..111a3f2d18 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -eabd52eaa454c37a5beb1f456c1ea649052eb58a +6396854336ae27fb14ccb792d80c31ff614b2afa \ No newline at end of file diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index de2533a954..db4cb4e1e6 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -45,6 +45,7 @@ use windmill_worker::process_relative_imports; use windmill_common::{ assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType}, error::to_anyhow, + utils::WarnAfterExt, worker::CLOUD_HOSTED, }; @@ -1447,6 +1448,7 @@ async fn raw_script_by_path_internal( w_id ) .fetch_optional(&mut *tx) + .warn_after_seconds(5) .await?; tx.commit().await?; @@ -1457,6 +1459,7 @@ async fn raw_script_by_path_internal( w_id ) .fetch_one(&db) + .warn_after_seconds(5) .await? .unwrap_or(false); diff --git a/backend/windmill-api/src/variables.rs b/backend/windmill-api/src/variables.rs index 919396b7ec..12791753d0 100644 --- a/backend/windmill-api/src/variables.rs +++ b/backend/windmill-api/src/variables.rs @@ -24,7 +24,10 @@ use serde_json::Value; use windmill_audit::audit_oss::{audit_log, AuditAuthorable}; use windmill_audit::ActionKind; use windmill_common::{ - db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, Pagination, StripPath}, variables::{ + db::UserDB, + error::{Error, JsonResult, Result}, + utils::{not_found_if_none, paginate, Pagination, StripPath, WarnAfterExt}, + variables::{ build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable, }, worker::CLOUD_HOSTED, @@ -693,6 +696,7 @@ pub async fn get_value_internal<'c>( LEFT JOIN account ON variable.account = account.id WHERE variable.path = $1 AND variable.workspace_id = $2", path, w_id ) .fetch_optional(&mut *tx) + .warn_after_seconds(5) .await?; let variable = if let Some(variable) = variable_o { diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index 1dfa1438d8..740c82366a 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -7,6 +7,7 @@ */ use crate::error; +use crate::utils::WarnAfterExt; use crate::worker::Connection; use crate::{worker::WORKER_GROUP, BASE_URL, DB}; use chrono::{SecondsFormat, Utc}; @@ -106,6 +107,7 @@ pub async fn get_workspace_key(w_id: &str, db: &DB) -> crate::error::Result Date: Wed, 27 Aug 2025 17:29:06 +0000 Subject: [PATCH 13/40] feat: autovacuum or high intensity tables --- ...3cf7578429009bee4f648e2c1bc3784fdbefc.json | 12 ++++++++++ ...9cf5e8484c147e457044d6b075323b163ebaa.json | 12 ++++++++++ ...8008a9479bf4b3d7231371ebf26382ecde365.json | 12 ---------- backend/src/monitor.rs | 24 ++++++++++++++++++- backend/windmill-worker/src/worker_utils.rs | 2 +- 5 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 backend/.sqlx/query-4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc.json create mode 100644 backend/.sqlx/query-807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa.json delete mode 100644 backend/.sqlx/query-b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365.json diff --git a/backend/.sqlx/query-4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc.json b/backend/.sqlx/query-4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc.json new file mode 100644 index 0000000000..6973159764 --- /dev/null +++ b/backend/.sqlx/query-4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc" +} diff --git a/backend/.sqlx/query-807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa.json b/backend/.sqlx/query-807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa.json new file mode 100644 index 0000000000..1d56449d18 --- /dev/null +++ b/backend/.sqlx/query-807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa" +} diff --git a/backend/.sqlx/query-b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365.json b/backend/.sqlx/query-b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365.json deleted file mode 100644 index 641c94c555..0000000000 --- a/backend/.sqlx/query-b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365" -} diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index ea79738215..1ad0e30a10 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -8,7 +8,7 @@ use std::{ atomic::{AtomicU16, Ordering}, Arc, Mutex, }, - time::Duration, + time::{Duration, Instant}, }; use chrono::{DateTime, NaiveDateTime, Utc}; @@ -1530,6 +1530,20 @@ pub async fn monitor_db( } }; + // run every hour + let vacuum_queue_f = async { + if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(60) { + if let Some(db) = conn.as_sql() { + let instant = Instant::now(); + tracing::info!("vacuuming tables"); + if let Err(e) = vacuuming_tables(&db).await { + tracing::error!("Error vacuuming v2_job: {:?}", e); + } + tracing::info!("vacuum tables done in {}s", instant.elapsed().as_secs()); + } + } + }; + let expired_items_f = async { if server_mode && !initial_load { if let Some(db) = conn.as_sql() { @@ -1607,6 +1621,7 @@ pub async fn monitor_db( expired_items_f, zombie_jobs_f, stale_jobs_f, + vacuum_queue_f, expose_queue_metrics_f, verify_license_key_f, worker_groups_alerts_f, @@ -1619,6 +1634,13 @@ pub async fn monitor_db( ); } +async fn vacuuming_tables(db: &Pool) -> error::Result<()> { + sqlx::query!("VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics") + .execute(db) + .await?; + Ok(()) +} + pub async fn expose_queue_metrics(db: &Pool) { let last_check = sqlx::query_scalar!( "SELECT created_at FROM metrics WHERE id LIKE 'queue_count_%' ORDER BY created_at DESC LIMIT 1" diff --git a/backend/windmill-worker/src/worker_utils.rs b/backend/windmill-worker/src/worker_utils.rs index 4da318d37b..d654e5af65 100644 --- a/backend/windmill-worker/src/worker_utils.rs +++ b/backend/windmill-worker/src/worker_utils.rs @@ -308,7 +308,7 @@ pub(crate) async fn queue_vacuum(conn: &Connection, worker_name: &str, hostname: tokio::task::spawn( (async move { tracing::info!(worker = %worker_name, hostname = %hostname, "vacuuming queue"); - if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status") + if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms") .execute(&db2) .await { From 006f32602c7609b282f15135989c5f164c109c1c Mon Sep 17 00:00:00 2001 From: pyranota <92104930+pyranota@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:44:35 +0200 Subject: [PATCH 14/40] fix: fix relative imports cache invalidation (#6468) * fix: new script on deps job for secondary scripts Signed-off-by: pyranota * make it work (dirty) Signed-off-by: pyranota * reduce db calls * remove `triggered_by_relative_import` Signed-off-by: pyranota * add comment to common_dependency_path Signed-off-by: pyranota * add fallback to old behavior Signed-off-by: pyranota * remove TODOs Signed-off-by: pyranota * pass deployed hash to git sync handler function Signed-off-by: pyranota * fix ci Signed-off-by: pyranota --------- Signed-off-by: pyranota --- ...d2ea9245f05e772f3601c99312c13ed65ae1a.json | 16 ++ ...a6aa59599f852cc17b16323fc627b6ad8671e.json | 16 ++ ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...11bffc0fc93032943369015971373f1f2af68.json | 17 ++ ...6163254_runnable_notify_on_insert.down.sql | 2 + ...826163254_runnable_notify_on_insert.up.sql | 6 + backend/tests/worker.rs | 48 ++++-- backend/windmill-api/src/scripts.rs | 13 +- backend/windmill-common/src/scripts.rs | 6 + backend/windmill-worker/src/lib.rs | 4 +- .../windmill-worker/src/worker_lockfiles.rs | 155 ++++++++++++++++-- 11 files changed, 237 insertions(+), 48 deletions(-) create mode 100644 backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json create mode 100644 backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json create mode 100644 backend/.sqlx/query-ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68.json create mode 100644 backend/migrations/20250826163254_runnable_notify_on_insert.down.sql create mode 100644 backend/migrations/20250826163254_runnable_notify_on_insert.up.sql diff --git a/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json b/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json new file mode 100644 index 0000000000..af706cdac9 --- /dev/null +++ b/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Text" + ] + }, + "nullable": [] + }, + "hash": "0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a" +} diff --git a/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json b/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json new file mode 100644 index 0000000000..eee4c4d493 --- /dev/null +++ b/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Text" + ] + }, + "nullable": [] + }, + "hash": "03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e" +} 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-ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68.json b/backend/.sqlx/query-ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68.json new file mode 100644 index 0000000000..3b5c65095a --- /dev/null +++ b/backend/.sqlx/query-ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, $4, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "ce6f3e803909d55c19169c77d4111bffc0fc93032943369015971373f1f2af68" +} diff --git a/backend/migrations/20250826163254_runnable_notify_on_insert.down.sql b/backend/migrations/20250826163254_runnable_notify_on_insert.down.sql new file mode 100644 index 0000000000..8b16eb1081 --- /dev/null +++ b/backend/migrations/20250826163254_runnable_notify_on_insert.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +DROP TRIGGER script_insert_trigger ON script; diff --git a/backend/migrations/20250826163254_runnable_notify_on_insert.up.sql b/backend/migrations/20250826163254_runnable_notify_on_insert.up.sql new file mode 100644 index 0000000000..06ab07ff55 --- /dev/null +++ b/backend/migrations/20250826163254_runnable_notify_on_insert.up.sql @@ -0,0 +1,6 @@ +-- Add up migration script here +CREATE TRIGGER script_insert_trigger +AFTER INSERT ON script +FOR EACH ROW +WHEN (NEW.lock IS NOT NULL) +EXECUTE FUNCTION notify_runnable_version_change('script'); diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index b58c74fbee..85d7555e1b 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -4679,30 +4679,42 @@ mod job_payload { test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; } - #[sqlx::test(fixtures("base", "hello"))] async fn test_dependencies_payload(db: Pool) { initialize_tracing().await; let server = ApiServer::start(db.clone()).await; let port = server.addr.port(); - let test = || async { - let result = RunJob::from(JobPayload::Dependencies { - path: "f/system/hello".to_string(), - hash: ScriptHash(123412), - language: ScriptLang::Deno, - dedicated_worker: None, - }) - .run_until_complete(&db, port) - .await - .json_result() - .unwrap(); + let result = RunJob::from(JobPayload::Dependencies { + path: "f/system/hello".to_string(), + hash: ScriptHash(123412), + language: ScriptLang::Deno, + dedicated_worker: None, + }) + .run_until_complete(&db, port) + .await + .json_result() + .unwrap(); - assert_eq!( - result.get("status").unwrap(), - &json!("Successful lock file generation") - ); - }; - test_for_versions(VERSION_FLAGS.iter().cloned(), test).await; + assert_eq!( + result.get("status").unwrap(), + &json!("Successful lock file generation") + ); + } + + #[sqlx::test(fixtures("base", "hello"))] + async fn test_dependencies_payload_min_1_427(db: Pool) { + *MIN_VERSION_IS_AT_LEAST_1_427.write().await = true; + test_dependencies_payload(db).await; + } + #[sqlx::test(fixtures("base", "hello"))] + async fn test_dependencies_payload_min_1_432(db: Pool) { + *MIN_VERSION_IS_AT_LEAST_1_432.write().await = true; + test_dependencies_payload(db).await; + } + #[sqlx::test(fixtures("base", "hello"))] + async fn test_dependencies_payload_min_1_440(db: Pool) { + *MIN_VERSION_IS_AT_LEAST_1_440.write().await = true; + test_dependencies_payload(db).await; } // Just test that deploying a flow work as expected. diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index db4cb4e1e6..49765660ab 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -33,11 +33,7 @@ use serde_json::json; use serde_json::value::RawValue; use sql_builder::prelude::*; use sqlx::{FromRow, Postgres, Transaction}; -use std::{ - collections::{hash_map::DefaultHasher, HashMap}, - hash::{Hash, Hasher}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_worker::process_relative_imports; @@ -45,6 +41,7 @@ use windmill_worker::process_relative_imports; use windmill_common::{ assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType}, error::to_anyhow, + scripts::hash_script, utils::WarnAfterExt, worker::CLOUD_HOSTED, }; @@ -379,12 +376,6 @@ async fn get_top_hub_scripts( Ok::<_, Error>((status_code, headers, response)) } -fn hash_script(ns: &NewScript) -> i64 { - let mut dh = DefaultHasher::new(); - ns.hash(&mut dh); - dh.finish() as i64 -} - async fn create_snapshot_script( authed: ApiAuthed, Extension(user_db): Extension, diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 7633e60e25..c2a753e1a9 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -649,3 +649,9 @@ pub struct HubScript { pub schema: Box, pub summary: Option, } + +pub fn hash_script(ns: &NewScript) -> i64 { + let mut dh = std::hash::DefaultHasher::new(); + ns.hash(&mut dh); + dh.finish() as i64 +} diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index 3972de49e7..ace218c207 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -63,7 +63,9 @@ mod worker_lockfiles; mod worker_utils; pub use worker::*; -pub use worker_lockfiles::process_relative_imports; +pub use worker_lockfiles::{ + process_relative_imports, trigger_dependents_to_recompute_dependencies, +}; pub use result_processor::handle_job_error; diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 38e3edec4d..358e93e0a1 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -17,7 +17,7 @@ use windmill_common::error::Result; use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeId}; use windmill_common::get_latest_deployed_hash_for_path; use windmill_common::jobs::JobPayload; -use windmill_common::scripts::ScriptHash; +use windmill_common::scripts::{hash_script, NewScript, ScriptHash}; #[cfg(feature = "python")] use windmill_common::worker::PythonAnnotations; use windmill_common::worker::{to_raw_value, to_raw_value_owned, write_file, Connection}; @@ -38,6 +38,11 @@ use windmill_parser_py_imports::parse_relative_imports; use windmill_parser_ts::parse_expr_for_imports; use windmill_queue::{append_logs, CanceledBy, MiniPulledJob, PushIsolationLevel}; +lazy_static::lazy_static! { + // TODO: To be removed in future versions + static ref WMDEBUG_NO_HASH_CHANGE_ON_DJ: bool = std::env::var("WMDEBUG_NO_HASH_CHANGE_ON_DJ").is_ok(); +} + use crate::common::OccupancyMetrics; use crate::csharp_executor::generate_nuget_lockfile; @@ -251,6 +256,7 @@ pub async fn handle_dependency_job( .is_some_and(|y| y.to_string().as_str() == "true") }) .unwrap_or(false); + let npm_mode = if job .script_lang .as_ref() @@ -338,30 +344,140 @@ pub async fn handle_dependency_job( )); } - let hash = job.runnable_id.unwrap_or(ScriptHash(0)); + let current_hash = job.runnable_id.unwrap_or(ScriptHash(0)); let w_id = &job.workspace_id; - sqlx::query!( - "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3", - &content, - &hash.0, - w_id - ) - .execute(db) - .await?; - - // `lock` has been updated; invalidate the cache. - cache::script::invalidate(hash); - let (deployment_message, parent_path) = get_deployment_msg_and_parent_path_from_args(job.args.clone()); + let script_info = sqlx::query_as::<_, windmill_common::scripts::Script>( + "SELECT * FROM script WHERE hash = $1 AND workspace_id = $2", + ) + .bind(¤t_hash.0) + .bind(w_id) + .fetch_one(db) + .await?; + + // DependencyJob can be triggered only from 2 places: + // 1. create_script function in windmill-api/src/scripts.rs + // 2. trigger_dependents_to_recompute_dependencies (in this file) + // + // First will **always** produce script with null in `lock` + // where Second will **always** do with lock being not null + let deployed_hash = if script_info.lock.is_some() && !*WMDEBUG_NO_HASH_CHANGE_ON_DJ { + let mut tx = db.begin().await?; + // This entire section exists to solve following problem: + // + // 2 workers, one script that depend on another in python + // run the original script on both workers + // you update the dependenecy of a relative import, + // run it again until you ran it on both, normally it should fail on one of those + // + // It happens because every worker has cached their own script versions. + // However usual dependency job does not update hash of the script (and cache is keyed by the hash). + // This logical branch will create new script which will update the hash and automatically invalidate cache. + // + // IMPORTANT: This will **only** be triggered by another DependencyJob. It will never be triggered by script (re)deployement + + let ns = NewScript { + path: script_info.path, + parent_hash: Some(current_hash), + summary: script_info.summary, + description: script_info.description, + content: script_info.content, + schema: script_info.schema, + is_template: Some(script_info.is_template), + // TODO: Make it either None everywhere (particularely when raw reqs are calculated) + // Or handle this case and conditionally make Some (only with raw reqs) + lock: None, + language: script_info.language, + kind: Some(script_info.kind), + tag: script_info.tag, + draft_only: script_info.draft_only, + envs: script_info.envs, + concurrent_limit: script_info.concurrent_limit, + concurrency_time_window_s: script_info.concurrency_time_window_s, + cache_ttl: script_info.cache_ttl, + dedicated_worker: script_info.dedicated_worker, + ws_error_handler_muted: script_info.ws_error_handler_muted, + priority: script_info.priority, + timeout: script_info.timeout, + delete_after_use: script_info.delete_after_use, + restart_unless_cancelled: script_info.restart_unless_cancelled, + deployment_message: deployment_message.clone(), + concurrency_key: script_info.concurrency_key, + visible_to_runner_only: script_info.visible_to_runner_only, + no_main_func: script_info.no_main_func, + codebase: script_info.codebase, + has_preprocessor: script_info.has_preprocessor, + on_behalf_of_email: script_info.on_behalf_of_email, + assets: script_info.assets, + }; + + let new_hash = hash_script(&ns); + + sqlx::query!(" + INSERT INTO script + (workspace_id, hash, path, parent_hashes, summary, description, content, \ + created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ + draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ + dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ + delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, \ + codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) + + SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, \ + content, created_by, schema, is_template, extra_perms, $4, language, kind, tag, \ + draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ + dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ + delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, \ + codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets + + FROM script WHERE hash = $2 AND workspace_id = $3; + ", + new_hash, current_hash.0, w_id, &content).execute(db).await?; + + // Archive current + sqlx::query!( + "UPDATE script SET archived = true WHERE hash = $1 AND workspace_id = $2", + current_hash.0, + w_id + ) + .execute(&mut *tx) + .await?; + + tx.commit().await?; + + ScriptHash(new_hash) + } else { + // We do not create new row for this update + // That means we can keep current hash and just update lock + sqlx::query!( + "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3", + &content, + ¤t_hash.0, + w_id + ) + .execute(db) + .await?; + + // `lock` has been updated; invalidate the cache. + // Since only worker that ran this Dependency Job has the cache + // we do not need to think about invalidating cache for other workers. + cache::script::invalidate(current_hash); + + if *WMDEBUG_NO_HASH_CHANGE_ON_DJ { + tracing::warn!("WMDEBUG_NO_HASH_CHANGE_ON_DJ usually should not be used. Behavior might be unstable. Please contact Windmill Team for support.") + } + + current_hash + }; + if let Err(e) = handle_deployment_metadata( &job.permissioned_as_email, &job.created_by, &db, &w_id, DeployedObject::Script { - hash, + hash: deployed_hash, path: script_path.to_string(), parent_path: parent_path.clone(), }, @@ -502,7 +618,7 @@ pub async fn process_relative_imports( Ok(()) } -async fn trigger_dependents_to_recompute_dependencies( +pub async fn trigger_dependents_to_recompute_dependencies( w_id: &str, script_path: &str, deployment_message: Option, @@ -535,6 +651,10 @@ async fn trigger_dependents_to_recompute_dependencies( args.insert("deployment_message".to_string(), to_raw_value(&dm)); } if let Some(ref p_path) = parent_path { + // NOTE: + // it's not used but maybe one day it will be useful. allows more back-compatibility for the workers when we need it + // also very useful for debugging/observability + // it adds that information to the job args so you can see from the runs page args.insert("common_dependency_path".to_string(), to_raw_value(&p_path)); } @@ -542,6 +662,7 @@ async fn trigger_dependents_to_recompute_dependencies( "already_visited".to_string(), to_raw_value(&already_visited), ); + let kind = s.importer_kind.clone().unwrap_or_default(); let job_payload = if kind == "script" { let r = get_latest_deployed_hash_for_path(db, w_id, s.importer_path.as_str()).await; @@ -577,7 +698,7 @@ async fn trigger_dependents_to_recompute_dependencies( Ok(Some(version)) => JobPayload::FlowDependencies { path: s.importer_path.clone(), dedicated_worker: None, - version: version, + version, }, Ok(None) => { tracing::error!( From 73a3f4cc73271759650e9246f4eb2e0efb7c7e37 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 27 Aug 2025 18:38:04 +0000 Subject: [PATCH 15/40] fix: fix okta and oauth0 sso settings --- frontend/src/lib/components/Auth0Setting.svelte | 9 +++++++-- frontend/src/lib/components/OktaSetting.svelte | 10 ++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/components/Auth0Setting.svelte b/frontend/src/lib/components/Auth0Setting.svelte index 5d8dc9ba5a..1116ea0d27 100644 --- a/frontend/src/lib/components/Auth0Setting.svelte +++ b/frontend/src/lib/components/Auth0Setting.svelte @@ -1,4 +1,5 @@ diff --git a/frontend/src/lib/components/OktaSetting.svelte b/frontend/src/lib/components/OktaSetting.svelte index 5a1836ec7d..7ad4cb01b4 100644 --- a/frontend/src/lib/components/OktaSetting.svelte +++ b/frontend/src/lib/components/OktaSetting.svelte @@ -1,4 +1,5 @@ From 0cc11b3f31aeee60a9d4a231cea5d4285d7ab37e Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:00:59 +0200 Subject: [PATCH 16/40] feat(aichat): allow reverting specific line for inline script suggestions (#6480) * draft * cleaning * settimeout temp fix * adjust colors * cleaning * good stuff * no timeout * add on finish callback * cleaning * fix * adpat accept all / reject all * cleaning * cleaning * adapt click on module reject/accept * clearer function names * nit * simplify --- frontend/src/lib/components/Editor.svelte | 30 ++++- .../copilot/chat/flow/FlowAIChat.svelte | 80 ++++++------- .../components/copilot/chat/monaco-adapter.ts | 99 ++++++++++++--- frontend/src/lib/components/copilot/shared.ts | 113 ++++++++++-------- 4 files changed, 205 insertions(+), 117 deletions(-) diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index ae35b9fcfb..4bbdeb0d71 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -676,7 +676,21 @@ let selectedCode = $state('') export function reviewAndApplyCode(code: string, applyAll: boolean = false) { - aiChatEditorHandler?.reviewAndApply(code, applyAll) + aiChatEditorHandler?.reviewChanges(code, { applyAll, mode: 'apply' }) + } + + export function reviewAppliedCode( + originalCode: string, + opts?: { onFinishedReview?: () => void } + ) { + aiChatEditorHandler?.reviewChanges(originalCode, { + mode: 'revert', + onFinishedReview: opts?.onFinishedReview + }) + } + + export function getAiChatEditorHandler() { + return aiChatEditorHandler } function addChatHandler(editor: meditor.IStandaloneCodeEditor) { @@ -1684,10 +1698,20 @@ {#if $reviewingChanges} { - aiChatEditorHandler?.acceptAll() + const mode = aiChatEditorHandler?.getReviewMode?.() + if (mode === 'revert') { + aiChatEditorHandler?.keepAll() + } else { + aiChatEditorHandler?.acceptAll() + } }} onRejectAll={() => { - aiChatEditorHandler?.rejectAll() + const mode = aiChatEditorHandler?.getReviewMode?.() + if (mode === 'revert') { + aiChatEditorHandler?.revertAll() + } else { + aiChatEditorHandler?.rejectAll() + } }} /> {/if} diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index e17c521f3c..7aade584f9 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -6,7 +6,7 @@ import { dfs } from '$lib/components/flows/previousResults' import { dfs as dfsApply } from '$lib/components/flows/dfs' import { getSubModules } from '$lib/components/flows/flowExplorer' - import type { FlowModule, OpenFlow, RawScript } from '$lib/gen' + import type { FlowModule, OpenFlow } from '$lib/gen' import { getIndexInNestedModules, getNestedModules } from './utils' import type { AIModuleAction, FlowAIChatHelpers } from './core' import { @@ -93,21 +93,10 @@ hasDiff: () => { return Object.keys(affectedModules).length > 0 }, - acceptAllModuleActions: () => { - for (const [id, affectedModule] of Object.entries(affectedModules)) { - if (affectedModule.action === 'removed') { - deleteStep(id) - } - // Hide diff editor if the module is a rawscript - if ( - affectedModule.action === 'modified' && - $currentEditor?.type === 'script' && - $currentEditor.stepId === id - ) { - $currentEditor.hideDiffMode() - } + acceptAllModuleActions() { + for (const id of Object.keys(affectedModules)) { + this.acceptModuleAction(id) } - affectedModules = {} }, rejectAllModuleActions() { for (const id of Object.keys(affectedModules)) { @@ -189,8 +178,11 @@ $currentEditor?.type === 'script' && $currentEditor.stepId === id ) { - $currentEditor.editor.setCode((oldModule.value as RawScript).content) - $currentEditor.hideDiffMode() + const aiChatEditorHandler = $currentEditor.editor.getAiChatEditorHandler() + if (aiChatEditorHandler) { + aiChatEditorHandler.revertAll({ disableReviewCallback: true }) + $currentEditor.hideDiffMode() + } } Object.keys(newModule).forEach((k) => delete newModule[k]) @@ -206,6 +198,18 @@ if (affectedModules[id]?.action === 'removed') { deleteStep(id) } + + if ( + affectedModules[id]?.action === 'modified' && + $currentEditor && + $currentEditor.type === 'script' && + $currentEditor.stepId === id + ) { + const aiChatEditorHandler = $currentEditor.editor.getAiChatEditorHandler() + if (aiChatEditorHandler) { + aiChatEditorHandler.keepAll({ disableReviewCallback: true }) + } + } delete affectedModules[id] }, // ai chat tools @@ -614,43 +618,27 @@ return cleanup }) - // Automatically show diff mode when selecting a rawscript module with pending changes + // Automatically show revert review when selecting a rawscript module with pending changes $effect(() => { if ( $currentEditor?.type === 'script' && $selectedId && affectedModules[$selectedId] && - lastSnapshot + $currentEditor.editor.getAiChatEditorHandler() ) { const moduleLastSnapshot = getModule($selectedId, lastSnapshot) - const currentModule = getModule($selectedId) - - if ( - moduleLastSnapshot && - currentModule && - currentModule.value.type === 'rawscript' && - moduleLastSnapshot.value.type === 'rawscript' - ) { - // Show diff mode automatically - $currentEditor.setDiffOriginal?.(moduleLastSnapshot.value.content ?? '') - $currentEditor.showDiffMode() - $currentEditor.setDiffButtons?.([ - { - text: 'Accept Changes', - color: 'green', - onClick: () => { - flowHelpers.acceptModuleAction($selectedId) - $currentEditor?.hideDiffMode() + const content = + moduleLastSnapshot?.value.type === 'rawscript' ? moduleLastSnapshot.value.content : '' + if (content.length > 0) { + untrack(() => + $currentEditor.editor.reviewAppliedCode(content, { + onFinishedReview: () => { + const id = $selectedId + flowHelpers.acceptModuleAction(id) + $currentEditor.hideDiffMode() } - }, - { - text: 'Reject Changes', - onClick: () => { - flowHelpers.revertModuleAction($selectedId) - $currentEditor?.hideDiffMode() - } - } - ]) + }) + ) } } }) diff --git a/frontend/src/lib/components/copilot/chat/monaco-adapter.ts b/frontend/src/lib/components/copilot/chat/monaco-adapter.ts index 65131ff45e..d940f3e3fa 100644 --- a/frontend/src/lib/components/copilot/chat/monaco-adapter.ts +++ b/frontend/src/lib/components/copilot/chat/monaco-adapter.ts @@ -24,6 +24,12 @@ export class AIChatEditorHandler { reviewingChanges: Writable = writable(false) groupChanges: { changes: VisualChangeWithDiffIndex[]; groupIndex: number }[] = [] + // Track review decisions + private reviewState: { + mode: 'apply' | 'revert' + onFinishedReview?: () => void + } | null = null + constructor(editor: meditor.IStandaloneCodeEditor) { this.editor = editor } @@ -65,7 +71,16 @@ export class AIChatEditorHandler { } } - async finish() { + async finish(opts?: { disableReviewCallback?: boolean }) { + // expose mode getter relies on reviewState + // Call completion callback if we're tracking review state + if (this.reviewState?.onFinishedReview && !opts?.disableReviewCallback) { + this.reviewState.onFinishedReview() + } + + // Reset review state + this.reviewState = null + this.clear() this.allowWriting() this.reviewingChanges.set(false) @@ -74,16 +89,30 @@ export class AIChatEditorHandler { }) } - async acceptAll() { + getReviewMode(): 'apply' | 'revert' | null { + return this.reviewState?.mode ?? null + } + + async acceptAll(opts?: { disableReviewCallback?: boolean }) { this.groupChanges.reverse() for (const group of this.groupChanges) { this.applyGroup(group) } - this.finish() + this.finish(opts) } - async rejectAll() { - this.finish() + async rejectAll(opts?: { disableReviewCallback?: boolean }) { + this.finish(opts) + } + + // Keep all changes, used in revert mode + async keepAll(opts?: { disableReviewCallback?: boolean }) { + this.finish(opts) + } + + // Revert all changes, used in revert mode + async revertAll(opts?: { disableReviewCallback?: boolean }) { + this.acceptAll(opts) } applyGroup(group: { changes: VisualChangeWithDiffIndex[]; groupIndex: number }) { @@ -167,23 +196,41 @@ export class AIChatEditorHandler { return changedLines } - async reviewAndApply(newCode: string, applyAll: boolean = false) { - if (aiChatManager.pendingNewCode === newCode) { + async reviewChanges( + targetCode: string, + opts?: { + applyAll?: boolean + mode?: 'apply' | 'revert' + onFinishedReview?: () => void + } + ) { + if (aiChatManager.pendingNewCode === targetCode && opts?.mode === 'apply') { this.acceptAll() return } else if (aiChatManager.pendingNewCode) { this.clear() } - aiChatManager.pendingNewCode = newCode - const changedLines = await this.calculateVisualChanges(newCode) + + aiChatManager.pendingNewCode = targetCode + const changedLines = await this.calculateVisualChanges(targetCode) if (changedLines.length === 0) return + // Initialize review state for tracking + this.reviewState = { + mode: opts?.mode ?? 'apply', + onFinishedReview: opts?.onFinishedReview + } + let indicesOfRejectedLineChanges: number[] = [] for (const [groupIndex, group] of this.groupChanges.entries()) { let collection: meditor.IEditorDecorationsCollection | undefined = undefined let ids: string[] = [] - const acceptFn = () => { + + const isRevert = opts?.mode === 'revert' + + // Apply this group and continue with remaining changes + const onApply = () => { this.applyGroup(group) this.clear() let newCodeWithRejects = '' @@ -196,9 +243,12 @@ export class AIChatEditorHandler { newCodeWithRejects += change.value } } - this.reviewAndApply(newCodeWithRejects) + this.reviewChanges(newCodeWithRejects, opts) } - const rejectFn = () => { + + // Discard this group and continue with remaining changes + const onDiscard = () => { + // This group was not applied (not reverted in revert mode) indicesOfRejectedLineChanges.push(...group.changes.map((c) => c.diffIndex)) collection?.clear() this.editor.changeViewZones((acc) => { @@ -211,28 +261,41 @@ export class AIChatEditorHandler { this.finish() } } + + // In revert mode: Accept = keep current code, Reject = revert to targetCode + // In apply mode: Accept = apply changes, Reject = discard changes + const acceptFn = isRevert ? onDiscard : onApply + const rejectFn = isRevert ? onApply : onDiscard + const changes = group.changes.map((c, i) => { if (i === group.changes.length - 1) { return { ...c, - options: { ...(c.options ?? {}), review: { acceptFn, rejectFn } } + options: { + ...(c.options ?? {}), + review: { + acceptFn, + rejectFn + } + } } } else { return c } }) - if (!applyAll) { + if (!opts?.applyAll) { ;({ collection, ids } = await displayVisualChanges( 'editor-windmill-chat-style', this.editor, - changes + changes, + isRevert )) - this.decorationsCollections.push(collection) - this.viewZoneIds.push(...ids) + this.decorationsCollections.push(collection) + this.viewZoneIds.push(...ids) } } - if (applyAll) { + if (opts?.applyAll) { this.acceptAll() } } diff --git a/frontend/src/lib/components/copilot/shared.ts b/frontend/src/lib/components/copilot/shared.ts index 5830685866..ed37052298 100644 --- a/frontend/src/lib/components/copilot/shared.ts +++ b/frontend/src/lib/components/copilot/shared.ts @@ -3,49 +3,49 @@ import { type editor as meditor } from 'monaco-editor' export type VisualChange = | { - type: 'added_inline' - position: { - line: number - column: number - } - value: string - options?: { - greenHighlight?: boolean - } - } - | { - type: 'added_block' - position: { - afterLineNumber: number - } - value: string - options?: { - greenHighlight?: boolean - review?: { - acceptFn: () => void - rejectFn: () => void + type: 'added_inline' + position: { + line: number + column: number } - extraChanges?: VisualChange[] - } - } - | { - type: 'deleted' - range: { - startLine: number - startColumn: number - endLine: number - endColumn: number - } - options?: { - isWholeLine?: boolean - review?: { - acceptFn: () => void - rejectFn: () => void + value: string + options?: { + greenHighlight?: boolean } - } - } + } + | { + type: 'added_block' + position: { + afterLineNumber: number + } + value: string + options?: { + greenHighlight?: boolean + review?: { + acceptFn: () => void + rejectFn: () => void + } + extraChanges?: VisualChange[] + } + } + | { + type: 'deleted' + range: { + startLine: number + startColumn: number + endLine: number + endColumn: number + } + options?: { + isWholeLine?: boolean + review?: { + acceptFn: () => void + rejectFn: () => void + } + } + } -function applyMonacoStyles(targetEl: HTMLElement, greenHighlight?: boolean) { +function applyMonacoStyles(targetEl: HTMLElement, greenHighlight?: boolean, revertMode?: boolean) { const computedStyles = window.getComputedStyle( document.querySelector('.monaco-editor .view-lines')! ) @@ -57,7 +57,9 @@ function applyMonacoStyles(targetEl: HTMLElement, greenHighlight?: boolean) { whiteSpace: 'pre' }) if (greenHighlight) { - targetEl.style.backgroundColor = 'var(--vscode-diffEditor-insertedTextBackground)' + targetEl.style.backgroundColor = revertMode + ? 'var(--vscode-diffEditor-removedTextBackground)' + : 'var(--vscode-diffEditor-insertedTextBackground)' } } @@ -71,7 +73,10 @@ export function setGlobalCSS(id: string, cssCode: string) { styleTag.textContent = cssCode } -function addInlineGhostText(change: Extract) { +function addInlineGhostText( + change: Extract, + revertMode?: boolean +) { const cssId = createLongHash() const decoration = { range: { @@ -81,8 +86,13 @@ function addInlineGhostText(change: Extract void } extraChanges?: VisualChange[] - } + }, + revertMode?: boolean ) { const el = document.createElement('div') el.textContent = text @@ -172,7 +183,7 @@ async function addMultilineGhostText( const reviewButtons = getReviewButtons(editor, options.review.acceptFn, options.review.rejectFn) el.append(reviewButtons) } - applyMonacoStyles(el, options?.greenHighlight) + applyMonacoStyles(el, options?.greenHighlight, revertMode) const addZonePromise = new Promise((resolve, reject) => { editor?.changeViewZones((acc) => { const id = acc.addZone({ @@ -193,14 +204,15 @@ export let VISUAL_CHANGES_CSS = `.editor-ghost-text-green { background-color: va export async function displayVisualChanges( cssId: string, editor: meditor.IStandaloneCodeEditor, - visualChanges: VisualChange[] + visualChanges: VisualChange[], + revertMode?: boolean ) { let decorations: meditor.IModelDeltaDecoration[] = [] let css = '' let ids: string[] = [] for (const change of visualChanges) { if (change.type === 'added_inline') { - const { css: newCss, decoration } = addInlineGhostText(change) + const { css: newCss, decoration } = addInlineGhostText(change, revertMode) decorations.push(decoration) css += newCss } else if (change.type === 'deleted') { @@ -212,7 +224,7 @@ export async function displayVisualChanges( endColumn: change.range.endColumn }, options: { - className: 'editor-ghost-text-removed', + className: revertMode ? 'editor-ghost-text-green' : 'editor-ghost-text-removed', isWholeLine: change.options?.isWholeLine } } @@ -247,7 +259,8 @@ export async function displayVisualChanges( change.value, change.position.afterLineNumber, change.value.split('\n').length, // we know it won't end by \n - change.options + change.options, + revertMode ) ids.push(id) } From 2ce11cea3be04ff4d7ff7e7f1ff1e3d2d9b8c67e Mon Sep 17 00:00:00 2001 From: hugocasa Date: Thu, 28 Aug 2025 18:56:57 +0200 Subject: [PATCH 17/40] prevent past due team plans to overuse (#6487) * prevent past due team plans to overuse * nit * update ee ref --- ...d2ea9245f05e772f3601c99312c13ed65ae1a.json | 16 ---- ...a6aa59599f852cc17b16323fc627b6ad8671e.json | 16 ---- ...6242a39dd40186f1dbb48ad3018bd9f6913ec.json | 34 +++++++ ...a2e60c9c24decb2a6b41125247bbf741e9c25.json | 46 ---------- ...1789afb8250adde6f38f458b115e787ed876f.json | 58 ++++++++++++ ...50aa180b29bbe931d948f6e61976f71b7cdb9.json | 22 ----- backend/ee-repo-ref.txt | 2 +- ...250827102435_add_team_plan_status.down.sql | 7 ++ ...20250827102435_add_team_plan_status.up.sql | 18 ++++ backend/src/main.rs | 2 +- backend/windmill-api/openapi.yaml | 10 +++ backend/windmill-api/src/workspaces.rs | 4 +- backend/windmill-common/src/workspaces.rs | 43 +++++++-- backend/windmill-queue/src/jobs.rs | 90 +++++++++++-------- backend/windmill-worker/src/common.rs | 5 +- .../components/settings/PremiumInfo.svelte | 11 ++- .../src/routes/(root)/(logged)/+layout.svelte | 35 +++++++- 17 files changed, 263 insertions(+), 156 deletions(-) delete mode 100644 backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json delete mode 100644 backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json create mode 100644 backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json delete mode 100644 backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json create mode 100644 backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json delete mode 100644 backend/.sqlx/query-d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9.json create mode 100644 backend/migrations/20250827102435_add_team_plan_status.down.sql create mode 100644 backend/migrations/20250827102435_add_team_plan_status.up.sql diff --git a/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json b/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json deleted file mode 100644 index af706cdac9..0000000000 --- a/backend/.sqlx/query-0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a" -} diff --git a/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json b/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json deleted file mode 100644 index eee4c4d493..0000000000 --- a/backend/.sqlx/query-03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Text" - ] - }, - "nullable": [] - }, - "hash": "03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e" -} diff --git a/backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json b/backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json new file mode 100644 index 0000000000..51a1ec68cb --- /dev/null +++ b/backend/.sqlx/query-124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n w.premium,\n COALESCE(cw.is_past_due, false) as \"is_past_due!\",\n cw.max_tolerated_executions\n FROM\n workspace w\n LEFT JOIN cloud_workspace_settings cw ON cw.workspace_id = w.id\n WHERE\n w.id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "premium", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "is_past_due!", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "max_tolerated_executions", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + null, + true + ] + }, + "hash": "124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec" +} diff --git a/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json b/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json deleted file mode 100644 index ab5b04afc6..0000000000 --- a/backend/.sqlx/query-4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT owner, premium, usage.usage as \"usage?\", workspace_settings.customer_id, workspace_settings.plan FROM workspace LEFT JOIN workspace_settings ON workspace_settings.workspace_id = $1 LEFT JOIN usage ON usage.id = $1 AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND usage.is_workspace IS true WHERE workspace.id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "owner", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "premium", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "usage?", - "type_info": "Int4" - }, - { - "ordinal": 3, - "name": "customer_id", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "plan", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - true, - true - ] - }, - "hash": "4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25" -} diff --git a/backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json b/backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json new file mode 100644 index 0000000000..9e3e7cb863 --- /dev/null +++ b/backend/.sqlx/query-ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n owner, \n premium, \n usage.usage as \"usage?\", \n workspace_settings.customer_id, \n workspace_settings.plan, \n COALESCE(cw.is_past_due, false) as \"is_past_due!\", \n cw.max_tolerated_executions\n FROM workspace\n LEFT JOIN workspace_settings \n ON workspace_settings.workspace_id = $1\n LEFT JOIN usage \n ON usage.id = $1\n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND usage.is_workspace IS true\n LEFT JOIN cloud_workspace_settings cw\n ON cw.workspace_id = $1\n WHERE workspace.id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "owner", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "premium", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "usage?", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "customer_id", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "plan", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "is_past_due!", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "max_tolerated_executions", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + true, + null, + true + ] + }, + "hash": "ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f" +} diff --git a/backend/.sqlx/query-d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9.json b/backend/.sqlx/query-d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9.json deleted file mode 100644 index 22b38a5217..0000000000 --- a/backend/.sqlx/query-d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT premium FROM workspace WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "premium", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9" -} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 111a3f2d18..dfa2a01850 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -6396854336ae27fb14ccb792d80c31ff614b2afa \ No newline at end of file +d16e52d570f10dfdabb04a9061fe7ebdbf5a2aa1 \ No newline at end of file diff --git a/backend/migrations/20250827102435_add_team_plan_status.down.sql b/backend/migrations/20250827102435_add_team_plan_status.down.sql new file mode 100644 index 0000000000..980b70d425 --- /dev/null +++ b/backend/migrations/20250827102435_add_team_plan_status.down.sql @@ -0,0 +1,7 @@ +-- Add down migration script here +DROP FUNCTION notify_team_plan_status_change; +DROP TRIGGER notify_team_plan_status_change ON cloud_workspace_settings; + +ALTER TABLE cloud_workspace_settings + DROP COLUMN is_past_due, + DROP COLUMN max_tolerated_executions; \ No newline at end of file diff --git a/backend/migrations/20250827102435_add_team_plan_status.up.sql b/backend/migrations/20250827102435_add_team_plan_status.up.sql new file mode 100644 index 0000000000..1b2007bfe7 --- /dev/null +++ b/backend/migrations/20250827102435_add_team_plan_status.up.sql @@ -0,0 +1,18 @@ +-- Add up migration script here +ALTER TABLE cloud_workspace_settings + ADD COLUMN is_past_due BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN max_tolerated_executions INTEGER; + +CREATE OR REPLACE FUNCTION notify_team_plan_status_change() +RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('notify_workspace_premium_change', NEW.workspace_id); -- reuse the same channel as the one used for workspace premium change => clear cache + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER team_plan_status_change_trigger +AFTER UPDATE OF is_past_due, max_tolerated_executions ON cloud_workspace_settings +FOR EACH ROW +EXECUTE FUNCTION notify_team_plan_status_change(); \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 9b80e58eef..be0b86ec46 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -857,7 +857,7 @@ Windmill Community Edition {GIT_VERSION} "notify_workspace_premium_change" => { let workspace_id = n.payload(); tracing::info!("Workspace premium change detected, invalidating workspace premium cache: {}", workspace_id); - windmill_common::workspaces::IS_PREMIUM_CACHE.remove(workspace_id); + windmill_common::workspaces::TEAM_PLAN_CACHE.remove(workspace_id); }, "notify_runnable_version_change" => { let payload = n.payload(); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index c2471cebfb..b0afbfb217 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -2034,6 +2034,11 @@ paths: - workspace parameters: - $ref: "#/components/parameters/WorkspaceId" + - name: skip_subscription_fetch + in: query + description: skip fetching subscription status from stripe + schema: + type: boolean responses: "200": @@ -2051,9 +2056,14 @@ paths: type: string status: type: string + is_past_due: + type: boolean + max_tolerated_executions: + type: number required: - premium - owner + - is_past_due /w/{workspace}/workspaces/threshold_alert: get: diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 78052b8ed8..9e3c7dc25e 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -415,7 +415,9 @@ async fn is_premium( ) -> JsonResult { require_admin(authed.is_admin, &authed.username)?; #[cfg(feature = "cloud")] - let premium = windmill_common::workspaces::is_premium_workspace(&_db, &_w_id).await; + let premium = windmill_common::workspaces::get_team_plan_status(&_db, &_w_id) + .await + .premium; #[cfg(not(feature = "cloud"))] let premium = false; Ok(Json(premium)) diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index 5d7d3d9967..c120e3c39a 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -86,22 +86,47 @@ impl Default for GitSyncSettings { } } +#[derive(Clone)] +pub struct TeamPlanStatus { + pub premium: bool, + pub is_past_due: bool, + pub max_tolerated_executions: Option, +} + lazy_static::lazy_static! { - pub static ref IS_PREMIUM_CACHE: Cache = Cache::new(5000); + pub static ref TEAM_PLAN_CACHE: Cache = Cache::new(5000); } #[cfg(feature = "cloud")] -pub async fn is_premium_workspace(_db: &crate::DB, _w_id: &str) -> bool { - let cached = IS_PREMIUM_CACHE.get(_w_id); +pub async fn get_team_plan_status(_db: &crate::DB, _w_id: &str) -> TeamPlanStatus { + let cached = TEAM_PLAN_CACHE.get(_w_id); if let Some(cached) = cached { return cached; } - let premium = sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", _w_id) - .fetch_one(_db) - .await - .unwrap_or(false); - IS_PREMIUM_CACHE.insert(_w_id.to_string(), premium); - premium + let team_plan_info = sqlx::query_as!( + TeamPlanStatus, + r#" + SELECT + w.premium, + COALESCE(cw.is_past_due, false) as "is_past_due!", + cw.max_tolerated_executions + FROM + workspace w + LEFT JOIN cloud_workspace_settings cw ON cw.workspace_id = w.id + WHERE + w.id = $1 + "#, + _w_id + ) + .fetch_one(_db) + .await + .unwrap_or_else(|_| TeamPlanStatus { + premium: false, + is_past_due: false, + max_tolerated_executions: None, + }); + TEAM_PLAN_CACHE.insert(_w_id.to_string(), team_plan_info.clone()); + team_plan_info } #[derive(Deserialize, Serialize, Debug)] diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 09b2a39695..3667239119 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -1419,8 +1419,9 @@ fn apply_completed_job_cloud_usage( let email2 = email.clone(); tokio::task::spawn(async move { let additional_usage = _duration / 1000; - let premium_workspace = - windmill_common::workspaces::is_premium_workspace(&db, &w_id).await; + let premium_workspace = windmill_common::workspaces::get_team_plan_status(&db, &w_id) + .await + .premium; tokio::time::timeout(std::time::Duration::from_secs(10), async move { let _ = sqlx::query!( "INSERT INTO usage (id, is_workspace, month_, usage) @@ -3636,8 +3637,8 @@ pub async fn push<'c, 'd>( ) -> Result<(Uuid, Transaction<'c, Postgres>), Error> { #[cfg(feature = "cloud")] if *CLOUD_HOSTED { - let premium_workspace = - windmill_common::workspaces::is_premium_workspace(_db, workspace_id).await; + let team_plan_status = + windmill_common::workspaces::get_team_plan_status(_db, workspace_id).await; // we track only non flow steps let (workspace_usage, user_usage) = if !matches!( job_payload, @@ -3655,7 +3656,7 @@ pub async fn push<'c, 'd>( .await .map_err(|e| Error::internal_err(format!("updating usage: {e:#}")))?; - let user_usage = if !premium_workspace { + let user_usage = if !team_plan_status.premium { Some(sqlx::query_scalar!( "INSERT INTO usage (id, is_workspace, month_, usage) VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1) @@ -3678,7 +3679,7 @@ pub async fn push<'c, 'd>( Ok((None, None)) }?; - if !premium_workspace { + if !team_plan_status.premium || team_plan_status.is_past_due { let is_super_admin = sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email) .fetch_optional(_db) @@ -3686,7 +3687,8 @@ pub async fn push<'c, 'd>( .unwrap_or(false); if !is_super_admin { - if email != ERROR_HANDLER_USER_EMAIL + if !team_plan_status.premium + && email != ERROR_HANDLER_USER_EMAIL && email != SCHEDULE_ERROR_HANDLER_USER_EMAIL && email != SCHEDULE_RECOVERY_HANDLER_USER_EMAIL && email != "worker@windmill.dev" @@ -3765,43 +3767,53 @@ pub async fn push<'c, 'd>( .flatten() .unwrap_or(1) }; + if team_plan_status.premium { + // team plan is premium but past due, we check if the workspace has exceeded the max tolerated executions + if team_plan_status.max_tolerated_executions.is_none() + || workspace_usage > team_plan_status.max_tolerated_executions.unwrap() + { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} team plan is past due and isn't allowed to run any more jobs. Please fix your payment method in the workspace settings." + ))); + } + } else { + if workspace_usage > MAX_FREE_EXECS + && !matches!(job_payload, JobPayload::Dependencies { .. }) + && !matches!(job_payload, JobPayload::FlowDependencies { .. }) + && !matches!(job_payload, JobPayload::AppDependencies { .. }) + { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." + ))); + } - if workspace_usage > MAX_FREE_EXECS - && !matches!(job_payload, JobPayload::Dependencies { .. }) - && !matches!(job_payload, JobPayload::FlowDependencies { .. }) - && !matches!(job_payload, JobPayload::AppDependencies { .. }) - { - return Err(error::Error::QuotaExceeded(format!( - "Workspace {workspace_id} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." - ))); - } + let in_queue_workspace = sqlx::query_scalar!( + "SELECT COUNT(id) FROM v2_job_queue WHERE workspace_id = $1", + workspace_id + ) + .fetch_one(_db) + .await? + .unwrap_or(0); - let in_queue_workspace = sqlx::query_scalar!( - "SELECT COUNT(id) FROM v2_job_queue WHERE workspace_id = $1", - workspace_id - ) - .fetch_one(_db) - .await? - .unwrap_or(0); + if in_queue_workspace > MAX_FREE_EXECS as i64 { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." + ))); + } - if in_queue_workspace > MAX_FREE_EXECS as i64 { - return Err(error::Error::QuotaExceeded(format!( - "Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces." - ))); - } - - let concurrent_runs_workspace = sqlx::query_scalar!( + let concurrent_runs_workspace = sqlx::query_scalar!( "SELECT COUNT(id) FROM v2_job_queue WHERE running = true AND workspace_id = $1", - workspace_id - ) - .fetch_one(_db) - .await? - .unwrap_or(0); + workspace_id + ) + .fetch_one(_db) + .await? + .unwrap_or(0); - if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS as i64 { - return Err(error::Error::QuotaExceeded(format!( - "Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." - ))); + if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS as i64 { + return Err(error::Error::QuotaExceeded(format!( + "Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces." + ))); + } } } } diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index 2b7523ecad..11718f3cf5 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -680,11 +680,12 @@ pub async fn resolve_job_timeout( let mut warn_msg: Option = None; #[cfg(feature = "cloud")] let cloud_premium_workspace = *CLOUD_HOSTED - && windmill_common::workspaces::is_premium_workspace( + && windmill_common::workspaces::get_team_plan_status( _conn.as_sql().expect("cloud cannot use http connection"), _w_id, ) - .await; + .await + .premium; #[cfg(not(feature = "cloud"))] let cloud_premium_workspace = false; diff --git a/frontend/src/lib/components/settings/PremiumInfo.svelte b/frontend/src/lib/components/settings/PremiumInfo.svelte index 1ed4b2fb12..bd7a91a026 100644 --- a/frontend/src/lib/components/settings/PremiumInfo.svelte +++ b/frontend/src/lib/components/settings/PremiumInfo.svelte @@ -30,6 +30,8 @@ seatsFromExtraComps: number usedSeats: number owner: string + is_past_due: boolean + max_tolerated_executions?: number } | undefined = undefined const plans = { @@ -157,8 +159,13 @@
{#if premiumInfo?.status === 'past_due'}

- Your last invoice is unpaid. Please update your payment method in the customer portal to - prevent account downgrade and the interruption of your job executions. + {#if premiumInfo.max_tolerated_executions === undefined || premiumInfo.usage > premiumInfo.max_tolerated_executions} + Your last invoice is unpaid, you cannot run any more jobs. Please update your payment + method in the customer portal to continue running jobs. + {:else} + Your last invoice is unpaid. Please update your payment method in the customer portal to + prevent the interruption of your job executions. + {/if}

{/if}
diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index 0969a4df3f..d03689e3d1 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -11,7 +11,7 @@ UserService, WorkspaceService } from '$lib/gen' - import { capitalize, classNames, getModifierKey } from '$lib/utils' + import { capitalize, classNames, getModifierKey, sendUserToast } from '$lib/utils' import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte' import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte' import CriticalAlertModal from '$lib/components/sidebar/CriticalAlertModal.svelte' @@ -305,6 +305,30 @@ muteSettings = { global: g_muted, workspace: ws_muted } } + + async function checkTeamPlanStatus(workspace: string) { + const premiumInfo = await WorkspaceService.getPremiumInfo({ + workspace, + skipSubscriptionFetch: true // won't load subscription status from stripe but only the past due status from db + }) + if (premiumInfo.is_past_due) { + if ( + premiumInfo.max_tolerated_executions === undefined || + (premiumInfo.usage ?? 0) > premiumInfo.max_tolerated_executions + ) { + sendUserToast( + 'Your last invoice is unpaid, you cannot run any more jobs. Please update your payment method in the workspace settings to continue running jobs.', + true + ) + } else { + sendUserToast( + 'Your last invoice is unpaid. Please update your payment method in the workspace settings to prevent the interruption of your job executions.', + true + ) + } + } + } + $effect(() => { $page.url && userSettings != undefined && untrack(() => onQueryChangeUserSettings()) }) @@ -350,6 +374,15 @@ mountModal = false } }) + + $effect(() => { + if (isCloudHosted()) { + const workspace = $workspaceStore + if (workspace) { + checkTeamPlanStatus(workspace) + } + } + }) From 4973c860f2c28d9bdc2af94530d90eb177234e5d Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 28 Aug 2025 17:44:57 +0000 Subject: [PATCH 18/40] fix: fix workflow as code behavior with multithread --- python-client/wmill/wmill/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index f24f2a35ee..36e7f7dc9a 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -1478,16 +1478,16 @@ def task(*args, **kwargs): params = {} if tag is not None: params["tag"] = tag - r = _client.post( + w_as_code_response = _client.post( f"/w/{w_id}/jobs/run/workflow_as_code/{job_id}/{f_name}", json={"args": json}, params=params, ) - job_id = r.text + job_id = w_as_code_response.text print(f"Executing task {func.__name__} on job {job_id}") - r = _client.wait_job(job_id) + job_result = _client.wait_job(job_id) print(f"Task {func.__name__} ({job_id}) completed") - return r + return job_result return inner From 6f4bdc0148fee335db5ea1d8912e53f22eeffee3 Mon Sep 17 00:00:00 2001 From: BaptisteMoureaux <43876576+BaptisteMoureaux@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:06:36 +0200 Subject: [PATCH 19/40] bump lsp go version & add private packages go support (#6484) --- lsp/Dockerfile | 10 +++++----- lsp/pyls_launcher.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lsp/Dockerfile b/lsp/Dockerfile index df34a4549a..de599e45ec 100644 --- a/lsp/Dockerfile +++ b/lsp/Dockerfile @@ -9,7 +9,7 @@ ENV PIPENV_VENV_IN_PROJECT=1 ENV XDG_CACHE_HOME=/pyls/.cache RUN apt-get update \ - && apt-get install -y shellcheck wget \ + && apt-get install -y shellcheck wget git ca-certificates \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip install pipenv @@ -21,13 +21,13 @@ RUN set -eux; \ url=; \ case "$arch" in \ 'amd64') \ - targz='go1.22.5.linux-amd64.tar.gz'; \ + targz='go1.25.0.linux-amd64.tar.gz'; \ ;; \ 'arm64') \ - targz='go1.22.5.linux-arm64.tar.gz'; \ + targz='go1.25.0.linux-arm64.tar.gz'; \ ;; \ 'armhf') \ - targz='go1.22.5.linux-armv6l.tar.gz'; \ + targz='go1.25.0.linux-armv6l.tar.gz'; \ ;; \ *) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \ esac; \ @@ -59,4 +59,4 @@ RUN chmod -R a+rX /usr/local && \ EXPOSE 3001 -CMD ["sh", "-c", "if [ -d /root/.cache ]; then export XDG_CACHE_HOME=/root/.cache && cp -r /pyls/.cache /root/.cache; fi && python3 pyls_launcher.py"] +CMD ["sh", "-c", "if [ -n \"$NETRC\" ]; then echo \"$NETRC\" > /root/.netrc && chmod 600 /root/.netrc; fi && if [ -d /root/.cache ]; then export XDG_CACHE_HOME=/root/.cache && cp -r /pyls/.cache /root/.cache; fi && python3 pyls_launcher.py"] diff --git a/lsp/pyls_launcher.py b/lsp/pyls_launcher.py index 614d47294e..8b19ca5ef5 100644 --- a/lsp/pyls_launcher.py +++ b/lsp/pyls_launcher.py @@ -97,7 +97,7 @@ if __name__ == "__main__": go_mod_path = os.path.join(monaco_path, "go.mod") if not os.path.exists(go_mod_path): f = open(go_mod_path, "w") - f.write("module mymod\ngo 1.22") + f.write("module mymod\ngo 1.25") f.close() port = int(os.environ.get("PORT", "3001")) app = web.Application( From 7a1c28f6d7ca177351187d53bc40e5225a862ffc Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 28 Aug 2025 20:29:04 +0100 Subject: [PATCH 20/40] chore(main): release 1.537.0 (#6486) * chore(main): release 1.537.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 15 +++ backend/Cargo.lock | 91 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 76 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be301a9f36..6a9df670c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [1.537.0](https://github.com/windmill-labs/windmill/compare/v1.536.0...v1.537.0) (2025-08-28) + + +### Features + +* **aichat:** allow reverting specific line for inline script suggestions ([#6480](https://github.com/windmill-labs/windmill/issues/6480)) ([0cc11b3](https://github.com/windmill-labs/windmill/commit/0cc11b3f31aeee60a9d4a231cea5d4285d7ab37e)) +* autovacuum or high intensity tables ([4ad0d25](https://github.com/windmill-labs/windmill/commit/4ad0d255f3eea303e97eab5325f89930b26f9e52)) + + +### Bug Fixes + +* fix okta and oauth0 sso settings ([73a3f4c](https://github.com/windmill-labs/windmill/commit/73a3f4cc73271759650e9246f4eb2e0efb7c7e37)) +* fix relative imports cache invalidation ([#6468](https://github.com/windmill-labs/windmill/issues/6468)) ([006f326](https://github.com/windmill-labs/windmill/commit/006f32602c7609b282f15135989c5f164c109c1c)) +* fix workflow as code behavior with multithread ([4973c86](https://github.com/windmill-labs/windmill/commit/4973c860f2c28d9bdc2af94530d90eb177234e5d)) + ## [1.536.0](https://github.com/windmill-labs/windmill/compare/v1.535.0...v1.536.0) (2025-08-27) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7b7bc45206..669bd8efbb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -5863,7 +5863,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.3+wasi-0.2.4", "wasm-bindgen", ] @@ -10253,9 +10253,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases 0.2.1", @@ -10264,7 +10264,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls 0.23.29", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.16", "tokio", "tracing", @@ -10273,9 +10273,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", @@ -10294,16 +10294,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -14770,11 +14770,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.3+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -15129,7 +15129,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "axum", @@ -15183,7 +15183,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "argon2", @@ -15300,7 +15300,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.536.0" +version = "1.537.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15315,7 +15315,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.536.0" +version = "1.537.0" dependencies = [ "chrono", "serde", @@ -15328,7 +15328,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "axum", @@ -15347,7 +15347,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -15427,7 +15427,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.536.0" +version = "1.537.0" dependencies = [ "regex", "serde", @@ -15442,7 +15442,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "bytes", @@ -15466,7 +15466,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.536.0" +version = "1.537.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15478,7 +15478,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.536.0" +version = "1.537.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15487,7 +15487,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15499,7 +15499,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "serde_json", @@ -15511,7 +15511,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "gosyn", @@ -15523,7 +15523,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15535,7 +15535,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "serde_json", @@ -15547,7 +15547,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "nu-parser", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15569,7 +15569,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15581,7 +15581,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -15604,7 +15604,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15618,7 +15618,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15635,7 +15635,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "lazy_static", @@ -15667,7 +15667,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15692,7 +15692,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "serde_json", @@ -15702,7 +15702,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -15735,7 +15735,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.536.0" +version = "1.537.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15745,7 +15745,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.536.0" +version = "1.537.0" dependencies = [ "anyhow", "async-recursion", @@ -16325,13 +16325,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.3", -] +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" [[package]] name = "writeable" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3f770576bf..69e5d4333e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.536.0" +version = "1.537.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ ] [workspace.package] -version = "1.536.0" +version = "1.537.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index b0afbfb217..eead7304a1 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.536.0 + version: 1.537.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index cdebe91692..dd145efd45 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.536.0"; +export const VERSION = "v1.537.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 8f98e43c0c..743ad64c75 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.536.0"; +export const VERSION = "1.537.0"; const command = new Command() .name("wmill") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6401b0b925..144e871b1c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.536.0", + "version": "1.537.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.536.0", + "version": "1.537.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 1f0841eea9..f98f5789ab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.536.0", + "version": "1.537.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 5fe27bbf38..001b2c6440 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.536.0" -wmill_pg = ">=1.536.0" +wmill = ">=1.537.0" +wmill_pg = ">=1.537.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 653590ef27..aec8eecbc7 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.536.0 + version: 1.537.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 68a5fd2cc3..114e9b0818 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.536.0' + ModuleVersion = '1.537.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 459257aee7..73633134e4 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.536.0" +version = "1.537.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index a515d6d8ae..ee75201d9b 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.536.0" +version = "1.537.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 49fd1c2814..3126b9087e 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.536.0", + "version": "1.537.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 6018f1935e..674c770bc6 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.536.0", + "version": "1.537.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 3fe765ee77..a2da630536 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.536.0 +1.537.0 From e28c9df60f20b70a4dcef802839750bb843b91a4 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 29 Aug 2025 06:57:01 +0000 Subject: [PATCH 21/40] fix: fix preprocessor not displaying immediately on addition --- frontend/src/lib/components/Editor.svelte | 14 ++++++++------ .../src/lib/components/graph/FlowGraphV2.svelte | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 4bbdeb0d71..d421920951 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -544,9 +544,7 @@ let sqlSchemaCompletor: IDisposable | undefined = undefined - async function updateSchema() { - const newSchemaRes = lang === 'graphql' ? args?.api : args?.database - + async function updateSchema(newSchemaRes: string | undefined) { if (typeof newSchemaRes === 'string') { const resourcePath = newSchemaRes.replace('$res:', '') dbSchema = $dbSchemas[resourcePath] @@ -1621,10 +1619,14 @@ ? untrack(() => addSqlTypeCompletions()) : sqlTypeCompletor?.dispose() }) + + let lastArg = undefined $effect(() => { - console.log('updating schema', lang, $dbSchemas) - readFieldsRecursively(args) - lang && $dbSchemas && untrack(() => updateSchema()) + let newArg = lang === 'graphql' ? args?.api : args?.database + if (newArg !== lastArg) { + lastArg = newArg + $dbSchemas && untrack(() => updateSchema(newArg)) + } }) $effect(() => { console.log('updating db schema completions', dbSchema, lang) diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 68ea11ed58..e8a5ee548d 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -487,7 +487,7 @@ additionalAssetsMap: flowGraphAssetsCtx?.val.additionalAssetsMap }, untrack(() => failureModule), - untrack(() => preprocessorModule), + preprocessorModule, eventHandler, success, $useDataflow, From fb25e413f80ab30993ffb233d90d18cf182167b5 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 29 Aug 2025 07:06:17 +0000 Subject: [PATCH 22/40] nit check --- frontend/src/lib/components/Editor.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index d421920951..7ed6954710 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -150,7 +150,7 @@ import { writable } from 'svelte/store' import { formatResourceTypes } from './copilot/chat/script/core' import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte' - import { editorPositionMap, readFieldsRecursively } from '$lib/utils' + import { editorPositionMap } from '$lib/utils' import { extToLang, langToExt } from '$lib/editorLangUtils' import { aiChatManager } from './copilot/chat/AIChatManager.svelte' import type { Selection } from 'monaco-editor' From e2b344ed02b02972366a011c3e873e0f43ab2843 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 29 Aug 2025 07:24:47 +0000 Subject: [PATCH 23/40] fix: skipPreprocessor on re-running job immedaitely from UI --- .../src/routes/(root)/(logged)/run/[...run]/+page.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte index 94c2d380b6..cb42658b10 100644 --- a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte @@ -355,12 +355,14 @@ if (job?.job_kind == 'script') { id = await JobService.runScriptByHash({ ...commonArgs, - hash: job.script_hash! + hash: job.script_hash!, + skipPreprocessor: true }) } else { id = await JobService.runFlowByPath({ ...commonArgs, - path: job.script_path! + path: job.script_path!, + skipPreprocessor: true }) } From 641d5651c5b6fa898e4fc8ad29a8508f9400c6d8 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 29 Aug 2025 09:38:25 +0000 Subject: [PATCH 24/40] fix: fix error handling of pre-processor steps --- backend/windmill-worker/src/js_eval.rs | 2 +- backend/windmill-worker/src/worker_flow.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/windmill-worker/src/js_eval.rs b/backend/windmill-worker/src/js_eval.rs index d5bf2add3b..8b0b2119b5 100644 --- a/backend/windmill-worker/src/js_eval.rs +++ b/backend/windmill-worker/src/js_eval.rs @@ -520,7 +520,7 @@ function get_from_env(name) {{ .map(|a| { format!("let {a} = get_from_env(\"{a}\");\n",) }) .join(""), if expr.contains("error") && transform_context.contains(&"previous_result".to_string()) { - "let error = previous_result.error" + "let error = previous_result.error;" } else { "" }, diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index 37c6e3fc70..f4874f6456 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -1027,7 +1027,7 @@ pub async fn update_flow_status_after_job_completion_internal( .ok_or_else(|| Error::internal_err(format!("requiring flow to be in the queue")))?; tx.commit().await?; - if matches!(module_step, Step::PreprocessorStep) { + if matches!(module_step, Step::PreprocessorStep) && success { let tag_and_concurrency_key = get_tag_and_concurrency(&flow, db).await; let require_args = tag_and_concurrency_key.as_ref().is_some_and(|x| { x.tag.as_ref().is_some_and(|t| t.contains("$args")) @@ -1144,9 +1144,7 @@ pub async fn update_flow_status_after_job_completion_internal( "error while updating args in preprocessing step: {e:#}" )) })?; - if success { - return Ok(UpdateFlowStatusAfterJobCompletion::PreprocessingStep); - } + return Ok(UpdateFlowStatusAfterJobCompletion::PreprocessingStep); } let job_root = flow_job From 89e20f51972cfbcfc6e4f6160b2ed82ce0fd4ad4 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 29 Aug 2025 09:43:00 +0000 Subject: [PATCH 25/40] chore(main): release 1.537.1 (#6491) * chore(main): release 1.537.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 9 +++ backend/Cargo.lock | 68 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 60 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a9df670c1..89fe783df1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.537.1](https://github.com/windmill-labs/windmill/compare/v1.537.0...v1.537.1) (2025-08-29) + + +### Bug Fixes + +* fix error handling of pre-processor steps ([641d565](https://github.com/windmill-labs/windmill/commit/641d5651c5b6fa898e4fc8ad29a8508f9400c6d8)) +* fix preprocessor not displaying immediately on addition ([e28c9df](https://github.com/windmill-labs/windmill/commit/e28c9df60f20b70a4dcef802839750bb843b91a4)) +* skipPreprocessor on re-running job immedaitely from UI ([e2b344e](https://github.com/windmill-labs/windmill/commit/e2b344ed02b02972366a011c3e873e0f43ab2843)) + ## [1.537.0](https://github.com/windmill-labs/windmill/compare/v1.536.0...v1.537.0) (2025-08-28) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 669bd8efbb..1c7d202114 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1018,9 +1018,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.61.4" +version = "0.61.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" +checksum = "eaa31b350998e703e9826b2104dd6f63be0508666e1aba88137af060e8944047" dependencies = [ "aws-smithy-types", ] @@ -2105,9 +2105,9 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "comfy-table" -version = "7.1.4" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +checksum = "3f8e18d0dca9578507f13f9803add0df13362b02c501c1c17734f0dbb52eaf0b" dependencies = [ "unicode-segmentation", "unicode-width 0.2.1", @@ -9877,9 +9877,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -15129,7 +15129,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "axum", @@ -15183,7 +15183,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "argon2", @@ -15300,7 +15300,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.537.0" +version = "1.537.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15315,7 +15315,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.537.0" +version = "1.537.1" dependencies = [ "chrono", "serde", @@ -15328,7 +15328,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "axum", @@ -15347,7 +15347,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "async-recursion", @@ -15427,7 +15427,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.537.0" +version = "1.537.1" dependencies = [ "regex", "serde", @@ -15442,7 +15442,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "bytes", @@ -15466,7 +15466,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.537.0" +version = "1.537.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15478,7 +15478,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.537.0" +version = "1.537.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15487,7 +15487,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "lazy_static", @@ -15499,7 +15499,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "serde_json", @@ -15511,7 +15511,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "gosyn", @@ -15523,7 +15523,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "lazy_static", @@ -15535,7 +15535,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "serde_json", @@ -15547,7 +15547,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "nu-parser", @@ -15558,7 +15558,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15569,7 +15569,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15581,7 +15581,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "async-recursion", @@ -15604,7 +15604,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "lazy_static", @@ -15618,7 +15618,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15635,7 +15635,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "lazy_static", @@ -15649,7 +15649,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "lazy_static", @@ -15667,7 +15667,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15692,7 +15692,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "serde_json", @@ -15702,7 +15702,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "async-recursion", @@ -15735,7 +15735,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.537.0" +version = "1.537.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15745,7 +15745,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.537.0" +version = "1.537.1" dependencies = [ "anyhow", "async-recursion", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 69e5d4333e..fce0ea353a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.537.0" +version = "1.537.1" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ ] [workspace.package] -version = "1.537.0" +version = "1.537.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index eead7304a1..f788be50d2 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.537.0 + version: 1.537.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index dd145efd45..bab3c7951b 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.537.0"; +export const VERSION = "v1.537.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 743ad64c75..28a98df79c 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.537.0"; +export const VERSION = "1.537.1"; const command = new Command() .name("wmill") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 144e871b1c..c37ee5d0f4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.537.0", + "version": "1.537.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.537.0", + "version": "1.537.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index f98f5789ab..7174d7cafa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.537.0", + "version": "1.537.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 001b2c6440..3c3a6899f4 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.537.0" -wmill_pg = ">=1.537.0" +wmill = ">=1.537.1" +wmill_pg = ">=1.537.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index aec8eecbc7..cd43078197 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.537.0 + version: 1.537.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 114e9b0818..9f98410bcf 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.537.0' + ModuleVersion = '1.537.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 73633134e4..60905554cb 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.537.0" +version = "1.537.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index ee75201d9b..e1f37fee4b 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.537.0" +version = "1.537.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 3126b9087e..bc3557e9ed 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.537.0", + "version": "1.537.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 674c770bc6..47dc2c096e 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.537.0", + "version": "1.537.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index a2da630536..4daaa7338c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.537.0 +1.537.1 From 8cd1c6474b3c55d706683f83b40ef5569245b559 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Fri, 29 Aug 2025 15:00:27 +0200 Subject: [PATCH 26/40] fix(frontend): capture/trigger UI nits (#6494) --- .../components/triggers/CaptureWrapper.svelte | 15 ++------- .../triggers/http/RouteEditorInner.svelte | 2 +- .../WebsocketTriggerEditorInner.svelte | 32 +++++++++---------- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/frontend/src/lib/components/triggers/CaptureWrapper.svelte b/frontend/src/lib/components/triggers/CaptureWrapper.svelte index 133bb029a2..4177c3d146 100644 --- a/frontend/src/lib/components/triggers/CaptureWrapper.svelte +++ b/frontend/src/lib/components/triggers/CaptureWrapper.svelte @@ -2,7 +2,7 @@ import { workspaceStore } from '$lib/stores' import { CaptureService, type CaptureConfig, type CaptureTriggerKind } from '$lib/gen' import { onDestroy, untrack } from 'svelte' - import { isObject, sendUserToast, sleep } from '$lib/utils' + import { sendUserToast, sleep } from '$lib/utils' import RouteCapture from './http/RouteCapture.svelte' import type { ConnectionInfo } from '../common/alert/ConnectionIndicator.svelte' import type { CaptureInfo } from './CaptureSection.svelte' @@ -37,7 +37,7 @@ captureType = 'webhook', data = {}, connectionInfo = $bindable(undefined), - args = $bindable({}), + args = {}, isValid = false, triggerDeployed = false }: Props = $props() @@ -100,7 +100,6 @@ } return captureConfigs } - getCaptureConfigs().then((captureConfigs) => setDefaultArgs(captureConfigs)) async function capture() { let i = 0 @@ -120,16 +119,6 @@ } } - function setDefaultArgs(captureConfigs: { [key: string]: CaptureConfig }) { - if (captureType in captureConfigs) { - const triggerConfig = captureConfigs[captureType].trigger_config - args = isObject(triggerConfig) ? triggerConfig : {} - } else { - args = {} - } - ready = true - } - onDestroy(() => { captureActive = false }) diff --git a/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte b/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte index a89425478b..67cd7639e0 100644 --- a/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte +++ b/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte @@ -596,7 +596,7 @@ {can_write} bind:static_asset_config showTestingBadge={isEditor} - isDraftOnly={trigger?.isDraft} + isDraftOnly={trigger ? trigger.isDraft : false} /> {#if !is_static_website} diff --git a/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte index d55000aba9..19c156fc8c 100644 --- a/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte @@ -417,8 +417,8 @@
- {#if !hideTarget} -
+
+ {#if !hideTarget}

Pick a script or flow to be triggered @@ -449,21 +449,21 @@ {/if}

+ {/if} - { - can_return_message = !can_return_message - }} - options={{ - right: 'Send runnable result', - rightTooltip: - 'Whether the runnable result should be sent as a message to the websocket server when not null.' - }} - disabled={!can_write} - /> - - {/if} + { + can_return_message = !can_return_message + }} + options={{ + right: 'Send runnable result', + rightTooltip: + 'Whether the runnable result should be sent as a message to the websocket server when not null.' + }} + disabled={!can_write} + /> + Date: Sat, 30 Aug 2025 02:32:35 +0000 Subject: [PATCH 27/40] fix: schema editor reactivity improvements (#6496) * all * all * all * all * all * nit * all * all * all * all --- frontend/package-lock.json | 3 +- frontend/package.json | 2 +- frontend/src/global.d.ts | 4 +- frontend/src/lib/components/ArgInput.svelte | 27 +- .../lib/components/EditableSchemaForm.svelte | 108 ++++---- frontend/src/lib/components/Portal.svelte | 18 +- frontend/src/lib/components/SchemaForm.svelte | 4 +- .../apps/components/layout/AppModal.svelte | 4 +- .../apps/editor/AppEditorHeader.svelte | 1 + .../component/ComponentNavigation.svelte | 8 +- .../component/componentCallbacks.svelte.ts | 2 +- .../ArrayStaticInputEditor.svelte | 5 +- .../editor/settingsPanel/GridCondition.svelte | 5 +- .../editor/settingsPanel/GridNavbar.svelte | 5 +- .../apps/editor/settingsPanel/GridTab.svelte | 5 +- .../editor/settingsPanel/TableActions.svelte | 5 +- .../common/drawer/Disposable.svelte | 101 +++++--- .../components/common/drawer/Drawer.svelte | 27 +- .../components/flows/content/FlowInput.svelte | 37 ++- .../flows/content/FlowModuleSuspend.svelte | 33 ++- .../flows/content/ScriptEditorDrawer.svelte | 21 +- .../propertyPicker/ObjectViewer.svelte | 16 +- .../components/schema/AddPropertyV2.svelte | 12 +- .../schema/EditableSchemaDrawer.svelte | 234 +++++++++--------- .../schema/EditableSchemaSdkWrapper.svelte | 22 +- .../schema/EditableSchemaWrapper.svelte | 12 +- .../schema/FlowPropertyEditor.svelte | 52 +--- .../components/schema/SchemaFormDND.svelte | 38 +-- .../schema/editable_schema_wrapper.ts | 1 - .../triggers/AddTriggersButton.svelte | 1 + .../routes/test_dev/sdk_schema/+page.svelte | 11 +- 31 files changed, 433 insertions(+), 391 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c37ee5d0f4..69d3d0a144 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ "@scalar/openapi-parser": "^0.15.0", "@tanstack/svelte-table": "npm:tanstack-table-8-svelte-5@^0.1", "@tutorlatin/svelte-tiny-virtual-list": "^3.0.2", - "@windmill-labs/svelte-dnd-action": "^0.9.48", + "@windmill-labs/svelte-dnd-action": "^0.9.44", "@xterm/addon-fit": "^0.10.0", "@xyflow/svelte": "^1.0.0", "ag-charts-community": "^9.0.1", @@ -3898,6 +3898,7 @@ "version": "0.9.48", "resolved": "https://registry.npmjs.org/@windmill-labs/svelte-dnd-action/-/svelte-dnd-action-0.9.48.tgz", "integrity": "sha512-A6pWayH3nOi79DZohTscGj5t2PuNtHlKM5WIAj9WlVDx5pS1A+MsyfuNZi8oCpj2590wFmQl+58G4y4xZqDa1Q==", + "license": "MIT", "peerDependencies": { "svelte": ">=3.23.0 || ^5.0.0-next.0" } diff --git a/frontend/package.json b/frontend/package.json index 7174d7cafa..775d5f4ec0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -92,7 +92,7 @@ "@scalar/openapi-parser": "^0.15.0", "@tanstack/svelte-table": "npm:tanstack-table-8-svelte-5@^0.1", "@tutorlatin/svelte-tiny-virtual-list": "^3.0.2", - "@windmill-labs/svelte-dnd-action": "^0.9.48", + "@windmill-labs/svelte-dnd-action": "^0.9.44", "@xterm/addon-fit": "^0.10.0", "@xyflow/svelte": "^1.0.0", "ag-charts-community": "^9.0.1", diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index 3cf0168db4..bd06974068 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -1,7 +1,7 @@ /// -declare type Item = import('svelte-dnd-action').Item -declare type DndEvent = import('svelte-dnd-action').DndEvent +declare type Item = import('@windmill-labs/svelte-dnd-action').Item +declare type DndEvent = import('@windmill-labs/svelte-dnd-action').DndEvent declare namespace svelte.JSX { interface HTMLAttributes { onconsider?: (event: CustomEvent> & { target: EventTarget & T }) => void diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 5e8b189a09..7897832188 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -992,8 +992,11 @@ } } bind:args={value} - dndType={`nested-${title}`} - hiddenArgs={['label', 'kind']} + hiddenArgs={[ + oneOf?.find((o) => Object.keys(o.properties ?? {}).includes('kind')) + ? 'kind' + : 'label' + ]} on:reorder={(e) => { if (oneOf && oneOf[objIdx]) { const keys = e.detail @@ -1086,20 +1089,14 @@ {disablePortal} {disabled} {prettifyHeader} - bind:schema={ - () => ({ - properties, - $schema: '', - required: nestedRequired ?? [], - type: 'object', - order - }), - (newSchema) => { - dispatch('nestedChange') - } - } + schema={{ + properties, + $schema: '', + required: nestedRequired ?? [], + type: 'object', + order + }} bind:args={value} - dndType={`nested-${title}`} on:reorder={(e) => { const keys = e.detail order = keys diff --git a/frontend/src/lib/components/EditableSchemaForm.svelte b/frontend/src/lib/components/EditableSchemaForm.svelte index a3dfc5a168..529e99a1f9 100644 --- a/frontend/src/lib/components/EditableSchemaForm.svelte +++ b/frontend/src/lib/components/EditableSchemaForm.svelte @@ -14,13 +14,18 @@ import FlowPropertyEditor from './schema/FlowPropertyEditor.svelte' import PropertyEditor from './schema/PropertyEditor.svelte' import SimpleEditor from './SimpleEditor.svelte' - import { createEventDispatcher, tick, untrack } from 'svelte' + import { createEventDispatcher, untrack } from 'svelte' import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte' import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte' import Label from './Label.svelte' import { sendUserToast } from '$lib/toast' import Toggle from './Toggle.svelte' - import { DynamicSelect, emptyString } from '$lib/utils' + import { + DynamicSelect, + emptyString, + generateRandomString, + readFieldsRecursively + } from '$lib/utils' import Popover from './meltComponents/Popover.svelte' import SchemaFormDnd from './schema/SchemaFormDND.svelte' import { deepEqual } from 'fast-equals' @@ -48,7 +53,6 @@ isAppInput?: boolean displayWebhookWarning?: boolean onlyMaskPassword?: boolean - dndType?: string | undefined editTab: | 'inputEditor' | 'history' @@ -75,6 +79,8 @@ addProperty?: import('svelte').Snippet runButton?: import('svelte').Snippet extraTab?: import('svelte').Snippet + schemaFormClassName?: string + onChange?: (args: Record) => void } let { @@ -91,7 +97,6 @@ isAppInput = false, displayWebhookWarning = false, onlyMaskPassword = false, - dndType = undefined, editTab, previewSchema = undefined, editPanelInitialSize = undefined, @@ -110,7 +115,9 @@ openEditTab, addProperty, runButton, - extraTab + extraTab, + schemaFormClassName = undefined, + onChange = undefined }: Props = $props() $effect.pre(() => { @@ -125,6 +132,13 @@ } }) + $effect(() => { + if (onChange) { + readFieldsRecursively(args) + onChange(args ?? {}) + } + }) + $effect(() => { if (schema && dynSelectCode !== undefined && dynSelectLang !== undefined) { if (dynSelectCode && dynSelectCode.trim()) { @@ -154,9 +168,10 @@ let variableEditor: VariableEditor | undefined = $state(undefined) let keys: string[] = $state( - Array.isArray(schema?.order) + (Array.isArray(schema?.order) ? [...schema.order] : (Object.keys(schema?.properties ?? {}) ?? Object.keys(schema?.properties ?? {})) + ).filter((x) => !hiddenArgs?.includes(x)) ) function alignOrderWithProperties(schema: { @@ -187,22 +202,18 @@ return hasChanged } function onSchemaChange() { - let editSchema = false if (alignOrderWithProperties(schema)) { - console.log('alignOrderWithProperties', JSON.stringify(schema, null, 2)) - editSchema = true + // console.log('alignOrderWithProperties', JSON.stringify(schema, null, 2)) } - let lkeys = schema?.order ?? Object.keys(schema?.properties ?? {}) + let lkeys = (schema?.order ?? Object.keys(schema?.properties ?? {})).filter( + (x) => !hiddenArgs?.includes(x) + ) if (schema?.properties && !deepEqual(lkeys, keys)) { keys = [...lkeys] - editSchema = true if (opened == undefined) { opened = keys[0] } } - if (editSchema) { - schema = schema - } } let opened: string | undefined = $state(untrack(() => keys[0])) @@ -248,30 +259,31 @@ // clear the input el.value = oldName } else { + let newSchema = $state.snapshot(schema) if (args) { args[newName] = args[oldName] delete args[oldName] } - schema.properties[newName] = schema.properties[oldName] - delete schema.properties[oldName] + newSchema.properties[newName] = newSchema.properties[oldName] + delete newSchema.properties[oldName] - if (schema.required?.includes(oldName)) { - schema.required = schema.required?.map((x) => (x === oldName ? newName : x)) + if (newSchema.required?.includes(oldName)) { + newSchema.required = newSchema.required?.map((x) => (x === oldName ? newName : x)) } // Replace the old name with the new name in the order array - if (schema.order) { - const index = schema.order.indexOf(oldName) + if (newSchema.order) { + const index = newSchema.order.indexOf(oldName) if (index !== -1) { - schema.order[index] = newName + newSchema.order[index] = newName } } opened = newName - schema = $state.snapshot(schema) - dispatch('change', schema) + schema = newSchema + sendUserToast('Argument renamed') } } @@ -368,6 +380,8 @@ const code = generateFn(functionName) dynSelectCode = dynSelectCode ? dynSelectCode.concat(code) : code } + + let dndType = $state(generateRandomString())
@@ -404,15 +418,15 @@ class="min-h-0 overflow-y-auto grow rounded-md {runButton ? 'flex flex-col gap-2' : ''}" > (previewSchema ? previewSchema : schema), (newSchema) => { schema = newSchema - tick().then(() => dispatch('change', schema)) } } - {dndType} + {hiddenArgs} {disableDnd} {onlyMaskPassword} bind:args @@ -420,11 +434,16 @@ opened = e.detail }} on:reorder={(e) => { + let order = e.detail + let newProperties = {} + for (let key of order) { + newProperties[key] = schema.properties[key] + } schema = { ...schema, + properties: newProperties, order: e.detail } - tick().then(() => dispatch('change', schema)) }} helperScript={{ type: 'inline', @@ -436,9 +455,6 @@ {diff} on:acceptChange on:rejectChange - on:nestedChange={() => { - dispatch('change', schema) - }} {shouldDispatchChanges} bind:isValid noVariablePicker={noVariablePicker || customUi?.disableVariablePicker === true} @@ -446,8 +462,8 @@ {@render runButton?.()} -
- {#if dynSelectFunctions.length > 0} + {#if dynSelectFunctions.length > 0} +
- {/if} -
+
+ {/if}
@@ -514,7 +530,7 @@ {#if jsonEnabled && customUi?.jsonOnly != true}
{#if addPropertyInEditorTab} - + {#snippet trigger()}
{#if opened === argName}
- {#if !hiddenArgs.includes(argName) && Object.keys(schema?.properties ?? {}).includes(argName)} + {#if Object.keys(schema?.properties ?? {}).includes(argName)} {#if typeof args == 'object' && schema?.properties[argName]} { - schema = $state.snapshot(schema) - dispatch('change', schema) - }} > {#snippet typeeditor()} {#if isFlowInput || isAppInput} @@ -742,13 +754,9 @@ type: v } } + schema.properties = schema.properties } } - on:selected={(e) => { - schema = schema - dispatch('change', schema) - dispatch('schemaChange') - }} > {#snippet children({ item })} {#each typeOptions as x} @@ -762,6 +770,9 @@ {#if isFlowInput || isAppInput} { + dndType = generateRandomString() + }} bind:defaultValue={schema.properties[argName].default} {variableEditor} {itemPicker} @@ -792,12 +803,6 @@ (x) => x !== argName ) } - dispatch('change', schema) - }} - on:schemaChange={(e) => { - schema = $state.snapshot(schema) - dispatch('change', schema) - dispatch('schemaChange') }} /> {/if} @@ -822,7 +827,6 @@ on:change={() => { try { schema = JSON.parse(schemaString) - dispatch('change', schema) error = '' } catch (err) { error = err.message diff --git a/frontend/src/lib/components/Portal.svelte b/frontend/src/lib/components/Portal.svelte index 5ea9c44e95..801188de2e 100644 --- a/frontend/src/lib/components/Portal.svelte +++ b/frontend/src/lib/components/Portal.svelte @@ -1,4 +1,4 @@ - -