From 0d55079c924ef1568ef162777562f6a41c25fb41 Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:30:11 +0100 Subject: [PATCH] Add on behaklft of selector for script flow apps (#8117) --- .../lib/components/OnBehalfOfSelector.svelte | 24 ++++-- .../src/lib/components/ScriptBuilder.svelte | 75 ++++++++++------- .../apps/editor/AppEditorHeaderDeploy.svelte | 57 +++++++++---- .../flows/content/FlowSettings.svelte | 82 ++++++++++++------- 4 files changed, 155 insertions(+), 83 deletions(-) diff --git a/frontend/src/lib/components/OnBehalfOfSelector.svelte b/frontend/src/lib/components/OnBehalfOfSelector.svelte index 56296b8946..9c9cde8060 100644 --- a/frontend/src/lib/components/OnBehalfOfSelector.svelte +++ b/frontend/src/lib/components/OnBehalfOfSelector.svelte @@ -26,15 +26,25 @@ targetWorkspace: string targetEmail: string | undefined selected: OnBehalfOfChoice - onSelect: (choice: OnBehalfOfChoice, email?: string) => void + onSelect: (choice: OnBehalfOfChoice, email?: string, username?: string) => void kind: string canPreserve: boolean /** The email of the custom-selected user (for display) */ customEmail?: string | undefined + /** When false, labels say "current" instead of "target" and modal text refers to "this workspace" */ + isDeployment?: boolean } - let { targetWorkspace, targetEmail, selected, onSelect, kind, canPreserve, customEmail }: Props = - $props() + let { + targetWorkspace, + targetEmail, + selected, + onSelect, + kind, + canPreserve, + customEmail, + isDeployment = true + }: Props = $props() let label = $derived( kind === 'trigger' @@ -96,7 +106,7 @@ } function selectUser(user: User) { - onSelect('custom', user.email) + onSelect('custom', user.email, user.username) modalOpen = false } @@ -130,7 +140,7 @@ > {targetUsername} - (target) + {isDeployment ? '(target)' : '(current)'} {/if} @@ -171,9 +181,9 @@
{#if kind === 'trigger'} - Choose the user this trigger will be recorded as edited by in the target workspace. + Choose the user this trigger will be recorded as edited by {isDeployment ? 'in the target workspace' : 'in this workspace'}. {:else} - Choose the user this {kind} will run on behalf of in the target workspace. The selected + Choose the user this {kind} will run on behalf of {isDeployment ? 'in the target workspace' : 'in this workspace'}. The selected user's permissions will be used when executing. {/if} In this mode, the script is meant to be run on dedicated workers that run the script at native speed. Can reach >1500rps per dedicated worker. Only - available on enterprise edition and for Python3, Deno, Bun and Bunnative. For other - languages, the efficiency is already on par with dedicated workers since - they do not spawn a full runtime {/snippet} @@ -1597,30 +1595,51 @@ {/snippet}
- { - if (script.on_behalf_of_email) { - script.on_behalf_of_email = undefined - preserveOnBehalfOf = false - } else { - script.on_behalf_of_email = $userStore?.email - } - }} - options={{ - right: 'Run on behalf of last editor' - }} - /> - {#if showPreserveToggle} + { + if (script.on_behalf_of_email) { + script.on_behalf_of_email = undefined + preserveOnBehalfOf = false + onBehalfOfChoice = undefined + } else { + script.on_behalf_of_email = $userStore?.email + } + }} options={{ - right: `Keep original author (${originalOnBehalfOfEmail})` + right: `Run on behalf of ${canPreserve ? 'a specified user' : 'last editor'}` }} /> - {/if} + {#if script.on_behalf_of_email && canPreserve} + → { + onBehalfOfChoice = choice + if (choice === 'me') { + script.on_behalf_of_email = $userStore?.email + customOnBehalfOfEmail = '' + preserveOnBehalfOf = false + } else if (choice === 'target') { + script.on_behalf_of_email = originalOnBehalfOfEmail + customOnBehalfOfEmail = '' + preserveOnBehalfOf = true + } else if (choice === 'custom' && email) { + script.on_behalf_of_email = email + customOnBehalfOfEmail = email + preserveOnBehalfOf = true + } + }} + kind="script" + {canPreserve} + customEmail={customOnBehalfOfEmail} + isDeployment={false} + /> + {/if} +
{#if !isCloudHosted()} diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte index 04ac40ccb8..5d5484a544 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte @@ -15,6 +15,9 @@ import { isCloudHosted } from '$lib/cloud' import EEOnly from '$lib/components/EEOnly.svelte' import TextInput from '$lib/components/text_input/TextInput.svelte' + import OnBehalfOfSelector, { + type OnBehalfOfChoice + } from '$lib/components/OnBehalfOfSelector.svelte' const WM_DEPLOYERS_GROUP = 'wm_deployers' @@ -51,13 +54,12 @@ } = $props() let isDeployer = $derived($userStore?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false) - let savedOnBehalfOfEmail = $derived(savedApp?.policy?.on_behalf_of_email) - let showPreserveToggle = $derived( - isDeployer && - policy?.on_behalf_of_email && - savedOnBehalfOfEmail && - savedOnBehalfOfEmail !== $userStore?.email + let canPreserve = $derived( + !!$userStore?.is_admin || !!$userStore?.is_super_admin || isDeployer ) + let savedOnBehalfOfEmail = $derived(savedApp?.policy?.on_behalf_of_email) + let onBehalfOfChoice: OnBehalfOfChoice = $state(undefined) + let customOnBehalfOfEmail: string = $state('') let dirtyCustomPath = $state(false) let path: Path | undefined = $state(undefined) @@ -193,19 +195,40 @@ Hence, users will only be able to use the app as intended by the publisher without risk for leaking resources not used in the app. + {#if canPreserve} +
+ Because you are either an admin or part of the {WM_DEPLOYERS_GROUP} group, you can select another user to run this app on behalf of. Once deployed the app will be run on behalf of + { + onBehalfOfChoice = choice + if (choice === 'me') { + policy.on_behalf_of_email = $userStore?.email + policy.on_behalf_of = `u/${$userStore?.username}` + customOnBehalfOfEmail = '' + preserveOnBehalfOf = false + } else if (choice === 'target') { + policy.on_behalf_of_email = savedOnBehalfOfEmail + customOnBehalfOfEmail = '' + preserveOnBehalfOf = true + } else if (choice === 'custom' && email) { + policy.on_behalf_of_email = email + policy.on_behalf_of = username ? `u/${username}` : undefined + customOnBehalfOfEmail = email + preserveOnBehalfOf = true + } + }} + kind="app" + {canPreserve} + customEmail={customOnBehalfOfEmail} + isDeployment={false} + /> +
+ {/if} -{#if showPreserveToggle} -
- -
-{/if}
diff --git a/frontend/src/lib/components/flows/content/FlowSettings.svelte b/frontend/src/lib/components/flows/content/FlowSettings.svelte index 6a9c7f918a..0520aef8c3 100644 --- a/frontend/src/lib/components/flows/content/FlowSettings.svelte +++ b/frontend/src/lib/components/flows/content/FlowSettings.svelte @@ -8,7 +8,7 @@ import { Alert, Button, SecondsInput } from '$lib/components/common' import { getContext } from 'svelte' import type { FlowEditorContext } from '../types' - import { enterpriseLicense, userStore } from '$lib/stores' + import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores' import { isCloudHosted } from '$lib/cloud' import Tooltip from '$lib/components/Tooltip.svelte' import SimpleEditor from '$lib/components/SimpleEditor.svelte' @@ -26,6 +26,9 @@ import { slide } from 'svelte/transition' import DebounceLimit from '../DebounceLimit.svelte' import EEOnly from '$lib/components/EEOnly.svelte' + import OnBehalfOfSelector, { + type OnBehalfOfChoice + } from '$lib/components/OnBehalfOfSelector.svelte' interface Props { noEditor: boolean @@ -46,12 +49,9 @@ const WM_DEPLOYERS_GROUP = 'wm_deployers' let isDeployer = $derived($userStore?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false) - let showPreserveToggle = $derived( - isDeployer && - flowStore.val.on_behalf_of_email && - $savedOnBehalfOfEmail && - $savedOnBehalfOfEmail !== $userStore?.email - ) + let canPreserve = $derived(!!$userStore?.is_admin || !!$userStore?.is_super_admin || isDeployer) + let onBehalfOfChoice: OnBehalfOfChoice = $state(undefined) + let customOnBehalfOfEmail: string = $state('') function asSchema(x: any) { return x as Schema @@ -382,34 +382,54 @@ /> - { - if (flowStore.val.on_behalf_of_email) { - flowStore.val.on_behalf_of_email = undefined - $preserveOnBehalfOf = false - } else { - flowStore.val.on_behalf_of_email = $userStore?.email - } - }} - options={{ - right: 'Run on behalf of last editor', - rightTooltip: - 'When this option is enabled, the flow will be run with the permissions of the last editor.' - }} - /> - {#if showPreserveToggle} + { + if (flowStore.val.on_behalf_of_email) { + flowStore.val.on_behalf_of_email = undefined + $preserveOnBehalfOf = false + onBehalfOfChoice = undefined + } else { + flowStore.val.on_behalf_of_email = $userStore?.email + } + }} options={{ - right: `Keep original author (${$savedOnBehalfOfEmail})` + right: `Run on behalf of ${canPreserve ? 'a specified user' : 'last editor'}`, + rightTooltip: + 'When this option is enabled, the flow will be run with the permissions of the corresponding user.' }} /> - {/if} + {#if flowStore.val.on_behalf_of_email && canPreserve} + → { + onBehalfOfChoice = choice + if (choice === 'me') { + flowStore.val.on_behalf_of_email = $userStore?.email + customOnBehalfOfEmail = '' + $preserveOnBehalfOf = false + } else if (choice === 'target') { + flowStore.val.on_behalf_of_email = $savedOnBehalfOfEmail + customOnBehalfOfEmail = '' + $preserveOnBehalfOf = true + } else if (choice === 'custom' && email) { + flowStore.val.on_behalf_of_email = email + customOnBehalfOfEmail = email + $preserveOnBehalfOf = true + } + }} + kind="flow" + {canPreserve} + customEmail={customOnBehalfOfEmail} + isDeployment={false} + /> + {/if} +
@@ -581,8 +601,8 @@ {#if flowStore.val.dedicated_worker}
- A worker group needs to be configured to listen to this flow. Select it in the dedicated - workers section of the worker group configuration. + A worker group needs to be configured to listen to this flow. Select it in the + dedicated workers section of the worker group configuration.
{/if}