diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index 394bd8c23a..d36f830b08 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -96,9 +96,9 @@ impl IntoResponse for Error { }; if matches!(status, axum::http::StatusCode::NOT_FOUND) { - tracing::warn!(not_found = e.to_string()); + tracing::warn!(message = e.to_string()); } else { - tracing::error!(error = e.to_string()); + tracing::error!(essage = e.to_string()); }; axum::response::Response::builder() diff --git a/cli/apps.ts b/cli/apps.ts index dad3fae466..1681f5afd0 100644 --- a/cli/apps.ts +++ b/cli/apps.ts @@ -54,6 +54,7 @@ export async function pushApp( } else { console.log(colors.yellow.bold("Creating new app...")); + console.log(message); await AppService.createApp({ workspace, requestBody: { @@ -101,12 +102,16 @@ async function push(opts: GlobalOptions, filePath: string) { const workspace = await resolveWorkspace(opts); await requireLogin(opts); +<<<<<<< Updated upstream await pushApp( workspace.workspaceId, filePath, undefined, parseFromFile(filePath) ); +======= + await pushApp(workspace.workspaceId, filePath, parseFromFile(filePath)); +>>>>>>> Stashed changes console.log(colors.bold.underline.green("App pushed")); } diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index b57a7a914b..fb5ddd09bc 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -165,6 +165,9 @@ // let widgets: HTMLElement | undefined = // document.getElementById('monaco-widgets-root') ?? undefined + if (!divEl) { + return + } editor = meditor.create(divEl as HTMLDivElement, { ...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets), model, diff --git a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte index d2e1f783a0..357e7f7316 100644 --- a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte +++ b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte @@ -125,22 +125,20 @@ } else { await runnableComponent?.runComponent() } - if (rowContext && rowInputs) { - rowInputs.set(id, { result: outputs.result.peak(), loading: false }) - } } let loading = false let css = initCss($app.css?.buttoncomponent, customCss) -{#each Object.keys(components['buttoncomponent'].initialData.configuration) as key (key)} +{#each Object.entries(components['buttoncomponent'].initialData.configuration) as [key, initialConfig] (key)} {/each} @@ -172,6 +170,16 @@ {render} {outputs} {extraKey} + onSuccess={(r) => { + let inputOutput = { result: r, loading: false } + if (rowContext && rowInputs) { + rowInputs.set(id, inputOutput) + } + if (iterContext && listInputs) { + listInputs.set(id, inputOutput) + } + console.log('success', r) + }} refreshOnStart={resolvedConfig.triggerOnAppLoad} > diff --git a/frontend/src/lib/components/apps/components/helpers/InputValue.svelte b/frontend/src/lib/components/apps/components/helpers/InputValue.svelte index 77c0cd604a..6c1abea0ce 100644 --- a/frontend/src/lib/components/apps/components/helpers/InputValue.svelte +++ b/frontend/src/lib/components/apps/components/helpers/InputValue.svelte @@ -28,6 +28,7 @@ export let error: string = '' export let key: string = '' export let field: string = key + export let onDemandOnly: boolean = false const { componentControl, runnableComponents } = getContext('AppViewerContext') @@ -47,6 +48,7 @@ } $: lastInput?.type == 'evalv2' && + !onDemandOnly && (fullContext.iter != undefined || fullContext.row != undefined || fullContext.group != undefined) && @@ -144,8 +146,9 @@ let lastExpr: any = undefined - const debounceEval = async () => { - let nvalue = await evalExpr(lastInput as EvalAppInput) + const debounceEval = async (s?: string) => { + let args = s == 'exprChanged' ? { file: { name: 'example.png' } } : undefined + let nvalue = await evalExpr(lastInput as EvalAppInput, args) if (field) { editorContext?.evalPreview.update((x) => { @@ -173,7 +176,7 @@ $: lastInput && lastInput.type == 'eval' && $stateId && $state && debounce2(debounceEval) - $: lastInput?.type == 'evalv2' && lastInput.expr && debounceEval() + $: lastInput?.type == 'evalv2' && lastInput.expr && debounceEval('exprChanged') $: lastInput?.type == 'templatev2' && lastInput.eval && debounceTemplate() async function handleConnection() { @@ -200,6 +203,12 @@ } else if (lastInput?.type == 'eval') { value = await evalExpr(lastInput as EvalAppInput) } else if (lastInput?.type == 'evalv2') { + if (onDemandOnly) { + value = (args?: any) => { + return evalExpr(lastInput as EvalV2AppInput, args) + } + return + } const skey = `${id}-${key}-${rowContext ? $rowContext.index : 0}-${ iterContext ? $iterContext.index : 0 }` @@ -257,9 +266,13 @@ ): Promise { if (iterContext && $iterContext.disabled) return try { + const context = computeGlobalContext( + $worldStore, + deepMergeWithPriority(fullContext, args ?? {}) + ) const r = await eval_like( input.expr, - computeGlobalContext($worldStore, deepMergeWithPriority(fullContext, args ?? {})), + context, true, $state, $mode == 'dnd', diff --git a/frontend/src/lib/components/apps/components/helpers/ResolveConfig.svelte b/frontend/src/lib/components/apps/components/helpers/ResolveConfig.svelte index e08e82fafc..222cb1f4e6 100644 --- a/frontend/src/lib/components/apps/components/helpers/ResolveConfig.svelte +++ b/frontend/src/lib/components/apps/components/helpers/ResolveConfig.svelte @@ -7,7 +7,7 @@ export let key: string export let resolvedConfig: any | { type: 'oneOf'; configuration: any; selected: string } export let configuration: RichConfiguration - + export let initialConfig: RichConfiguration | undefined = undefined $: configuration?.type == 'oneOf' && handleSelected(configuration.selected) function handleSelected(selected: string) { @@ -22,11 +22,13 @@ {#each Object.keys(configuration.configuration?.[choice] ?? {}) as nestedKey (nestedKey)} {#if resolvedConfig.configuration?.[choice] != undefined} {/if} {/each} @@ -36,6 +38,8 @@ key={key + extraKey} {id} input={configuration} + onDemandOnly={(initialConfig?.type == 'static' || initialConfig?.type == 'evalv2') && + initialConfig?.onDemandOnly} bind:value={resolvedConfig} /> {/if} diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte index 626d312206..f3c1612ecd 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte @@ -515,7 +515,7 @@ recordJob(jobId, result, undefined, transformerResult) delete $errorByComponent[id] - dispatch('success') + dispatch('success', result) donePromise?.(result) } @@ -530,7 +530,7 @@ onMount(() => { cancellableRun = (inlineScript?: InlineScript, setRunnableJobEditorPanel?: boolean) => { let rejectCb: (err: Error) => void - let p: Partial> = new Promise((resolve, reject) => { + let p: Partial> = new Promise((resolve, reject) => { rejectCb = reject donePromise = resolve executeComponent(true, inlineScript, setRunnableJobEditorPanel).catch(reject) diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte index dc85133b88..8f7454fef5 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte @@ -27,15 +27,15 @@ | 'close' | 'clearFiles' configuration: { - gotoUrl: { url: string | undefined; newTab: boolean | undefined } + gotoUrl: { url: (() => string) | string | undefined; newTab: boolean | undefined } setTab: { setTab: { id: string; index: number }[] | undefined } sendToast?: { - message: string | undefined + message: (() => string) | string | undefined } sendErrorToast?: { - message: string | undefined + message: (() => string) | string | undefined appendError: boolean | undefined } openModal?: { @@ -82,6 +82,7 @@ export let errorHandledByComponent: boolean = false export let hasChildrens: boolean = false export let allowConcurentRequests = false + export let onSuccess: (result: any) => void = () => {} export function setArgs(value: any) { runnableComponent?.setArgs(value) @@ -122,7 +123,7 @@ ) } - export function handleSideEffect(success: boolean, errorMessage?: string) { + export async function handleSideEffect(success: boolean, errorMessage?: string) { const sideEffect = success ? doOnSuccess : doOnError if (recomputeIds && success) { @@ -146,33 +147,39 @@ } break case 'gotoUrl': - const url = sideEffect?.configuration?.gotoUrl?.url - - if (!url) return + let gotoUrl = sideEffect?.configuration?.gotoUrl?.url + if (!gotoUrl) return + if (typeof gotoUrl === 'function') { + gotoUrl = await gotoUrl() + } const newTab = sideEffect?.configuration?.gotoUrl?.newTab if (newTab) { - window.open(url, '_blank') + window.open(gotoUrl, '_blank') } else { - window.location.href = url + window.location.href = gotoUrl } break case 'sendToast': { - const message = sideEffect?.configuration?.sendToast?.message + let message = sideEffect?.configuration?.sendToast?.message if (!message) return - + if (typeof message === 'function') { + message = await message() + } sendUserToast(message, !success) break } case 'sendErrorToast': { - const message = sideEffect?.configuration?.sendErrorToast?.message + let message = sideEffect?.configuration?.sendErrorToast?.message const appendError = sideEffect?.configuration?.sendErrorToast?.appendError if (!message) return - + if (typeof message === 'function') { + message = await message() + } sendUserToast(message, true, [], appendError ? errorMessage : undefined) break } @@ -254,7 +261,10 @@ on:doneError on:cancel on:resultSet={() => (initializing = false)} - on:success={() => handleSideEffect(true)} + on:success={(e) => { + onSuccess(e.detail) + handleSideEffect(true) + }} on:handleError={(e) => handleSideEffect(false, e.detail)} {outputs} {errorHandledByComponent} diff --git a/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte b/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte index 1c98eab63a..fca2432ed4 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte @@ -16,7 +16,6 @@ import { HelpersService, type UploadFilePart } from '$lib/gen' import { writable, type Writable } from 'svelte/store' import { Ban, CheckCheck, FileWarning, Files, RefreshCcw, Trash } from 'lucide-svelte' - import InputValue from '../helpers/InputValue.svelte' export let id: string export let configuration: RichConfigurations @@ -82,7 +81,14 @@ return } - const path = (await inputValue?.computeExpr({ file: fileToUpload })) ?? fileToUploadKey + let pathTemplate = resolvedConfig?.type?.configuration?.s3?.pathTemplate as any + + const path = + typeof pathTemplate == 'function' + ? (await pathTemplate?.({ + file: fileToUpload + })) ?? fileToUploadKey + : fileToUploadKey $fileUploads = $fileUploads.filter((fileUpload) => fileUpload.name !== fileToUpload.name) @@ -196,8 +202,6 @@ } } - let inputValue: InputValue | undefined = undefined - async function deleteFile(fileKey: string) { await HelpersService.deleteS3File({ workspace: $workspaceStore!, @@ -245,25 +249,27 @@ /> {/each} -{#each Object.keys(components['s3fileinputcomponent'].initialData.configuration) as key (key)} +{#each Object.entries(components['s3fileinputcomponent'].initialData.configuration) as [key, value] (key)} {/each} -{#if configuration.type?.['configuration']?.s3.pathTemplate} + {#if render}
diff --git a/frontend/src/lib/components/apps/editor/appUtils.ts b/frontend/src/lib/components/apps/editor/appUtils.ts index f6bace868e..a6b97aa6b8 100644 --- a/frontend/src/lib/components/apps/editor/appUtils.ts +++ b/frontend/src/lib/components/apps/editor/appUtils.ts @@ -25,7 +25,8 @@ import type { StaticAppInput, EvalAppInput, EvalV2AppInput, - InputConnectionEval + InputConnectionEval, + StaticAppInputOnDemand } from '../inputType' import { get, type Writable } from 'svelte/store' import { deepMergeWithPriority } from '$lib/utils' @@ -640,7 +641,9 @@ export type InitConfig< configuration: { [Choice in keyof T[Property]['configuration']]: { [IT in keyof T[Property]['configuration'][Choice]]: T[Property]['configuration'][Choice][IT] extends StaticAppInput - ? T[Property]['configuration'][Choice][IT]['value'] | undefined + ? T[Property]['configuration'][Choice][IT] extends StaticAppInputOnDemand + ? () => Promise + : T[Property]['configuration'][Choice][IT]['value'] | undefined : undefined } } diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index 153ff6d41e..040594317a 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -58,12 +58,7 @@ import type { } from '../../types' import type { Size } from '../../svelte-grid/types' -import type { - AppInputSpec, - EvalV2AppInput, - ResultAppInput, - StaticAppInput -} from '../../inputType' +import type { AppInputSpec, EvalV2AppInput, ResultAppInput, StaticAppInput } from '../../inputType' export type BaseComponent = { type: T @@ -404,7 +399,8 @@ const onSuccessClick = { fieldType: 'text', type: 'static', value: '', - placeholder: '/apps/get/foo' + placeholder: '/apps/get/foo', + onDemandOnly: true }, newTab: { tooltip: 'Open the url in a new tab', @@ -419,7 +415,8 @@ const onSuccessClick = { value: [] as Array<{ id: string; index: number }>, fieldType: 'array', subFieldType: 'tab-select', - tooltip: 'Set the tabs id and index to go to on success' + tooltip: 'Set the tabs id and index to go to on success', + onDemandOnly: true } }, sendToast: { @@ -428,7 +425,8 @@ const onSuccessClick = { fieldType: 'text', type: 'static', value: '', - placeholder: 'Hello there' + placeholder: 'Hello there', + onDemandOnly: true } }, openModal: { @@ -489,7 +487,8 @@ const onErrorClick = { fieldType: 'text', type: 'static', value: '', - placeholder: '/apps/get/foo' + placeholder: '/apps/get/foo', + onDemandOnly: true }, newTab: { tooltip: 'Open the url in a new tab', @@ -513,7 +512,8 @@ const onErrorClick = { fieldType: 'text', type: 'static', value: '', - placeholder: 'Hello there' + placeholder: 'Hello there', + onDemandOnly: true }, appendError: { tooltip: 'Append the error message to the toast', @@ -3051,10 +3051,12 @@ This is a paragraph. }, */ pathTemplate: { - type: 'eval', + type: 'evalv2', expr: `\`\${file.name}\``, fieldType: 'template', - } + connections: [], + onDemandOnly: true + } as EvalV2AppInput } } } as const diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentInputTypeEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentInputTypeEditor.svelte index 56afe6aa3f..51e6868d47 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentInputTypeEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentInputTypeEditor.svelte @@ -12,7 +12,6 @@ export let componentInput: AppInput export let disableStatic: boolean = false export let evalV2editor: EvalV2InputEditor | undefined - export let id: string const { onchange, connectingInput, app } = getContext('AppViewerContext') diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte index 1f13a35b56..2a1fd34115 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte @@ -220,7 +220,6 @@
@@ -371,6 +370,7 @@ inputSpecsConfiguration={initialConfiguration} bind:inputSpecs={componentSettings.item.data.configuration} userInputEnabled={false} + acceptSelf /> {:else if componentSettings.item.data.type != 'containercomponent'} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/GridTabDisabled.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/GridTabDisabled.svelte index 50595490ce..aab13f4188 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/GridTabDisabled.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/GridTabDisabled.svelte @@ -19,9 +19,10 @@ on:change={() => { if (disablable) { field = { - type: 'eval', + type: 'evalv2', expr: 'false', - fieldType: 'boolean' + fieldType: 'boolean', + connections: [] } } else { field = { diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte index 31b725183e..47a82e4a5c 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte @@ -36,6 +36,7 @@ export let shouldFormatExpression: boolean = false export let fixedOverflowWidgets: boolean = true export let loading: boolean = false + export let acceptSelf: boolean = false const { connectingInput, app } = getContext('AppViewerContext') @@ -168,6 +169,7 @@ {:else if componentInput?.type === 'evalv2'} ('AppViewerContext') const { evalPreview } = getContext('AppEditorContext') @@ -25,7 +26,7 @@ $: extraLib = componentInput?.expr && $worldStore - ? buildExtraLib($worldStore?.outputsById ?? {}, id, $state, false) + ? buildExtraLib($worldStore?.outputsById ?? {}, acceptSelf ? '' : id, $state, false) : undefined if ( diff --git a/frontend/src/lib/components/apps/inputType.ts b/frontend/src/lib/components/apps/inputType.ts index 9a8f4af63f..0d72d2d2bd 100644 --- a/frontend/src/lib/components/apps/inputType.ts +++ b/frontend/src/lib/components/apps/inputType.ts @@ -74,6 +74,7 @@ export type EvalInputV2 = { type: 'evalv2' expr: string connections: InputConnectionEval[] + onDemandOnly?: boolean } export type RowInput = { @@ -162,6 +163,7 @@ type InputConfiguration = { convertTo?: ReadFileAs } noStatic?: boolean + onDemandOnly?: boolean } export type StaticOptions = { @@ -216,6 +218,8 @@ export type UserAppInput = Extract export type ResultAppInput = Extract export type EvalAppInput = Extract export type EvalV2AppInput = Extract +export type StaticAppInputOnDemand = Extract + export type UploadAppInput = Extract export type RichAppInput =