feat: add dynamic default args to approval page form

This commit is contained in:
Ruben Fiszel
2023-06-20 14:35:33 +02:00
parent eab70dcd35
commit cb3177e3c0
12 changed files with 137 additions and 74 deletions
+3 -4
View File
@@ -203,7 +203,7 @@ curl https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compos
curl https://raw.githubusercontent.com/windmill-labs/windmill/main/Caddyfile -o Caddyfile
curl https://raw.githubusercontent.com/windmill-labs/windmill/main/.env -o .env
docker compose up -d --pull always
docker compose up -d
```
Go to http://localhost et voilà :)
@@ -373,12 +373,11 @@ it being synced automatically everyday.
This will use the backend of <https://app.windmill.dev> but your own frontend
with hot-code reloading.
1. Install [caddy](https://caddyserver.com)
2. Go to `frontend/`:
1. Go to `frontend/`:
1. `npm install`
2. `npm run generate-backend-client`
3. `npm run dev`
3. Et voilà, windmill should be available at `http://localhost:3000/`
2. Et voilà, windmill should be available at `http://localhost:3000/`
### Backend + Frontend
+2
View File
@@ -20,6 +20,7 @@ services:
windmill_server:
# Use ghcr.io/windmill-labs/windmill-ee:main for the ee
image: ghcr.io/windmill-labs/windmill:main
pull_policy: always
deploy:
replicas: 1
restart: unless-stopped
@@ -44,6 +45,7 @@ services:
windmill_worker:
# Use ghcr.io/windmill-labs/windmill-ee:main for the ee
image: ghcr.io/windmill-labs/windmill:main
pull_policy: always
deploy:
replicas: 3
restart: unless-stopped
+1 -1
View File
@@ -48,7 +48,7 @@ REMOTE=http://localhost REMOTE_LSP=http://localhost npm run dev
**On OSX:**
```bash
brew install llvm caddy gsed
brew install llvm gsed
# make LLVM tools available on PATH
echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc
@@ -15,9 +15,8 @@
import ModuleStatus from './ModuleStatus.svelte'
import { displayDate, emptyString, isOwner, pluralize, truncateRev } from '$lib/utils'
import JobArgs from './JobArgs.svelte'
import Tooltip from './Tooltip.svelte'
import { Loader2 } from 'lucide-svelte'
import SchemaForm from './SchemaForm.svelte'
import FlowStatusWaitingForEvents from './FlowStatusWaitingForEvents.svelte'
const dispatch = createEventDispatcher()
@@ -165,8 +164,6 @@
$: selected = isListJob ? 'sequence' : 'graph'
let payload: any = {}
function isSuccess(arg: any): boolean | undefined {
if (arg == undefined) {
return undefined
@@ -227,48 +224,7 @@
/>
</div>
{:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === FlowStatusModule.type.WAITING_FOR_EVENTS}
<div class="w-full h-full mt-2 text-sm text-gray-600">
<p>Waiting to be resumed</p>
<div>
{#if is_owner}
<div class="flex flex-row gap-2 mt-2">
<div>
<Button
color="green"
variant="border"
on:click={async () =>
await JobService.resumeSuspendedFlowAsOwner({
workspace: workspaceId ?? $workspaceStore ?? '',
id: job?.id ?? '',
requestBody: JSON.parse(payload)
})}
>Resume <Tooltip
>Since you are an owner of this flow, you can send resume events without
necessarily knowing the resume id sent by the approval step</Tooltip
></Button
>
</div>
{#if job.raw_flow?.modules?.[job?.flow_status?.step - 1]?.suspend?.resume_form?.schema}
<div class="w-full border rounded-lg p-2">
<SchemaForm
noVariablePicker
bind:args={payload}
schema={job.raw_flow?.modules?.[job?.flow_status?.step - 1]?.suspend
?.resume_form?.schema}
/>
</div>
{/if}
<Tooltip
>The payload is optional, it is passed to the following step through the
`resume` variable</Tooltip
>
</div>
{:else}
You cannot resume the flow yourself without receiving the resume secret since you
are not an owner of {job.script_path}
{/if}
</div>
</div>
<FlowStatusWaitingForEvents {workspaceId} {job} {is_owner} />
{:else if job.logs}
<div class="text-xs p-4 bg-gray-50 overflow-auto max-h-80 border">
<pre class="w-full">{job.logs}</pre>
@@ -0,0 +1,70 @@
<script lang="ts">
import { Job, JobService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import SchemaForm from './SchemaForm.svelte'
import Tooltip from './Tooltip.svelte'
import { Button } from './common'
export let is_owner: boolean
export let workspaceId: string | undefined
export let job: Job
let payload: object = {}
$: approvalStep = (job?.flow_status?.step ?? 1) - 1
$: job && getDefaultArgs()
async function getDefaultArgs() {
let jobId = job.flow_status?.modules?.[approvalStep]?.job
if (!jobId) {
return {}
}
let job_result = await JobService.getCompletedJobResult({
workspace: workspaceId ?? $workspaceStore ?? '',
id: jobId
})
payload = job_result?.default_args ?? {}
}
</script>
<div class="w-full h-full mt-2 text-sm text-gray-600">
<p>Waiting to be resumed</p>
<div>
{#if is_owner}
<div class="flex flex-row gap-2 mt-2">
<div>
<Button
color="green"
variant="border"
on:click={async () =>
await JobService.resumeSuspendedFlowAsOwner({
workspace: workspaceId ?? $workspaceStore ?? '',
id: job?.id ?? '',
requestBody: payload
})}
>Resume <Tooltip
>Since you are an owner of this flow, you can send resume events without necessarily
knowing the resume id sent by the approval step</Tooltip
></Button
>
</div>
{#if job.raw_flow?.modules?.[approvalStep]?.suspend?.resume_form?.schema}
<div class="w-full border rounded-lg p-2">
<SchemaForm
noVariablePicker
bind:args={payload}
schema={job.raw_flow?.modules?.[approvalStep]?.suspend?.resume_form?.schema}
/>
</div>
{/if}
<Tooltip
>The payload is optional, it is passed to the following step through the `resume` variable</Tooltip
>
</div>
{:else}
You cannot resume the flow yourself without receiving the resume secret since you are not an
owner of {job.script_path}
{/if}
</div>
</div>
+2 -1
View File
@@ -7,9 +7,10 @@
export let text: string
export let tooltip: string | undefined = undefined
export let view = false
export let size: xs | 'sm' | 'md' | 'lg' = 'md'
</script>
<Button color="light" on:click={() => (view = !view)} variant="border"
<Button color="light" on:click={() => (view = !view)} {size} variant="border"
>{text}
{#if tooltip}
<Tooltip wrapperClass="mx-1">{tooltip}</Tooltip>
@@ -65,7 +65,7 @@
<Pane size={40}>
<Tabs bind:selected>
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="suspend">Suspend</Tab>
<Tab value="suspend">Suspend/Approval</Tab>
<Tab value="sleep">Sleep</Tab>
<svelte:fragment slot="content">
<div class="overflow-hidden bg-white">
@@ -65,7 +65,7 @@
<Pane size={40}>
<Tabs bind:selected>
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="suspend">Suspend</Tab>
<Tab value="suspend">Suspend/Approval</Tab>
<Tab value="sleep">Sleep</Tab>
<svelte:fragment slot="content">
<div class="overflow-hidden bg-white">
@@ -111,7 +111,7 @@
<Tabs bind:selected>
<!-- <Tab value="retries">Retries</Tab> -->
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="suspend">Suspend</Tab>
<Tab value="suspend">Suspend/Approval</Tab>
<Tab value="sleep">Sleep</Tab>
<svelte:fragment slot="content">
@@ -263,7 +263,7 @@
{#if !$selectedId.includes('failure')}
<Tab value="cache">Cache</Tab>
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="suspend">Suspend</Tab>
<Tab value="suspend">Suspend/Approval</Tab>
<Tab value="sleep">Sleep</Tab>
<Tab value="same_worker">Shared Directory</Tab>
{/if}
@@ -1,5 +1,6 @@
<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'
@@ -13,7 +14,7 @@
</script>
<h2 class="pb-4">
Suspend
Suspend/Approval
<Tooltip documentationLink="https://docs.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
@@ -54,23 +55,43 @@
{/if}
{#if flowModule.suspend}
<div class="mt-4" />
<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()
<div class="flex gap-4">
<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
}
} 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 {
endpoints,
default_args: {
foo: "foo",
bar: true,
},
}`}</code
></pre
>
</Slider></div
></div
>
<div class="mt-2" />
{/if}
{#if flowModule.suspend?.resume_form}
@@ -21,8 +21,8 @@
.map((x) => x.resume_id)
.includes(new Number($page.params.resume).valueOf())
$: schema =
job?.raw_flow?.modules?.[(job?.flow_status?.step ?? 1) - 1]?.suspend?.resume_form?.schema
$: approvalStep = (job?.flow_status?.step ?? 1) - 1
$: schema = job?.raw_flow?.modules?.[approvalStep]?.suspend?.resume_form?.schema
let timeout: NodeJS.Timer | undefined = undefined
let error: string | undefined = undefined
let payload: any = {}
@@ -53,6 +53,20 @@
timeout && clearInterval(timeout)
})
$: job && getDefaultArgs()
async function getDefaultArgs() {
let jobId = job?.flow_status?.modules?.[approvalStep]?.job
if (!jobId) {
return {}
}
let job_result = await JobService.getCompletedJobResult({
workspace: job?.workspace_id ?? '',
id: jobId
})
payload = job_result?.default_args ?? {}
}
async function getJob() {
const suspendedJobFlow = await JobService.getSuspendedJobFlow({
workspace: $page.params.workspace,