fix: improve approval/prompt helpers

This commit is contained in:
Ruben Fiszel
2024-01-20 15:25:49 +01:00
parent 3250d1ea47
commit 9c344605ed
11 changed files with 300 additions and 102 deletions

View File

@@ -204,15 +204,14 @@
<div class="inline-highlight relative grow min-h-[200px]">
{#if result != undefined && length != undefined && largeObject != undefined}
{#if resultKind && !['json', 's3object', 's3object-list'].includes(resultKind)}
<div class="top-0 flex flex-row w-full justify-between items-center"
<div class="top-1 absolute flex flex-row w-full justify-between items-center"
><div class="mb-2 text-tertiary text-sm">
as JSON&nbsp;<input class="windmillapp" type="checkbox" bind:checked={forceJson} /></div
>
<slot name="copilot-fix" />
</div>
{/if}
{#if typeof result == 'object' && Object.keys(result).length > 0}
<div class="top-0 mb-2 w-full min-w-[400px] text-sm relative"
><slot name="copilot-fix" />
</div><div
class="py-3"
/>{/if}{#if typeof result == 'object' && Object.keys(result).length > 0}<div
class="top-1 mb-2 w-full min-w-[400px] text-sm absolute"
>{#if !disableExpand}
<div class="text-tertiary text-xs absolute top-5.5 right-0 inline-flex gap-2 z-10">
<button on:click={() => copyToClipboard(toJsonStr(result))}
@@ -231,11 +230,8 @@
{/if}
</div>
{/if}</div
>
{/if}
{#if !forceJson && resultKind == 'table-col'}
>{/if}{#if !forceJson && resultKind == 'table-col'}
{@const data = 'table-col' in result ? result['table-col'] : result}
<AutoDataTable objects={transform(data)} />
{:else if !forceJson && resultKind == 'table-row'}
{@const data = 'table-row' in result ? result['table-row'] : result}
@@ -329,7 +325,7 @@
<pre class="text-sm whitespace-pre-wrap text-primary">{result.error.stack ?? ''}</pre>
<slot />
</div>
{:else if !forceJson && resultKind == 'approval'}<div class="flex flex-col gap-3 mt-8 mx-4">
{:else if !forceJson && resultKind == 'approval'}<div class="flex flex-col gap-3 mt-2 mx-4">
<Button
color="green"
variant="border"

View File

@@ -9,6 +9,9 @@
import sql from 'svelte-highlight/languages/sql'
import powershell from 'svelte-highlight/languages/powershell'
import type { Script } from '$lib/gen'
import { Button } from './common'
import { copyToClipboard } from '$lib/utils'
import { ClipboardCopy } from 'lucide-svelte'
export let code: string = ''
export let language: Script.language | 'frontend' | undefined
@@ -51,16 +54,26 @@
$: lang = getLang(language)
</script>
{#if code?.length < 5000}
{#if !lines}
<Highlight class="nowrap {$$props.class}" language={lang} {code} />
{:else}
<Highlight class="nowrap {$$props.class}" language={lang} {code} let:highlighted>
<LineNumbers {highlighted} />
</Highlight>
{/if}
{:else}
<pre class="overflow-auto max-h-screen {$$props.class}"
><code class="language-{language}">{code}</code></pre
<div class="relative overflow-x-auto">
<Button
class="absolute top-2 right-2"
on:click={() => copyToClipboard(code)}
color="light"
size="xs"
>
{/if}
<ClipboardCopy size={12} />
</Button>
{#if code?.length < 5000}
{#if !lines}
<Highlight class="nowrap {$$props.class}" language={lang} {code} />
{:else}
<Highlight class="nowrap {$$props.class}" language={lang} {code} let:highlighted>
<LineNumbers {highlighted} />
</Highlight>
{/if}
{:else}
<pre class="overflow-auto max-h-screen {$$props.class}"
><code class="language-{language}">{code}</code></pre
>
{/if}
</div>

View File

@@ -13,7 +13,7 @@
<div class="w-full">
<div class="flex flex-row justify-between items-center mb-2">
<h2 class="text-base font-semibold flex flex-row gap-1">
<h2 class="text-base font-semibold flex flex-row items-center gap-1">
{#if collapsable}
<button class="flex items-center gap-1" on:click={() => (collapsed = !collapsed)}>
{#if collapsed}

View File

@@ -30,7 +30,14 @@
<SplitPanesWrapper>
<Splitpanes horizontal>
<Pane size={flowModule ? 60 : 100}>
<Alert notRounded type="info" title="All branches will be run" tooltip="Branch all" documentationLink="https://www.windmill.dev/docs/flows/flow_branches#branch-all" class="m-2">
<Alert
notRounded
type="info"
title="All branches will be run"
tooltip="Branch all"
documentationLink="https://www.windmill.dev/docs/flows/flow_branches#branch-all"
class="m-2"
>
The result of this step is the list of the result of each branch.
</Alert>
@@ -70,7 +77,7 @@
<Pane size={40}>
<Tabs bind:selected>
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="suspend">Suspend/Approval</Tab>
<Tab value="suspend">Suspend/Approval/Prompt</Tab>
<Tab value="sleep">Sleep</Tab>
<Tab value="mock">Mock</Tab>
<Tab value="lifetime">Lifetime</Tab>

View File

@@ -31,7 +31,13 @@
<SplitPanesWrapper>
<Splitpanes horizontal>
<Pane size={flowModule ? 60 : 100}>
<Alert type="info" title="Only first branch whose condition is true will be run" tooltip="Branch one" documentationLink="https://www.windmill.dev/docs/flows/flow_branches#branch-one" class="m-2">
<Alert
type="info"
title="Only first branch whose condition is true will be run"
tooltip="Branch one"
documentationLink="https://www.windmill.dev/docs/flows/flow_branches#branch-one"
class="m-2"
>
The result of this step is the result of the branch.
</Alert>
<div class="p-2">
@@ -70,7 +76,7 @@
<Pane size={40}>
<Tabs bind:selected>
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="suspend">Suspend/Approval</Tab>
<Tab value="suspend">Suspend/Approval/Prompt</Tab>
<Tab value="sleep">Sleep</Tab>
<Tab value="mock">Mock</Tab>
<Tab value="lifetime">Lifetime</Tab>

View File

@@ -13,6 +13,7 @@
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import { Check, Code, Zap } from 'lucide-svelte'
import SuspendDrawer from './SuspendDrawer.svelte'
export let failureModule: boolean
export let shouldDisableTriggerScripts: boolean = false
@@ -67,51 +68,80 @@
{#if kind == 'trigger'}
<div class="mt-2" />
<Alert title="Trigger scripts" role="info">
Trigger scripts are designed to pull data from an external source and return all of the new items since the last run, without resorting to external webhooks.<br/><br/>
Trigger scripts are designed to pull data from an external source and return all of the new
items since the last run, without resorting to external webhooks.<br /><br />
A trigger script is intended to be used with <a href="https://www.windmill.dev/docs/core_concepts/scheduling" target="_blank" class="text-blue-400">schedules</a> and <a href="https://www.windmill.dev/docs/core_concepts/resources_and_types#states" target="_blank" class="text-blue-400">states</a> in order to compare the execution to the previous one and process each new item in a <a href="https://www.windmill.dev/docs/flows/flow_loops" target="_blank" class="text-blue-400">for loop</a>. If there are no new items, the flow will be skipped.<br/><br/>
A trigger script is intended to be used with
<a
href="https://www.windmill.dev/docs/core_concepts/scheduling"
target="_blank"
class="text-blue-400">schedules</a
>
and
<a
href="https://www.windmill.dev/docs/core_concepts/resources_and_types#states"
target="_blank"
class="text-blue-400">states</a
>
in order to compare the execution to the previous one and process each new item in a
<a
href="https://www.windmill.dev/docs/flows/flow_loops"
target="_blank"
class="text-blue-400">for loop</a
>. If there are no new items, the flow will be skipped.<br /><br />
By default, adding a trigger will set the schedule to 15 minutes.
To see all ways to trigger a flow, check <a
By default, adding a trigger will set the schedule to 15 minutes. To see all ways to trigger
a flow, check
<a
href="https://www.windmill.dev/docs/getting_started/trigger_flows"
target="_blank"
class="text-blue-400"
>Triggering Flows</a>.
class="text-blue-400">Triggering Flows</a
>.
</Alert>
{/if}
{#if kind == 'script'}
<div class="mt-2" />
<Alert title="Action Scripts" role="info">
An action script is simply a script that is neither a trigger nor an approval script. Those are the majority of the scripts.
An action script is simply a script that is neither a trigger nor an approval script. Those
are the majority of the scripts.
</Alert>
{/if}
{#if kind == 'approval'}
<div class="mt-2" />
<Alert title="Approval Step" role="info">
An approval step will suspend the execution of a flow until it has been approved through the resume endpoints or the approval page by and solely by the recipients of the secret urls. See details in 'Advanced' -> 'Suspend' settings of the step.<br/><br/>
For further details, visit <a
<Alert title="Approval/Prompt Step" role="info">
An approval/prompt step will suspend the execution of a flow until it has been approved
and/or the prompts have been filled in the UI or through the resume endpoints or the
approval page by and solely by the recipients of the secret urls. See details in 'Advanced'
-> 'Suspend' settings of the step. A prompt is a specialized approval step with payload that
can be self-approved by the caller.<br /><br />
For further details, visit
<a
href="https://www.windmill.dev/docs/flows/flow_approval"
target="_blank"
class="text-blue-500"
>Approval Steps Documentation</a>.
class="text-blue-500">Approval/Prompt Steps Documentation</a
>
or
<div class="inline-flex">
<SuspendDrawer text="Approval/Step prompt helpers" />
</div>
</Alert>
{/if}
<h3 class="pb-2 pt-4">
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
<Tooltip documentationLink={
kind === 'script'
<Tooltip
documentationLink={kind === 'script'
? 'https://www.windmill.dev/docs/flows/editor_components#flow-actions'
: kind === 'trigger'
? 'https://www.windmill.dev/docs/flows/flow_trigger'
: kind === 'approval'
? 'https://www.windmill.dev/docs/flows/flow_approval'
: 'https://www.windmill.dev/docs/getting_started/flows_quickstart#flow-editor'
}>
Embed <span>{kind == 'script' ? 'action' : kind}</span> script directly inside a flow instead of saving the script into your workspace for
reuse. You can always save an inline script to your workspace later.
: 'https://www.windmill.dev/docs/getting_started/flows_quickstart#flow-editor'}
>
Embed <span>{kind == 'script' ? 'action' : kind}</span> script directly inside a flow instead
of saving the script into your workspace for reuse. You can always save an inline script to your
workspace later.
</Tooltip>
</h3>
{#if noEditor}

View File

@@ -68,8 +68,15 @@
<div slot="header" class="grow">
<input bind:value={mod.summary} placeholder={'Summary'} />
</div>
<Alert type="info" title="For loops" tooltip="For loops" documentationLink="https://www.windmill.dev/docs/flows/flow_loops" class="m-2">
Add steps inside the loop and specify an iterator expression that defines the sequence over which your subsequent steps will iterate.
<Alert
type="info"
title="For loops"
tooltip="For loops"
documentationLink="https://www.windmill.dev/docs/flows/flow_loops"
class="m-2"
>
Add steps inside the loop and specify an iterator expression that defines the sequence over
which your subsequent steps will iterate.
</Alert>
<Splitpanes horizontal class="!max-h-[calc(100%-48px)]">
<Pane size={60} minSize={20} class="p-4">
@@ -77,7 +84,8 @@
<div class="flex flex-row gap-8 mt-2 mb-6">
<div>
<div class="mb-2 text-sm font-bold"
>Skip failures <Tooltip documentationLink="https://www.windmill.dev/docs/flows/flow_loops"
>Skip failures <Tooltip
documentationLink="https://www.windmill.dev/docs/flows/flow_loops"
>If disabled, the flow will fail as soon as one of the iteration fail. Otherwise,
the error will be collected as the result of the iteration. Regardless of this
setting, if an error handler is defined, it will process the error.</Tooltip
@@ -100,7 +108,11 @@
/>
</div>
<div>
<div class="mb-2 text-sm font-bold">Parallelism <Tooltip>Assign a maximum number of branches run in parallel to control huge for-loops.</Tooltip> </div>
<div class="mb-2 text-sm font-bold"
>Parallelism <Tooltip
>Assign a maximum number of branches run in parallel to control huge for-loops.</Tooltip
>
</div>
<input
type="number"
disabled={!mod.value.parallel}
@@ -152,7 +164,7 @@
<Tabs bind:selected>
<!-- <Tab value="retries">Retries</Tab> -->
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="suspend">Suspend/Approval</Tab>
<Tab value="suspend">Suspend/Approval/Prompt</Tab>
<Tab value="sleep">Sleep</Tab>
<Tab value="mock">Mock</Tab>
<Tab value="lifetime">Lifetime</Tab>

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import SchemaEditor from '$lib/components/SchemaEditor.svelte'
import Slider from '$lib/components/Slider.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import InputTransformForm from '$lib/components/InputTransformForm.svelte'
@@ -16,6 +15,7 @@
import type { FlowEditorContext } from '../types'
import Section from '$lib/components/Section.svelte'
import Label from '$lib/components/Label.svelte'
import SuspendDrawer from './SuspendDrawer.svelte'
const { selectedId, flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
const result = $flowStateStore[$selectedId]?.previewResult ?? {}
@@ -49,13 +49,18 @@
}
</script>
<Section label="Suspend/Approval" class="w-full">
<Section label="Suspend/Approval/Prompt" 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.
used flexibly for other purposes.
</Tooltip>
<div class="ml-4">
<div class="flex">
<SuspendDrawer text="Approval/Prompt helpers" />
</div>
</div>
</svelte:fragment>
<Toggle
@@ -175,53 +180,29 @@
<Alert type="warning" title="Adding a form to the approval page is an EE feature" />
{/if}
<Toggle
checked={Boolean(flowModule.suspend.resume_form)}
options={{
right: 'Add a form to the approval page'
}}
disabled={emptyString($enterpriseLicense)}
on:change={(e) => {
if (flowModule.suspend) {
if (e.detail) {
flowModule.suspend.resume_form = {
schema: emptySchema()
<div class="flex gap-4">
<Toggle
checked={Boolean(flowModule.suspend.resume_form)}
options={{
right: 'Add a form to the approval page'
}}
disabled={emptyString($enterpriseLicense)}
on:change={(e) => {
if (flowModule.suspend) {
if (e.detail) {
flowModule.suspend.resume_form = {
schema: emptySchema()
}
} else {
flowModule.suspend.resume_form = undefined
}
} else {
flowModule.suspend.resume_form = undefined
}
}
}}
/>
<div>
<Slider size="xs" text="How to add dynamic default args & enums">
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
>
For enums, use `enums`, e.g:
<pre
><code
>{`return {
endpoints,
enums: {
foo: ["choice1", "choice2"]
},
}`}</code
></pre
>
</Slider></div
>
}}
/>
<div class="flex">
<SuspendDrawer text="Default args & Dynamic enums help" />
</div>
</div>
{/if}
{#if flowModule.suspend?.resume_form}
<SchemaEditor bind:schema={flowModule.suspend.resume_form.schema} />

View File

@@ -0,0 +1,144 @@
<script lang="ts">
import HighlightCode from '$lib/components/HighlightCode.svelte'
import Section from '$lib/components/Section.svelte'
import { Script } from '$lib/gen'
import { HelpCircle } from 'lucide-svelte'
import { Button, Drawer, Tab, Tabs } from '../../common'
import DrawerContent from '../../common/drawer/DrawerContent.svelte'
import TabContent from '$lib/components/common/tabs/TabContent.svelte'
let drawer: Drawer
export let text: string = 'Approval Help'
</script>
<Button
size="xs"
variant="border"
color="light"
on:click={() => {
drawer.openDrawer()
}}
>{text} <HelpCircle size={12} />
</Button>
<Drawer bind:this={drawer}>
<DrawerContent title="Suspend/Approval/Prompt help" on:close={drawer.closeDrawer}>
<div class="flex flex-col gap-y-6 text-xs text-primary font-normal">
<Section label="Form/Payload">
To add a form, go to the <b>Form</b> tab, inside the Advanced {'->'} Suspend tab, and add a form.
You can then get back the payloads using `resume` (single approver), or `resumes` (multiple approvers)
in the next step. Forms are an EE feature only. The approver list itself is fetchable using `approvers`
</Section>
<Section label="Prompt">
A prompt is simply an approval step that can be self-approved. To do this, include the
resume url in the returned payload of the step. The UX will automatically adapt and show the
prompt to the operator when running the flow. e.g:
<Tabs selected="deno" class="pt-4">
<Tab value="deno">TypeScript (Deno)</Tab>
<Tab value="bun">TypeScript (Bun)</Tab>
<Tab value="python">Python</Tab>
<svelte:fragment slot="content">
<TabContent value="deno" class="p-2">
<HighlightCode
language={Script.language.DENO}
code={`import * as wmill from "npm:windmill-client@^1.158.2"
export async function main() {
const resumeUrls = await wmill.getResumeUrls("approver1")
return {
resume: resumeUrls['resume']
default_args: {}, // optional, see below
enums: {} // optional, see below
}
}`}
/>
</TabContent>
<TabContent value="bun" class="p-2">
<HighlightCode
language={Script.language.DENO}
code={`import * as wmill from "windmill-client"
export async function main() {
const resumeUrls = await wmill.getResumeUrls("approver1")
return {
resume: resumeUrls['resume']
default_args: {}, // optional, see below
enums: {} // optional, see below
}
}`}
/>
</TabContent>
<TabContent value="python" class="p-2">
<HighlightCode
language={Script.language.PYTHON3}
code={`import wmill
def main():
urls = wmill.get_resume_urls()
return {
"resume": urls["resume"]
"default_args": {}, # optional, see below
"enums": {} # optional, see below
}
`}
/>
</TabContent>
</svelte:fragment>
</Tabs>
</Section>
<Section label="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:
<HighlightCode
language={Script.language.DENO}
code={`//this assumes the Form tab has a string field named "foo" and a checkbox named "bar"
import * as wmill from "npm:windmill-client@^1.158.2"
export async function main() {
// if no argument is passed, if user is logged in, it will use the user's username
const resumeUrls = await wmill.getResumeUrls("approver1")
// send the resumeUrls to the recipient or see Prompt section above
return {
default_args: {
foo: "foo",
bar: true
}
}
}`}
/>
</Section>
<Section label="Dynamics enums">
As one of the return key of this step, return an object `enums` that contains the default
arguments of the form arguments. e.g:
<HighlightCode
language={Script.language.DENO}
code={`
//this assumes the Form tab has a string field named "foo"
import * as wmill from "npm:windmill-client@^1.158.2"
export async function main() {
// if no argument is passed, if user is logged in, it will use the user's username
const resumeUrls = await wmill.getResumeUrls("approver1")
// send the resumeUrls to the recipient or see Prompt section above
return {
enums: {
foo: ["choice1", "choice2"]
},
}
}`}
/>
</Section>
</div>
</DrawerContent>
</Drawer>

View File

@@ -78,7 +78,7 @@
tabindex="-1"
>
<CheckCircle2 size={14} />
Approval
Approval/Prompt
</button>
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"

View File

@@ -265,6 +265,13 @@ export async function main(approver?: string) {
return wmill.getResumeUrls(approver)
}`
export const PYTHON_INIT_CODE_APPROVAL = `import wmill
def main():
urls = wmill.get_resume_urls()
return urls
`
export const BUN_INIT_CODE_APPROVAL = `import * as wmill from "windmill-client@^1.158.2"
export async function main(approver?: string) {
@@ -355,6 +362,8 @@ export function initialCode(
} else if (language === 'python3') {
if (kind === 'trigger') {
return PYTHON_INIT_CODE_TRIGGER
} else if (kind === 'approval') {
return PYTHON_INIT_CODE_APPROVAL
} else if (subkind === 'flow') {
return PYTHON_INIT_CODE_CLEAR
} else if (kind === 'failure') {