mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat: add dynamic default args to approval page form
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user