mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
feat(frontend): Migrate flow advanced settings to new layout (#2589)
* feat(frontend): wip * feat(frontend): migrate flow advanced settings to new layout --------- Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
export let tooltip: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="w-full">
|
||||
<div class="flex flex-row justify-between">
|
||||
<h2 class="text-base font-semibold mb-2 flex flex-row gap-1">
|
||||
{label}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
|
||||
@@ -10,40 +12,40 @@
|
||||
$: isCacheEnabled = Boolean(flowModule.cache_ttl)
|
||||
</script>
|
||||
|
||||
<h2 class="pb-4">
|
||||
Cache
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/cache">
|
||||
If defined, the result of the step will be cached for the number of seconds defined such that if
|
||||
this step were to be re-triggered with the same input it would retrieve and return its cached
|
||||
value instead of recomputing it.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
{#if flowModule.value.type != 'rawscript'}
|
||||
<p
|
||||
>The cache settings need to be set in the referenced script/flow settings directly. Cache for
|
||||
hub scripts is not available yet.</p
|
||||
>
|
||||
{:else}
|
||||
<Toggle
|
||||
checked={isCacheEnabled}
|
||||
on:change={() => {
|
||||
if (isCacheEnabled && flowModule.cache_ttl != undefined) {
|
||||
flowModule.cache_ttl = undefined
|
||||
} else {
|
||||
flowModule.cache_ttl = 600
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Cache the results for each possible inputs'
|
||||
}}
|
||||
/>
|
||||
<div class="mb-4">
|
||||
<span class="text-xs font-bold">How long to keep cache valid</span>
|
||||
<Section label="Cache" class="flex flex-col gap-4">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/cache">
|
||||
If defined, the result of the step will be cached for the number of seconds defined such that
|
||||
if this step were to be re-triggered with the same input it would retrieve and return its
|
||||
cached value instead of recomputing it.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if flowModule.cache_ttl}
|
||||
<SecondsInput bind:seconds={flowModule.cache_ttl} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if flowModule.value.type != 'rawscript'}
|
||||
<p
|
||||
>The cache settings need to be set in the referenced script/flow settings directly. Cache for
|
||||
hub scripts is not available yet.</p
|
||||
>
|
||||
{:else}
|
||||
<Toggle
|
||||
checked={isCacheEnabled}
|
||||
on:change={() => {
|
||||
if (isCacheEnabled && flowModule.cache_ttl != undefined) {
|
||||
flowModule.cache_ttl = undefined
|
||||
} else {
|
||||
flowModule.cache_ttl = 600
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Cache the results for each possible inputs'
|
||||
}}
|
||||
/>
|
||||
<Label label="How long to keep cache valid">
|
||||
{#if flowModule.cache_ttl}
|
||||
<SecondsInput bind:seconds={flowModule.cache_ttl} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</Label>
|
||||
{/if}
|
||||
</Section>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { getContext } from 'svelte'
|
||||
import { NEVER_TESTED_THIS_FAR } from '../models'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
|
||||
const { flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
@@ -15,72 +16,75 @@
|
||||
let editor: SimpleEditor | undefined = undefined
|
||||
|
||||
$: isStopAfterIfEnabled = Boolean(flowModule.stop_after_if)
|
||||
|
||||
$: result = $flowStateStore[flowModule.id]?.previewResult ?? NEVER_TESTED_THIS_FAR
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-start space-y-2 {$$props.class}">
|
||||
<h2 class="pb-4">
|
||||
Early stop/Break
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/early_stop">
|
||||
If defined, at the end of the step, the predicate expression will be evaluated to decide if
|
||||
the flow should stop early.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<Toggle
|
||||
checked={isStopAfterIfEnabled}
|
||||
on:change={() => {
|
||||
if (isStopAfterIfEnabled && flowModule.stop_after_if) {
|
||||
flowModule.stop_after_if = undefined
|
||||
} else {
|
||||
flowModule.stop_after_if = {
|
||||
expr: 'result == undefined',
|
||||
skip_if_stopped: false
|
||||
}
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Early stop or Break if condition met'
|
||||
}}
|
||||
/>
|
||||
<Section label="Early stop/Break" class="w-full">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/early_stop">
|
||||
If defined, at the end of the step, the predicate expression will be evaluated to decide if
|
||||
the flow should stop early.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<div
|
||||
class="w-full border p-2 flex flex-col {flowModule.stop_after_if ? '' : 'bg-surface-secondary'}"
|
||||
>
|
||||
{#if flowModule.stop_after_if}
|
||||
<Toggle
|
||||
bind:checked={flowModule.stop_after_if.skip_if_stopped}
|
||||
options={{
|
||||
right: 'Label flow as "skipped" if stopped'
|
||||
}}
|
||||
/>
|
||||
<span class="text-xs font-bold">Stop condition expression</span>
|
||||
<div class="border w-full">
|
||||
<PropPickerWrapper
|
||||
{result}
|
||||
pickableProperties={undefined}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
<Toggle
|
||||
checked={isStopAfterIfEnabled}
|
||||
on:change={() => {
|
||||
if (isStopAfterIfEnabled && flowModule.stop_after_if) {
|
||||
flowModule.stop_after_if = undefined
|
||||
} else {
|
||||
flowModule.stop_after_if = {
|
||||
expr: 'result == undefined',
|
||||
skip_if_stopped: false
|
||||
}
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Early stop or Break if condition met'
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="w-full border p-2 flex flex-col {flowModule.stop_after_if
|
||||
? ''
|
||||
: 'bg-surface-secondary'}"
|
||||
>
|
||||
{#if flowModule.stop_after_if}
|
||||
<Toggle
|
||||
bind:checked={flowModule.stop_after_if.skip_if_stopped}
|
||||
options={{
|
||||
right: 'Label flow as "skipped" if stopped'
|
||||
}}
|
||||
>
|
||||
<SimpleEditor
|
||||
bind:this={editor}
|
||||
lang="javascript"
|
||||
bind:code={flowModule.stop_after_if.expr}
|
||||
class="small-editor"
|
||||
extraLib={`declare const result = ${JSON.stringify(result)};`}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
{:else}
|
||||
<Toggle
|
||||
disabled
|
||||
options={{
|
||||
right: 'Label flow as "skipped" if stopped'
|
||||
}}
|
||||
/> <span class="text-xs font-bold">Stop condition expression</span>
|
||||
<textarea disabled rows="3" class="min-h-[80px]" />
|
||||
{/if}
|
||||
</div>
|
||||
/>
|
||||
<span class="text-xs font-bold">Stop condition expression</span>
|
||||
<div class="border w-full">
|
||||
<PropPickerWrapper
|
||||
{result}
|
||||
pickableProperties={undefined}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
}}
|
||||
>
|
||||
<SimpleEditor
|
||||
bind:this={editor}
|
||||
lang="javascript"
|
||||
bind:code={flowModule.stop_after_if.expr}
|
||||
class="few-lines-editor"
|
||||
extraLib={`declare const result = ${JSON.stringify(result)};`}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
{:else}
|
||||
<Toggle
|
||||
disabled
|
||||
options={{
|
||||
right: 'Label flow as "skipped" if stopped'
|
||||
}}
|
||||
/> <span class="text-xs font-bold">Stop condition expression</span>
|
||||
<textarea disabled rows="3" class="min-h-[80px]" />
|
||||
{/if}
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
|
||||
import JsonEditor from '$lib/components/apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
|
||||
export let flowModule: FlowModule
|
||||
|
||||
@@ -14,43 +15,45 @@
|
||||
$: isMockEnabled = Boolean(flowModule.mock?.enabled)
|
||||
</script>
|
||||
|
||||
<h2 class="pb-4">
|
||||
Mock
|
||||
<Tooltip>
|
||||
If defined and enabled, the step will immediately return the mock value instead of being
|
||||
executed.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<Toggle
|
||||
checked={isMockEnabled}
|
||||
on:change={() => {
|
||||
if (isMockEnabled) {
|
||||
flowModule.mock = {
|
||||
enabled: false,
|
||||
return_value: flowModule.mock?.return_value
|
||||
<Section label="Mock">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip>
|
||||
If defined and enabled, the step will immediately return the mock value instead of being
|
||||
executed.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<Toggle
|
||||
checked={isMockEnabled}
|
||||
on:change={() => {
|
||||
if (isMockEnabled) {
|
||||
flowModule.mock = {
|
||||
enabled: false,
|
||||
return_value: flowModule.mock?.return_value
|
||||
}
|
||||
} else {
|
||||
flowModule.mock = {
|
||||
enabled: true,
|
||||
return_value: flowModule.mock?.return_value ?? { example: 'value' }
|
||||
}
|
||||
code = JSON.stringify(flowModule.mock?.return_value, null, 2)
|
||||
}
|
||||
} else {
|
||||
flowModule.mock = {
|
||||
enabled: true,
|
||||
return_value: flowModule.mock?.return_value ?? { example: 'value' }
|
||||
}
|
||||
code = JSON.stringify(flowModule.mock?.return_value, null, 2)
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Enable step mocking'
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<span class="text-xs font-bold">Mocked Return value</span>
|
||||
{#if flowModule?.mock?.return_value != undefined}
|
||||
<JsonEditor {code} bind:value={flowModule.mock.return_value} />
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
disabled
|
||||
value={flowModule.mock?.return_value ? code : ''}
|
||||
class="w-full p-2 border rounded-md"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
}}
|
||||
options={{
|
||||
right: 'Enable step mocking'
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<span class="text-xs font-bold">Mocked Return value</span>
|
||||
{#if flowModule?.mock?.return_value != undefined}
|
||||
<JsonEditor {code} bind:value={flowModule.mock.return_value} />
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
disabled
|
||||
value={flowModule.mock?.return_value ? code : ''}
|
||||
class="w-full p-2 border rounded-md"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { SecondsInput } from '../../common'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
|
||||
export let flowModule: FlowModule
|
||||
export let previousModuleId: string | undefined
|
||||
@@ -27,53 +29,54 @@
|
||||
$: isSleepEnabled = Boolean(flowModule.sleep)
|
||||
</script>
|
||||
|
||||
<h2 class="pb-4">
|
||||
Sleep
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/sleep">
|
||||
If defined, at the end of the step, the flow will sleep for a number of seconds before
|
||||
scheduling the next job (if any, no effect if the step is the last one).
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<Toggle
|
||||
checked={isSleepEnabled}
|
||||
on:change={() => {
|
||||
if (isSleepEnabled && flowModule.sleep != undefined) {
|
||||
flowModule.sleep = undefined
|
||||
} else {
|
||||
flowModule.sleep = {
|
||||
type: 'static',
|
||||
value: 0
|
||||
<Section label="Sleep" class="w-full">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/sleep">
|
||||
If defined, at the end of the step, the flow will sleep for a number of seconds before
|
||||
scheduling the next job (if any, no effect if the step is the last one).
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<Toggle
|
||||
checked={isSleepEnabled}
|
||||
on:change={() => {
|
||||
if (isSleepEnabled && flowModule.sleep != undefined) {
|
||||
flowModule.sleep = undefined
|
||||
} else {
|
||||
flowModule.sleep = {
|
||||
type: 'static',
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Sleep after module successful execution'
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<span class="text-xs font-bold">Sleep for duration</span>
|
||||
{#if flowModule.sleep && schema.properties['sleep']}
|
||||
<div class="border">
|
||||
<PropPickerWrapper
|
||||
notSelectable
|
||||
{result}
|
||||
displayContext={false}
|
||||
pickableProperties={undefined}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
}}
|
||||
>
|
||||
<InputTransformForm
|
||||
bind:arg={flowModule.sleep}
|
||||
argName="sleep"
|
||||
{schema}
|
||||
{previousModuleId}
|
||||
argExtra={{ seconds: true }}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</div>
|
||||
}}
|
||||
options={{
|
||||
right: 'Sleep after module successful execution'
|
||||
}}
|
||||
/>
|
||||
<Label label="Sleep for duration">
|
||||
{#if flowModule.sleep && schema.properties['sleep']}
|
||||
<div class="border">
|
||||
<PropPickerWrapper
|
||||
notSelectable
|
||||
{result}
|
||||
displayContext={false}
|
||||
pickableProperties={undefined}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
editor?.focus()
|
||||
}}
|
||||
>
|
||||
<InputTransformForm
|
||||
bind:arg={flowModule.sleep}
|
||||
argName="sleep"
|
||||
{schema}
|
||||
{previousModuleId}
|
||||
argExtra={{ seconds: true }}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</Label>
|
||||
</Section>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
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'
|
||||
|
||||
const { selectedId, flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const result = $flowStateStore[$selectedId]?.previewResult ?? {}
|
||||
@@ -47,165 +49,166 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<h2 class="pb-4">
|
||||
Suspend/Approval
|
||||
<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 purpose.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<Section label="Suspend/Approval" class="w-full">
|
||||
<svelte:fragment slot="header">
|
||||
<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 purpose.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<Toggle
|
||||
checked={isSuspendEnabled}
|
||||
on:change={() => {
|
||||
if (isSuspendEnabled && flowModule.suspend != undefined) {
|
||||
flowModule.suspend = undefined
|
||||
} else {
|
||||
flowModule.suspend = {
|
||||
required_events: 1,
|
||||
timeout: 1800
|
||||
<Toggle
|
||||
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'
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
options={{
|
||||
right: 'Suspend flow execution until events/approvals received'
|
||||
}}
|
||||
/>
|
||||
|
||||
<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">
|
||||
<span class="text-xs font-bold">Number of approvals/events required for resuming flow</span>
|
||||
|
||||
{#if flowModule.suspend}
|
||||
<input
|
||||
bind:value={flowModule.suspend.required_events}
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="1"
|
||||
/>
|
||||
{:else}
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
|
||||
<span class="text-xs font-bold">Timeout</span>
|
||||
|
||||
{#if flowModule.suspend}
|
||||
<SecondsInput bind:seconds={flowModule.suspend.timeout} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
<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>
|
||||
{: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">
|
||||
|
||||
{#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>
|
||||
</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
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="mb-4" />
|
||||
|
||||
<span class="text-xs font-bold"
|
||||
>Require approvers to be members of one of the following user groups (leave empty for
|
||||
any)
|
||||
</span>
|
||||
{#if allUserGroups.length !== 0 && flowModule.suspend && schema.properties['groups']}
|
||||
<div class="border">
|
||||
<PropPickerWrapper
|
||||
{result}
|
||||
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}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col mt-4 gap-4">
|
||||
{#if flowModule.suspend}
|
||||
<Toggle
|
||||
disabled={emptyString($enterpriseLicense)}
|
||||
checked={Boolean(flowModule.suspend.user_auth_required)}
|
||||
checked={Boolean(flowModule.suspend.resume_form)}
|
||||
options={{
|
||||
right: 'Require approvers to be logged in'
|
||||
right: 'Add a form to the approval page'
|
||||
}}
|
||||
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: []
|
||||
if (e.detail) {
|
||||
flowModule.suspend.resume_form = {
|
||||
schema: emptySchema()
|
||||
}
|
||||
} else if (!e.detail) {
|
||||
flowModule.suspend.user_groups_required = undefined
|
||||
} else {
|
||||
flowModule.suspend.resume_form = undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="mb-4" />
|
||||
|
||||
<span class="text-xs font-bold"
|
||||
>Require approvers to be members of one of the following user groups (leave empty for any)
|
||||
</span>
|
||||
{#if allUserGroups.length !== 0 && flowModule.suspend && schema.properties['groups']}
|
||||
<div class="border">
|
||||
<PropPickerWrapper
|
||||
{result}
|
||||
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}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col mt-4 gap-4">
|
||||
{#if flowModule.suspend}
|
||||
<Toggle
|
||||
checked={Boolean(flowModule.suspend.resume_form)}
|
||||
options={{
|
||||
right: 'Add a form to the approval page'
|
||||
}}
|
||||
on:change={(e) => {
|
||||
if (flowModule.suspend) {
|
||||
if (e.detail) {
|
||||
flowModule.suspend.resume_form = {
|
||||
schema: emptySchema()
|
||||
}
|
||||
} else {
|
||||
flowModule.suspend.resume_form = undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<Slider size="xs" text="How to add dynamic default args">
|
||||
As one of the return key of this step, return an object `default_args` that contains the
|
||||
default arguments of the form arguments. e.g:
|
||||
<pre
|
||||
><code
|
||||
>{`return {
|
||||
<div>
|
||||
<Slider size="xs" text="How to add dynamic default args">
|
||||
As one of the return key of this step, return an object `default_args` that contains the
|
||||
default arguments of the form arguments. e.g:
|
||||
<pre
|
||||
><code
|
||||
>{`return {
|
||||
endpoints,
|
||||
default_args: {
|
||||
foo: "foo",
|
||||
bar: true,
|
||||
},
|
||||
}`}</code
|
||||
></pre
|
||||
>
|
||||
</Slider></div
|
||||
>
|
||||
{/if}
|
||||
{#if flowModule.suspend?.resume_form}
|
||||
<SchemaEditor bind:schema={flowModule.suspend.resume_form.schema} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
></pre
|
||||
>
|
||||
</Slider></div
|
||||
>
|
||||
{/if}
|
||||
{#if flowModule.suspend?.resume_form}
|
||||
<SchemaEditor bind:schema={flowModule.suspend.resume_form.schema} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Section>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
|
||||
@@ -10,39 +11,40 @@
|
||||
$: istimeoutEnabled = Boolean(flowModule.timeout)
|
||||
</script>
|
||||
|
||||
<h2 class="pb-4">
|
||||
Timeout
|
||||
<Tooltip>
|
||||
If defined, the custom timeout will be used instead of the instance timeout for the step. The
|
||||
step's timeout cannot be greater than the instance timeout.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<Section label="Timeout">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip>
|
||||
If defined, the custom timeout will be used instead of the instance timeout for the step. The
|
||||
step's timeout cannot be greater than the instance timeout.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<Toggle
|
||||
checked={istimeoutEnabled}
|
||||
on:change={() => {
|
||||
if (istimeoutEnabled && flowModule.timeout != undefined) {
|
||||
flowModule.timeout = undefined
|
||||
} else {
|
||||
flowModule.timeout = 300
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Add a custom timeout for this step'
|
||||
}}
|
||||
/>
|
||||
<div class="mb-4">
|
||||
<span class="text-xs font-bold">Timeout duration</span>
|
||||
<Toggle
|
||||
checked={istimeoutEnabled}
|
||||
on:change={() => {
|
||||
if (istimeoutEnabled && flowModule.timeout != undefined) {
|
||||
flowModule.timeout = undefined
|
||||
} else {
|
||||
flowModule.timeout = 300
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Add a custom timeout for this step'
|
||||
}}
|
||||
/>
|
||||
<div class="mb-4">
|
||||
<span class="text-xs font-bold">Timeout duration</span>
|
||||
|
||||
{#if flowModule.timeout}
|
||||
<SecondsInput bind:seconds={flowModule.timeout} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
{#if flowModule.timeout}
|
||||
<SecondsInput bind:seconds={flowModule.timeout} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
|
||||
<div class="mt-4" />
|
||||
<div class="mt-4" />
|
||||
|
||||
<Alert title="Only used when testing the full flow" type="info">
|
||||
<p class="text-sm"> The timeout will be ignored when running "Test this step" </p>
|
||||
</Alert>
|
||||
</div>
|
||||
<Alert title="Only used when testing the full flow" type="info">
|
||||
<p class="text-sm"> The timeout will be ignored when running "Test this step" </p>
|
||||
</Alert>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { SecondsInput } from '$lib/components/common'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
|
||||
export let flowModule: FlowModule
|
||||
|
||||
@@ -32,123 +33,124 @@
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col {$$props.class ?? ''}">
|
||||
<h2>
|
||||
Retries
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/retries">
|
||||
If defined, upon error this step will be retried with a delay and a maximum number of attempts
|
||||
as defined below.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<Section label="Retries">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/retries">
|
||||
If defined, upon error this step will be retried with a delay and a maximum number of
|
||||
attempts as defined below.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex h-[calc(100%-22px)]">
|
||||
<div class="w-1/2 h-full overflow-auto pr-2">
|
||||
<div class="pt-4">
|
||||
<Toggle
|
||||
checked={isConstantRetryEnabled}
|
||||
on:change={() => {
|
||||
if (isConstantRetryEnabled && flowModule.retry?.constant) {
|
||||
flowModule.retry.constant = undefined
|
||||
if (!isExponentialRetryEnabled) {
|
||||
flowModule.retry = undefined
|
||||
<div class="flex h-[calc(100%-22px)]">
|
||||
<div class="w-1/2 h-full overflow-auto pr-2">
|
||||
<div class="pt-4">
|
||||
<Toggle
|
||||
checked={isConstantRetryEnabled}
|
||||
on:change={() => {
|
||||
if (isConstantRetryEnabled && flowModule.retry?.constant) {
|
||||
flowModule.retry.constant = undefined
|
||||
if (!isExponentialRetryEnabled) {
|
||||
flowModule.retry = undefined
|
||||
}
|
||||
} else {
|
||||
setConstantRetries()
|
||||
}
|
||||
} else {
|
||||
setConstantRetries()
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Constant retry enabled'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if flowModule.retry?.constant}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input bind:value={flowModule.retry.constant.attempts} type="number" />
|
||||
<div class="text-xs font-bold !mt-2">Delay</div>
|
||||
<SecondsInput bind:seconds={flowModule.retry.constant.seconds} />
|
||||
{:else}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input type="number" disabled />
|
||||
<div class="text-xs font-bold !mt-2">Delay</div>
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
<div class="pt-6">
|
||||
<Toggle
|
||||
checked={isExponentialRetryEnabled}
|
||||
on:change={() => {
|
||||
if (isExponentialRetryEnabled && flowModule.retry?.exponential) {
|
||||
flowModule.retry.exponential = undefined
|
||||
if (!isConstantRetryEnabled) {
|
||||
flowModule.retry = undefined
|
||||
}
|
||||
} else {
|
||||
setExpoentialRetries()
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Exponential backoff enabled'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if flowModule.retry?.exponential}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input bind:value={flowModule.retry.exponential.attempts} type="number" />
|
||||
<div class="text-xs font-bold !mt-2">Mulitplier</div>
|
||||
<span class="text-xs text-tertiary">delay = multiplier * base ^ (number of attempt)</span>
|
||||
<input bind:value={flowModule.retry.exponential.multiplier} type="number" />
|
||||
<div class="text-xs font-bold !mt-2">Base (in seconds)</div>
|
||||
<input bind:value={flowModule.retry.exponential.seconds} type="number" step="1" />
|
||||
{:else}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input type="number" disabled />
|
||||
<div class="text-xs font-bold !mt-2">Mulitplier</div>
|
||||
<span class="text-xs text-tertiary">delay = multiplier * base ^ (number of attempt)</span>
|
||||
<input type="number" disabled />
|
||||
<div class="text-xs font-bold !mt-2">Base (in seconds)</div>
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-1/2 h-full overflow-auto pl-2">
|
||||
{#if true}
|
||||
{@const { attempts: cAttempts, seconds: cSeconds } = flowModule.retry?.constant || {}}
|
||||
{@const {
|
||||
attempts: eAttempts,
|
||||
seconds: eSeconds,
|
||||
multiplier
|
||||
} = flowModule.retry?.exponential || {}}
|
||||
{@const cArray = Array.from({ length: cAttempts || 0 }, () => cSeconds)}
|
||||
{@const eArray = Array.from(
|
||||
{ length: eAttempts || 0 },
|
||||
(_, i) => (multiplier || 0) * (eSeconds || 0) ** (i + cArray.length + 1)
|
||||
)}
|
||||
{@const array = [...cArray, ...eArray]}
|
||||
<div class="bg-surface-secondary border rounded px-4 py-2">
|
||||
<div class="text-xs font-medium mb-2">Retry attempts</div>
|
||||
{#if array.length > 0}
|
||||
<table class="text-xs">
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 pb-1">1:</td>
|
||||
<td class="pb-1">After {array[0]} second{array[0] === 1 ? '' : 's'}</td>
|
||||
</tr>
|
||||
{#each array.slice(1) as delay, i}
|
||||
{@const index = i + 2}
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 align-top">{index}:</td>
|
||||
<td class="pb-1 whitespace-nowrap">
|
||||
{delay} second{delay === 1 ? '' : 's'} after attempt #{index - 1}
|
||||
{#if i > cArray.length - 2}
|
||||
<span class="text-gray-400 pl-2">
|
||||
({multiplier} * {eSeconds}<sup>{index}</sup>)
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{:else}
|
||||
<div class="text-xs">No retries</div>
|
||||
{/if}
|
||||
}}
|
||||
options={{
|
||||
right: 'Constant retry enabled'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if flowModule.retry?.constant}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input bind:value={flowModule.retry.constant.attempts} type="number" />
|
||||
<div class="text-xs font-bold !mt-2">Delay</div>
|
||||
<SecondsInput bind:seconds={flowModule.retry.constant.seconds} />
|
||||
{:else}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input type="number" disabled />
|
||||
<div class="text-xs font-bold !mt-2">Delay</div>
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
<div class="pt-6">
|
||||
<Toggle
|
||||
checked={isExponentialRetryEnabled}
|
||||
on:change={() => {
|
||||
if (isExponentialRetryEnabled && flowModule.retry?.exponential) {
|
||||
flowModule.retry.exponential = undefined
|
||||
if (!isConstantRetryEnabled) {
|
||||
flowModule.retry = undefined
|
||||
}
|
||||
} else {
|
||||
setExpoentialRetries()
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Exponential backoff enabled'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if flowModule.retry?.exponential}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input bind:value={flowModule.retry.exponential.attempts} type="number" />
|
||||
<div class="text-xs font-bold !mt-2">Mulitplier</div>
|
||||
<span class="text-xs text-tertiary">delay = multiplier * base ^ (number of attempt)</span>
|
||||
<input bind:value={flowModule.retry.exponential.multiplier} type="number" />
|
||||
<div class="text-xs font-bold !mt-2">Base (in seconds)</div>
|
||||
<input bind:value={flowModule.retry.exponential.seconds} type="number" step="1" />
|
||||
{:else}
|
||||
<div class="text-xs font-bold !mt-2">Attempts</div>
|
||||
<input type="number" disabled />
|
||||
<div class="text-xs font-bold !mt-2">Mulitplier</div>
|
||||
<span class="text-xs text-tertiary">delay = multiplier * base ^ (number of attempt)</span>
|
||||
<input type="number" disabled />
|
||||
<div class="text-xs font-bold !mt-2">Base (in seconds)</div>
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-1/2 h-full overflow-auto pl-2">
|
||||
{#if true}
|
||||
{@const { attempts: cAttempts, seconds: cSeconds } = flowModule.retry?.constant || {}}
|
||||
{@const {
|
||||
attempts: eAttempts,
|
||||
seconds: eSeconds,
|
||||
multiplier
|
||||
} = flowModule.retry?.exponential || {}}
|
||||
{@const cArray = Array.from({ length: cAttempts || 0 }, () => cSeconds)}
|
||||
{@const eArray = Array.from(
|
||||
{ length: eAttempts || 0 },
|
||||
(_, i) => (multiplier || 0) * (eSeconds || 0) ** (i + cArray.length + 1)
|
||||
)}
|
||||
{@const array = [...cArray, ...eArray]}
|
||||
<div class="bg-surface-secondary border rounded px-4 py-2">
|
||||
<div class="text-xs font-medium mb-2">Retry attempts</div>
|
||||
{#if array.length > 0}
|
||||
<table class="text-xs">
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 pb-1">1:</td>
|
||||
<td class="pb-1">After {array[0]} second{array[0] === 1 ? '' : 's'}</td>
|
||||
</tr>
|
||||
{#each array.slice(1) as delay, i}
|
||||
{@const index = i + 2}
|
||||
<tr>
|
||||
<td class="font-semibold pr-1 align-top">{index}:</td>
|
||||
<td class="pb-1 whitespace-nowrap">
|
||||
{delay} second{delay === 1 ? '' : 's'} after attempt #{index - 1}
|
||||
{#if i > cArray.length - 2}
|
||||
<span class="text-gray-400 pl-2">
|
||||
({multiplier} * {eSeconds}<sup>{index}</sup>)
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{:else}
|
||||
<div class="text-xs">No retries</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user