mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 08:07:03 +00:00
add instructions for triggers on flows
This commit is contained in:
@@ -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 <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 <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 <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 <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 <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 <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'}
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user