add instructions for triggers on flows

This commit is contained in:
Ruben Fiszel
2023-01-26 01:06:53 +01:00
parent 9dfe335789
commit e2bc5e19c6
11 changed files with 244 additions and 154 deletions
@@ -39,12 +39,14 @@
editor?.insertAtCursor(detail)
}}
>
<SimpleEditor
bind:this={editor}
lang="javascript"
bind:code={branch.expr}
class="small-editor"
shouldBindKey={false}
extraLib={stepPropPicker.extraLib}
/>
<div class="border border-gray-400">
<SimpleEditor
bind:this={editor}
lang="javascript"
bind:code={branch.expr}
class="small-editor border "
shouldBindKey={false}
extraLib={stepPropPicker.extraLib}
/>
</div>
</PropPickerWrapper>
@@ -27,133 +27,143 @@
</script>
<div class="p-4 h-full flex flex-col">
{#if !failureModule}
<div class="center-center">
<div class="max-w-min">
<ToggleButtonGroup bind:selected={kind}>
<ToggleButton position="left" value="script" size="sm" startIcon={{ icon: faCode }}>
Action &nbsp;<Tooltip>
An action script is simply a script that is neither a trigger nor an approval script.
Those are the majority of the scripts.
</Tooltip>
</ToggleButton>
{#if !shouldDisableTriggerScripts}
<ToggleButton position="center" value="trigger" size="sm" startIcon={{ icon: faBolt }}>
Trigger &nbsp;<Tooltip>
Used as a first step most commonly with a state and a schedule to watch for changes
on an external system, compute the diff since last time, set the new state. The
diffs are then treated one by one with a for-loop.
{#if summary == 'Terminate flow'}
<Alert role="info" title="The flow stops here"
>This is an identity step with an early stop that has 'true' for expression</Alert
>
{:else}{#if !failureModule}
<div class="center-center">
<div class="max-w-min">
<ToggleButtonGroup bind:selected={kind}>
<ToggleButton position="left" value="script" size="sm" startIcon={{ icon: faCode }}>
Action &nbsp;<Tooltip>
An action script is simply a script that is neither a trigger nor an approval
script. Those are the majority of the scripts.
</Tooltip>
</ToggleButton>
{/if}
<ToggleButton position="right" value="approval" size="sm" startIcon={{ icon: faCheck }}>
Approval &nbsp;<Tooltip>
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
those secret urls. Use `wmill.getResumeUrls()` in Typescript or
`wmill.get_resume_urls()` in Python from the wmill client to generate those URLs.
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
{#if !shouldDisableTriggerScripts}
<ToggleButton
position="center"
value="trigger"
size="sm"
startIcon={{ icon: faBolt }}
>
Trigger &nbsp;<Tooltip>
Used as a first step most commonly with a state and a schedule to watch for
changes on an external system, compute the diff since last time, set the new
state. The diffs are then treated one by one with a for-loop.
</Tooltip>
</ToggleButton>
{/if}
<ToggleButton position="right" value="approval" size="sm" startIcon={{ icon: faCheck }}>
Approval &nbsp;<Tooltip>
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
those secret urls. Use `wmill.getResumeUrls()` in Typescript or
`wmill.get_resume_urls()` in Python from the wmill client to generate those URLs.
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
</div>
</div>
</div>
{/if}
{#if kind == 'trigger'}
<div class="mt-2" />
<Alert title="Trigger script automatic schedule" role="info">
A schedule will be automatically attached to this flow to run every 15 minutes. Adjust
frequency in 'Settings -> Schedule'</Alert
>
{/if}
<h3 class="pb-2 pt-4">
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
<Tooltip>
Embed a 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>
<div class="flex flex-row">
<div class="flex flex-row flex-wrap gap-2">
<FlowScriptPicker
label="Typescript"
lang={Script.language.DENO}
on:click={() => {
dispatch('new', {
language: RawScript.language.DENO,
kind,
subkind: 'flow'
})
}}
/>
<FlowScriptPicker
label="Python"
lang={Script.language.PYTHON3}
on:click={() => {
dispatch('new', {
language: RawScript.language.PYTHON3,
kind,
subkind: 'flow'
})
}}
/>
{#if kind != 'approval'}
{/if}
{#if kind == 'trigger'}
<div class="mt-2" />
<Alert title="Trigger script automatic schedule" role="info">
A schedule will be automatically attached to this flow to run every 15 minutes. Adjust
frequency in 'Settings -> Schedule'</Alert
>
{/if}
<h3 class="pb-2 pt-4">
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
<Tooltip>
Embed a 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>
<div class="flex flex-row">
<div class="flex flex-row flex-wrap gap-2">
<FlowScriptPicker
label="Go"
lang={Script.language.GO}
label="Typescript"
lang={Script.language.DENO}
on:click={() => {
dispatch('new', {
language: RawScript.language.GO,
kind,
subkind: 'flow'
})
}}
/>
{/if}
{#if kind == 'script'}
<FlowScriptPicker
label="Bash"
lang={Script.language.BASH}
on:click={() => {
dispatch('new', {
language: RawScript.language.BASH,
language: RawScript.language.DENO,
kind,
subkind: 'flow'
})
}}
/>
{#if !failureModule}
<FlowScriptPicker
label={`PostgreSQL`}
lang="pgsql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'pgsql' })}
/>
<FlowScriptPicker
label="Python"
lang={Script.language.PYTHON3}
on:click={() => {
dispatch('new', {
language: RawScript.language.PYTHON3,
kind,
subkind: 'flow'
})
}}
/>
{#if kind != 'approval'}
<FlowScriptPicker
label={`MySQL`}
lang="mysql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'mysql' })}
label="Go"
lang={Script.language.GO}
on:click={() => {
dispatch('new', {
language: RawScript.language.GO,
kind,
subkind: 'flow'
})
}}
/>
{/if}
{/if}
</div>
</div>
<h3 class="mb-2 mt-6"
>Use pre-made <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script</h3
>
{#if pick_existing == 'hub'}
<PickHubScript bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</PickHubScript>
{:else}
<WorkspaceScriptPicker displayLock bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</WorkspaceScriptPicker>
{#if kind == 'script'}
<FlowScriptPicker
label="Bash"
lang={Script.language.BASH}
on:click={() => {
dispatch('new', {
language: RawScript.language.BASH,
kind,
subkind: 'flow'
})
}}
/>
{#if !failureModule}
<FlowScriptPicker
label={`PostgreSQL`}
lang="pgsql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'pgsql' })}
/>
<FlowScriptPicker
label={`MySQL`}
lang="mysql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'mysql' })}
/>
{/if}
{/if}
</div>
</div>
<h3 class="mb-2 mt-6"
>Use pre-made <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script</h3
>
{#if pick_existing == 'hub'}
<PickHubScript bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</PickHubScript>
{:else}
<WorkspaceScriptPicker displayLock bind:filter {kind} on:pick>
<ToggleHubWorkspace bind:selected={pick_existing} />
</WorkspaceScriptPicker>
{/if}
{/if}
</div>
@@ -14,12 +14,20 @@
import { getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import autosize from 'svelte-autosize'
import Slider from '$lib/components/Slider.svelte'
import { page } from '$app/stores'
import { workspaceStore } from '$lib/stores'
import { copyToClipboard } from '$lib/utils'
import { Icon } from 'svelte-awesome'
import { faClipboard } from '@fortawesome/free-solid-svg-icons'
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
export let initialPath: string
let topHeight = 0
$: url = `${$page.url.hostname}/api/w/${$workspaceStore}/jobs/run/f/${$flowStore?.path}`
</script>
<div class="h-full overflow-hidden">
@@ -32,34 +40,78 @@
<Tab value="settings-graph">Graph</Tab>
<svelte:fragment slot="content">
<TabContent value="settings-metadata" class="p-4">
<Path bind:path={$flowStore.path} {initialPath} namePlaceholder="my_flow" kind="flow" />
<label class="block my-4">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
<input
type="text"
bind:value={$flowStore.summary}
placeholder="A short summary of the flow displayed when the flow is listed"
id="flow-summary"
<TabContent value="settings-metadata" class="p-4 h-full">
<div class="overflow-auto h-full">
<Path
bind:path={$flowStore.path}
{initialPath}
namePlaceholder="my_flow"
kind="flow"
/>
</label>
<label class="block my-4" for="inp">
<span class="text-gray-700 text-sm">
Description
<Required required={false} />
<textarea
use:autosize
<label class="block my-4">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
<input
type="text"
class="text-sm"
id="inp"
bind:value={$flowStore.description}
placeholder="A description to help users understand what this flow does and how to use it."
rows="3"
bind:value={$flowStore.summary}
placeholder="A short summary of the flow displayed when the flow is listed"
id="flow-summary"
/>
</span>
</label>
</label>
<label class="block my-4" for="inp">
<span class="text-gray-700 text-sm">
Description
<Required required={false} />
<textarea
use:autosize
type="text"
class="text-sm"
id="inp"
bind:value={$flowStore.description}
placeholder="A description to help users understand what this flow does and how to use it."
rows="3"
/>
</span>
</label>
<Slider text="How to trigger from external events?">
<div class="text-sm text-gray-600 border p-4">
There are 2 ways to trigger a flow based on external events:
<ul class="pt-4">
<li
>1. Send a webhook after each event: <a
on:click={(e) => {
e.preventDefault()
copyToClipboard(url)
}}
href={$page.url.protocol + '//' + url}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{url}
<span class="text-gray-700 ml-2">
<Icon data={faClipboard} />
</span>
</a>
</li>
<li class="mt-2">
<div class="flex flex-row gap-2">
2. Use a trigger script and schedule this flow to run as frequently as
needed and compare a state persisted in Windmill to the state of the
external system. If a difference is detected, then the rest of the flow is
triggered. Oftentimes, the second step of a flow is a for-loop that will
iterate over every elements. When using a trigger, a default schedule will
be created.
<img
class="shadow-lg border rounded"
alt="static button"
src="/trigger_button.png"
/>
</div>
</li></ul
>
</div>
</Slider>
</div>
</TabContent>
<TabContent value="settings-schedule" class="p-4">
<Alert type="info" title="Primary Schedule">
@@ -85,7 +85,7 @@
</div>
<div>
<FlowModuleSchemaMap bind:modules={branch.modules} />
<FlowModuleSchemaMap parentType="branchall" bind:modules={branch.modules} />
</div>
</div>
{/each}
@@ -69,7 +69,7 @@
</span>
</div>
<div>
<FlowModuleSchemaMap bind:modules={module.value.default} />
<FlowModuleSchemaMap parentType="branchone" bind:modules={module.value.default} />
</div>
{#each module.value.branches ?? [] as branch, branchIndex (branchIndex)}
@@ -108,7 +108,7 @@
</div>
<div>
<FlowModuleSchemaMap bind:modules={branch.modules} />
<FlowModuleSchemaMap parentType="branchone" bind:modules={branch.modules} />
</div>
</div>
{/each}
@@ -23,13 +23,14 @@
export let root: boolean = false
export let modules: FlowModule[] | undefined
export let parentType: 'flow' | 'forloop' | 'branchall' | 'branchone' | undefined = undefined
let indexToRemove: number | undefined = undefined
const { select, selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
async function insertNewModuleAtIndex(
index: number,
kind: 'script' | 'forloop' | 'branchone' | 'branchall' | 'flow' | 'trigger' | 'approval'
kind: 'script' | 'forloop' | 'branchone' | 'branchall' | 'flow' | 'trigger' | 'approval' | 'end'
): Promise<void> {
await idMutex.runExclusive(async () => {
var module = emptyModule(kind == 'flow')
@@ -50,6 +51,9 @@
flowModule.summary = 'Trigger'
} else if (kind == 'approval') {
flowModule.summary = 'Approval'
} else if (kind == 'end') {
flowModule.summary = 'Terminate flow'
flowModule.stop_after_if = { skip_if_stopped: false, expr: 'true' }
}
select(flowModule.id)
})
@@ -136,10 +140,15 @@
</div>
{/each}
{/if}
<InsertModuleButton
trigger={modules?.length == 0}
on:new={(e) => insertNewModuleAtIndex(modules?.length ?? 0, e.detail)}
/>
{#if !modules || modules[modules?.length - 1]?.summary != 'Terminate flow'}
<InsertModuleButton
stop={!root && parentType == 'branchone'}
trigger={root && modules?.length == 0}
on:new={(e) => insertNewModuleAtIndex(modules?.length ?? 0, e.detail)}
/>
{:else}
<div class="my-2" />
{/if}
</ul>
{#if root}
<div class="sticky bottom-0 bg-gray-50 flex-none px-4 py-1 pb-2 border-t">
@@ -10,10 +10,11 @@
} from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import { Repeat } from 'lucide-svelte'
import { Repeat, Square } from 'lucide-svelte'
const dispatch = createEventDispatcher()
export let trigger = false
export let stop = false
</script>
<Menu noMinW placement="bottom-start" let:close>
@@ -113,5 +114,18 @@
<Icon data={faBarsStaggered} scale={0.8} class="mr-2" />
Flow
</button>
{#if stop}
<button
class="w-full text-left p-2 hover:bg-gray-100 inline-flex gap-2.5"
on:click={() => {
close()
dispatch('new', 'end')
}}
role="menuitem"
>
<Square size={14} />
End Flow
</button>
{/if}
</div>
</Menu>
@@ -11,7 +11,7 @@
import Icon from 'svelte-awesome'
import IconedResourceType from '$lib/components/IconedResourceType.svelte'
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
import { Building, Repeat } from 'lucide-svelte'
import { Building, Repeat, Square } from 'lucide-svelte'
export let mod: FlowModule
export let index: number
@@ -107,6 +107,8 @@
<div slot="icon">
{#if mod.value.type === 'rawscript'}
<LanguageIcon lang={mod.value.language} width={16} height={16} />
{:else if mod.summary == 'Terminate flow'}
<Square size={16} />
{:else if mod.value.type === 'identity'}
<Icon data={faLongArrowDown} scale={1.1} />
{:else if mod.value.type === 'script'}
+2 -1
View File
@@ -16,7 +16,8 @@
'Stopping the server timed out',
'Canceled',
'Missing service editorService',
'Unexpected usage'
'Unexpected usage',
'NetworkError when attempting to fetch resource.'
]
async function loadUser() {
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB