294 lines
9.1 KiB
Svelte
294 lines
9.1 KiB
Svelte
<script lang="ts">
|
|
import Toggle from '$lib/components/Toggle.svelte'
|
|
import Tooltip from '$lib/components/Tooltip.svelte'
|
|
import InputTransformForm from '$lib/components/InputTransformForm.svelte'
|
|
import type SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
|
import { getContext, tick } from 'svelte'
|
|
|
|
import { Alert, Tab, Tabs } from '$lib/components/common'
|
|
import { GroupService, type FlowModule } from '$lib/gen'
|
|
import { emptySchema, emptyString } from '$lib/utils'
|
|
import { enterpriseLicense, workspaceStore } from '$lib/stores.js'
|
|
import { SecondsInput } from '../../common'
|
|
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
|
|
import type { FlowEditorContext } from '../types'
|
|
import Section from '$lib/components/Section.svelte'
|
|
import Label from '$lib/components/Label.svelte'
|
|
import SuspendDrawer from './SuspendDrawer.svelte'
|
|
import EditableSchemaDrawer from '$lib/components/schema/EditableSchemaDrawer.svelte'
|
|
import AddProperty from '$lib/components/schema/AddProperty.svelte'
|
|
|
|
const { selectedId, flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
|
const result = $flowStateStore[$selectedId]?.previewResult ?? {}
|
|
let editor: SimpleEditor | undefined = undefined
|
|
|
|
export let flowModule: FlowModule
|
|
export let previousModuleId: string | undefined
|
|
|
|
let schema = emptySchema()
|
|
|
|
let allUserGroups: string[] = []
|
|
let suspendTabSelected: 'core' | 'form' | 'permissions' = 'core'
|
|
|
|
$: isSuspendEnabled = Boolean(flowModule.suspend)
|
|
|
|
async function loadGroups(): Promise<void> {
|
|
allUserGroups = await GroupService.listGroupNames({ workspace: $workspaceStore! })
|
|
schema.properties['groups'] = {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
enum: allUserGroups
|
|
}
|
|
}
|
|
}
|
|
|
|
$: {
|
|
if ($workspaceStore && allUserGroups.length === 0) {
|
|
loadGroups()
|
|
}
|
|
}
|
|
|
|
let jsonView: boolean = false
|
|
</script>
|
|
|
|
<Section label="Suspend/Approval/Prompt" class="w-full">
|
|
<svelte:fragment slot="action">
|
|
<SuspendDrawer text="Approval/Prompt helpers" />
|
|
</svelte:fragment>
|
|
<svelte:fragment slot="header">
|
|
<div class="flex flex-row items-center gap-2">
|
|
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/flow_approval">
|
|
If defined, at the end of the step, the flow will be suspended until it receives external
|
|
requests to be resumed or canceled. This is most useful to implement approval steps but can
|
|
be used flexibly for other purposes.
|
|
</Tooltip>
|
|
<Toggle
|
|
size="xs"
|
|
checked={isSuspendEnabled}
|
|
on:change={() => {
|
|
if (isSuspendEnabled && flowModule.suspend != undefined) {
|
|
flowModule.suspend = undefined
|
|
} else {
|
|
flowModule.suspend = {
|
|
required_events: 1,
|
|
timeout: 1800
|
|
}
|
|
}
|
|
}}
|
|
options={{
|
|
right: 'Suspend flow execution until events/approvals received'
|
|
}}
|
|
/>
|
|
</div>
|
|
</svelte:fragment>
|
|
|
|
<div class="overflow-x-auto scrollbar-hidden">
|
|
<Tabs bind:selected={suspendTabSelected}>
|
|
<Tab size="xs" value="core" disabled={!isSuspendEnabled}>
|
|
<div class="flex gap-2 items-center my-1">Core</div>
|
|
</Tab>
|
|
<Tab size="xs" value="form" disabled={!isSuspendEnabled}>
|
|
<div class="flex gap-2 items-center my-1">Form</div>
|
|
</Tab>
|
|
<Tab size="xs" value="permissions" disabled={!isSuspendEnabled}>
|
|
<div class="flex gap-2 items-center my-1">Permissions</div>
|
|
</Tab>
|
|
</Tabs>
|
|
</div>
|
|
|
|
{#if suspendTabSelected === 'core'}
|
|
<div class="flex flex-col mt-4 gap-4">
|
|
<Label label="Number of approvals/events required for resuming flow">
|
|
{#if flowModule.suspend}
|
|
<input
|
|
bind:value={flowModule.suspend.required_events}
|
|
type="number"
|
|
min="1"
|
|
placeholder="1"
|
|
/>
|
|
{:else}
|
|
<input type="number" disabled />
|
|
{/if}
|
|
</Label>
|
|
<Label label="Timeout">
|
|
{#if flowModule.suspend}
|
|
<SecondsInput bind:seconds={flowModule.suspend.timeout} />
|
|
{:else}
|
|
<SecondsInput disabled />
|
|
{/if}
|
|
</Label>
|
|
|
|
<Toggle
|
|
options={{
|
|
right: 'Continue on disapproval/timeout',
|
|
rightTooltip:
|
|
'Instead of failing the flow and bubbling up the error, continue to the next step which would allow to put a branchone right after to handle both cases separately. If any disapproval/timeout event is received, the resume payload will be similar to every error result in Winmdill, an object containing an `error` field which you can use to distinguish between approvals and disapproval/timeouts'
|
|
}}
|
|
checked={Boolean(flowModule.suspend?.continue_on_disapprove_timeout)}
|
|
disabled={!Boolean(flowModule.suspend)}
|
|
on:change={(e) => {
|
|
if (flowModule.suspend) {
|
|
flowModule.suspend.continue_on_disapprove_timeout = e.detail
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
{:else if suspendTabSelected === 'permissions'}
|
|
<div class="flex flex-col mt-4 gap-4">
|
|
{#if emptyString($enterpriseLicense)}
|
|
<Alert type="warning" title="Editing permissions is only available in enterprise version" />
|
|
{/if}
|
|
{#if flowModule.suspend}
|
|
<div class="flex flex-col gap-2">
|
|
<Toggle
|
|
disabled={emptyString($enterpriseLicense)}
|
|
checked={Boolean(flowModule.suspend.user_auth_required)}
|
|
options={{
|
|
right: 'Require approvers to be logged in'
|
|
}}
|
|
on:change={(e) => {
|
|
if (flowModule.suspend) {
|
|
flowModule.suspend.user_auth_required = e.detail
|
|
if (e.detail && flowModule.suspend?.user_groups_required === undefined) {
|
|
flowModule.suspend.user_groups_required = {
|
|
type: 'static',
|
|
value: []
|
|
}
|
|
} else if (!e.detail) {
|
|
flowModule.suspend.user_groups_required = undefined
|
|
flowModule.suspend.self_approval_disabled = false
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
|
|
<Toggle
|
|
options={{
|
|
right: 'Disable self-approval',
|
|
rightTooltip: 'The user who triggered the flow will not be allowed to approve it'
|
|
}}
|
|
checked={Boolean(flowModule.suspend.self_approval_disabled)}
|
|
disabled={!Boolean(flowModule.suspend.user_auth_required)}
|
|
on:change={(e) => {
|
|
if (flowModule.suspend) {
|
|
flowModule.suspend.self_approval_disabled = e.detail
|
|
}
|
|
}}
|
|
/>
|
|
|
|
<div class="mb-4" />
|
|
|
|
{#if Boolean(flowModule.suspend.user_auth_required) && allUserGroups.length !== 0 && flowModule.suspend && schema.properties['groups']}
|
|
<span class="text-xs font-bold"
|
|
>Require approvers to be members of one of the following user groups (leave empty for
|
|
any)
|
|
</span>
|
|
<div class="border">
|
|
<PropPickerWrapper
|
|
{result}
|
|
noFlowPlugConnect
|
|
displayContext={false}
|
|
pickableProperties={undefined}
|
|
on:select={({ detail }) => {
|
|
editor?.insertAtCursor(detail)
|
|
editor?.focus()
|
|
}}
|
|
>
|
|
<InputTransformForm
|
|
class="min-h-[256px] items-start"
|
|
bind:arg={flowModule.suspend.user_groups_required}
|
|
argName="groups"
|
|
{schema}
|
|
{previousModuleId}
|
|
bind:editor
|
|
/>
|
|
</PropPickerWrapper>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<div class="grid grid-cols-4 mt-4 gap-8">
|
|
<div class="col-span-2">
|
|
{#if flowModule?.suspend?.resume_form}
|
|
<EditableSchemaDrawer
|
|
bind:schema={flowModule.suspend.resume_form.schema}
|
|
on:change={(e) => {
|
|
const schema = e.detail
|
|
|
|
// If the schema is empty, remove the form
|
|
if (Object.keys(schema?.properties ?? {}).length === 0) {
|
|
tick().then(() => {
|
|
if (!flowModule.suspend) return
|
|
flowModule.suspend.resume_form = undefined
|
|
})
|
|
}
|
|
}}
|
|
{jsonView}
|
|
/>
|
|
{:else if emptyString($enterpriseLicense)}
|
|
<Alert type="warning" title="Adding a form to the approval page is an EE feature" />
|
|
{:else}
|
|
<div class="flex flex-col items-end mb-2 w-full">
|
|
<Toggle
|
|
checked={false}
|
|
label="JSON View"
|
|
size="xs"
|
|
options={{
|
|
right: 'JSON Editor',
|
|
rightTooltip:
|
|
'Arguments can be edited either using the wizard, or by editing their JSON Schema.'
|
|
}}
|
|
lightMode
|
|
on:change={() => {
|
|
if (flowModule.suspend) {
|
|
flowModule.suspend.resume_form = {
|
|
schema: emptySchema()
|
|
}
|
|
}
|
|
jsonView = true
|
|
}}
|
|
/>
|
|
</div>
|
|
<AddProperty
|
|
on:change={(e) => {
|
|
jsonView = false
|
|
if (flowModule.suspend) {
|
|
flowModule.suspend.resume_form = {
|
|
schema: e.detail
|
|
}
|
|
}
|
|
}}
|
|
schema={{}}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
<div class="col-span-2 flex flex-col gap-4">
|
|
{#if flowModule.suspend}
|
|
{#if emptyString($enterpriseLicense)}
|
|
<Alert type="warning" title="Adding a form to the approval page is an EE feature" />
|
|
{/if}
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<div class="flex">
|
|
<SuspendDrawer text="Default args & Dynamic enums help" />
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{#if flowModule.suspend}
|
|
<Toggle
|
|
bind:checked={flowModule.suspend.hide_cancel}
|
|
size="xs"
|
|
options={{
|
|
right: 'Hide cancel button on approval page'
|
|
}}
|
|
disabled={!Boolean(flowModule?.suspend?.resume_form)}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</Section>
|