approvers are available to the step right after approval

This commit is contained in:
Ruben Fiszel
2022-11-02 10:43:19 +01:00
parent 9eb12e09b6
commit c049838fd2
18 changed files with 149 additions and 132 deletions
@@ -656,6 +656,7 @@ async fn transform_input(
token: &str,
steps: Vec<Uuid>,
resumes: &[Value],
approvers: Vec<String>,
by_id: &IdContext,
base_internal_url: &str,
) -> anyhow::Result<Map<String, serde_json::Value>> {
@@ -682,6 +683,7 @@ async fn transform_input(
resumes.last().map(|v| json!(v)).unwrap_or_default(),
),
("resumes".to_string(), resumes.clone().into()),
("approvers".to_string(), json!(approvers.clone())),
];
let v = eval_timeout(
@@ -854,6 +856,7 @@ async fn push_next_flow_job(
};
let mut resume_messages: Vec<Value> = vec![];
let mut approvers: Vec<String> = vec![];
/* (suspend / resume), when starting a module, if previous module has a
* non-zero `suspend` value, collect `resume_job`s for the previous module job.
@@ -886,6 +889,12 @@ async fn push_next_flow_job(
.await?;
resume_messages.extend(resumes.iter().map(|r| r.value.clone()));
approvers.extend(resumes.iter().map(|r| {
r.approver
.as_deref()
.unwrap_or_else(|| "anonymous")
.to_string()
}));
let required_events = suspend.required_events.unwrap() as u16;
if resume_messages.len() >= required_events as usize {
@@ -1087,6 +1096,7 @@ async fn push_next_flow_job(
&token,
steps.to_vec(),
resume_messages.as_slice(),
approvers,
by_id,
base_internal_url,
)
+1 -1
View File
@@ -40,7 +40,7 @@
class="text-xs text-blue-500"
on:click={async () => {
await getResource(value.substring('$res:'.length))
jsonViewer.openModal()
jsonViewer.toggleDrawer()
}}>{value}</button
>{:else if asJson.length > 40}
{truncate(asJson, 40)}<a
+2 -2
View File
@@ -28,7 +28,7 @@
<Badge large>
<Icon data={faHourglassHalf} scale={SMALL_ICON_SCALE} class="mr-2" />
Job ran in {msToSec(job.duration_ms)} s
Ran in {msToSec(job.duration_ms)} s
</Badge>
</div>
{:else if job && 'success' in job}
@@ -39,7 +39,7 @@
</Badge>
<Badge large>
<Icon data={faHourglassHalf} scale={SMALL_ICON_SCALE} class="mr-2" />
Job ran in {msToSec(job.duration_ms)}s
Ran in {msToSec(job.duration_ms)}s
</Badge>
</div>
{:else if job && 'running' in job && job.running}
@@ -200,16 +200,18 @@
/>
</div>
<h2 class="border-b pb-1 mt-4"> Metadata </h2>
<Button
color="light"
size="sm"
endIcon={{ icon: viewScriptKind ? faChevronUp : faChevronDown }}
on:click={() => (viewScriptKind = !viewScriptKind)}
>
Specialize the script as a specific module kind for flows
</Button>
<div>
<Button
color="light"
size="sm"
endIcon={{ icon: viewScriptKind ? faChevronUp : faChevronDown }}
on:click={() => (viewScriptKind = !viewScriptKind)}
>
Specialize the script as a specific module kind for flows
</Button>
</div>
{#if viewScriptKind}
<div class="max-w-lg" transition:slide>
<div class="max-w-lg">
<RadioButton
label="Script Type"
options={[
@@ -249,17 +251,18 @@
</div>
{/if}
{#if script.language == 'deno' && script.kind == Script.kind.SCRIPT}
<Button
color="light"
size="sm"
endIcon={{ icon: viewTemplate ? faChevronUp : faChevronDown }}
on:click={() => (viewTemplate = !viewTemplate)}
>
Use a predefined template specific to this language and script kind
</Button>
<div
><Button
color="light"
size="sm"
endIcon={{ icon: viewTemplate ? faChevronUp : faChevronDown }}
on:click={() => (viewTemplate = !viewTemplate)}
>
Use a predefined template specific to this language and script kind
</Button>
</div>
{#if viewTemplate}
<div class="max-w-lg" transition:slide>
<div class="max-w-lg">
<RadioButton
label="Template"
options={[
@@ -45,8 +45,9 @@
},
light: {
border:
'border bg-white hover:bg-gray-100 focus:bg-gray-100 text-gray-700 hover:text-gray-800 focus:text-gray-800 focus:ring-gray-300',
contained: 'bg-white hover:bg-gray-100 focus:bg-gray-100 text-gray-700 focus:ring-gray-300'
'border border-gray-300 bg-white hover:bg-gray-100 focus:bg-gray-100 text-gray-700 hover:text-gray-800 focus:text-gray-800 focus:ring-gray-300',
contained:
'bg-white border-gray-300 hover:bg-gray-100 focus:bg-gray-100 text-gray-700 focus:ring-gray-300'
}
}
@@ -17,7 +17,7 @@
modules: Array<FlowModule>
}
export let parentModule: FlowModule | undefined
export let previousModuleId: string | undefined
export let previousModule: FlowModule | undefined
const { previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
let editor: SimpleEditor | undefined = undefined
@@ -25,9 +25,10 @@
$: pickableProperties = getStepPropPicker(
$flowStateStore,
parentModule,
previousModuleId,
previousModule,
$flowStore,
previewArgs
previewArgs,
true
).pickableProperties
</script>
@@ -43,7 +44,7 @@
<span class="mb-2 text-sm font-bold">Branch predicate</span>
<div class="border w-full">
<PropPickerWrapper
priorId={previousModuleId}
priorId={previousModule?.id}
{pickableProperties}
on:select={({ detail }) => {
editor?.insertAtCursor(detail)
@@ -12,7 +12,7 @@
export let flowModule: FlowModule
export let parentModule: FlowModule | undefined
export let previousModuleId: string | undefined
export let previousModule: FlowModule | undefined
let selected: string = 'early-stop'
</script>
@@ -77,13 +77,17 @@
<TabContent value="early-stop" class="flex flex-col flex-1 h-full">
<div class="p-4 overflow-y-auto">
<FlowModuleEarlyStop {previousModuleId} bind:flowModule {parentModule} />
<FlowModuleEarlyStop
previousModuleId={previousModule?.id}
bind:flowModule
{parentModule}
/>
</div>
</TabContent>
<TabContent value="suspend" class="flex flex-col flex-1 h-full">
<div class="p-4 overflow-y-auto">
<FlowModuleSuspend {previousModuleId} bind:flowModule />
<FlowModuleSuspend previousModuleId={previousModule?.id} bind:flowModule />
</div>
</TabContent>
</div>
@@ -24,10 +24,7 @@
<FlowFailureModule />
{:else}
{#each $flowStore.value.modules as flowModule, index (flowModule.id ?? index)}
<FlowModuleWrapper
bind:flowModule
previousModuleId={$flowStore.value.modules[index - 1]?.id}
/>
<FlowModuleWrapper bind:flowModule previousModule={$flowStore.value.modules[index - 1]} />
{/each}
{/if}
{/key}
@@ -14,7 +14,7 @@
</script>
<div class="space-y-4 p-4">
<div class="text-sm font-bold">Common script</div>
<div class="text-sm font-bold">{failureModule ? 'Error handler' : 'Common script'}</div>
<div class="grid sm:grid-col-2 lg:grid-cols-3 gap-4">
<FlowScriptPicker
label="Inline Python (3.10)"
@@ -23,8 +23,8 @@
on:click={() => {
dispatch('new', {
language: RawScript.language.PYTHON3,
kind: 'script',
subkind: failureModule ? 'failure' : 'flow'
kind: failureModule ? 'failure' : 'script',
subkind: 'flow'
})
}}
/>
@@ -36,8 +36,8 @@
on:click={() => {
dispatch('new', {
language: RawScript.language.DENO,
kind: 'script',
subkind: failureModule ? 'failure' : 'flow'
kind: failureModule ? 'failure' : 'script',
subkind: 'flow'
})
}}
/>
@@ -49,8 +49,8 @@
on:click={() => {
dispatch('new', {
language: RawScript.language.GO,
kind: 'script',
subkind: failureModule ? 'failure' : 'flow'
kind: failureModule ? 'failure' : 'script',
subkind: 'flow'
})
}}
/>
@@ -81,9 +81,9 @@
<div class="text-sm font-bold pt-8">
Trigger script
<Tooltip>
Used as a first step most commonly with an internal 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.
Used as a first step most commonly with an internal 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>
</div>
@@ -20,7 +20,7 @@
export let mod: FlowModule
export let parentModule: FlowModule | undefined
export let previousModuleId: string | undefined
export let previousModule: FlowModule | undefined
let editor: SimpleEditor | undefined = undefined
let selected: string = 'early-stop'
@@ -28,9 +28,10 @@
$: pickableProperties = getStepPropPicker(
$flowStateStore,
parentModule,
previousModuleId,
previousModule,
$flowStore,
previewArgs
previewArgs,
true
).pickableProperties
</script>
@@ -52,7 +53,7 @@
{#if mod.value.iterator.type == 'javascript'}
<div class="border w-full">
<PropPickerWrapper
priorId={previousModuleId}
priorId={previousModule?.id}
{pickableProperties}
on:select={({ detail }) => {
editor?.insertAtCursor(detail)
@@ -106,13 +107,17 @@
<TabContent value="early-stop" class="flex flex-col flex-1 h-full">
<div class="p-4 overflow-y-auto">
<FlowModuleEarlyStop {previousModuleId} bind:flowModule={mod} {parentModule} />
<FlowModuleEarlyStop
previousModuleId={previousModule?.id}
bind:flowModule={mod}
{parentModule}
/>
</div>
</TabContent>
<TabContent value="suspend" class="flex flex-col flex-1 h-full">
<div class="p-4 overflow-y-auto">
<FlowModuleSuspend {previousModuleId} bind:flowModule={mod} />
<FlowModuleSuspend previousModuleId={previousModule?.id} bind:flowModule={mod} />
</div>
</TabContent>
</div>
@@ -31,7 +31,7 @@
export let failureModule: boolean = false
export let parentModule: FlowModule | undefined = undefined
export let previousModuleId: string | undefined = undefined
export let previousModule: FlowModule | undefined
let editor: Editor
let modulePreview: ModulePreview
@@ -54,7 +54,14 @@
$: stepPropPicker = failureModule
? { pickableProperties: { previous_result: { error: 'the error message' } }, extraLib: '' }
: getStepPropPicker($flowStateStore, parentModule, previousModuleId, $flowStore, previewArgs)
: getStepPropPicker(
$flowStateStore,
parentModule,
previousModule,
$flowStore,
previewArgs,
true
)
function onKeyDown(event: KeyboardEvent) {
if ((event.ctrlKey || event.metaKey) && event.key == 'Enter') {
@@ -178,7 +185,7 @@
{#if selected === 'inputs'}
<div class="h-full overflow-auto">
<PropPickerWrapper
priorId={previousModuleId}
priorId={previousModule?.id}
pickableProperties={stepPropPicker.pickableProperties}
>
<SchemaForm
@@ -200,14 +207,14 @@
<FlowRetries bind:flowModule class="px-4 pb-4 h-full overflow-auto" />
{:else if selected === 'early-stop'}
<FlowModuleEarlyStop
{previousModuleId}
previousModuleId={previousModule?.id}
bind:flowModule
class="px-4 pb-4 h-full overflow-auto"
{parentModule}
/>
{:else if selected === 'suspend'}
<div class="px-4 pb-4 h-full overflow-auto">
<FlowModuleSuspend {previousModuleId} bind:flowModule />
<FlowModuleSuspend previousModuleId={previousModule?.id} bind:flowModule />
</div>
{/if}
</div>
@@ -12,9 +12,9 @@
const { previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
export let previousModuleId: string | undefined
export let flowModule: FlowModule
export let parentModule: FlowModule | undefined
export let previousModuleId: string | undefined
let editor: SimpleEditor | undefined = undefined
@@ -26,7 +26,7 @@
const propPicker = getStepPropPicker(
$flowStateStore,
parentModule,
flowModule.id,
flowModule,
$flowStore,
previewArgs
).pickableProperties
@@ -28,14 +28,14 @@
// Pointer to parent module, only defined within Branches or Loops.
export let parentModule: FlowModule | undefined = undefined
// Pointer to previous module, for easy access to testing results
export let previousModuleId: string | undefined = undefined
export let previousModule: FlowModule | undefined = undefined
</script>
{#if flowModule.id === $selectedId}
{#if flowModule.value.type === 'forloopflow'}
<FlowLoop bind:mod={flowModule} {parentModule} {previousModuleId} />
<FlowLoop bind:mod={flowModule} {parentModule} {previousModule} />
{:else if flowModule.value.type === 'branchone' || flowModule.value.type === 'branchall'}
<FlowBranchesWrapper {previousModuleId} bind:flowModule {parentModule} />
<FlowBranchesWrapper {previousModule} bind:flowModule {parentModule} />
{:else if flowModule.value.type === 'identity'}
{#if $selectedId == 'failure'}
<Alert type="info" title="Error handlers are triggered upon non recovered errors">
@@ -46,15 +46,19 @@
Steps are retried until they succeed, or until the maximum number of retries defined for that
spec is reached, at which point the error handler is called.
</Alert>
{:else}
<h1 class="p-4"
>Select a step kind <Tooltip
>Until being defined, this step acts as an identify function, returning as result its
input and assigning it a key 'previous_result' if the input is not a json object</Tooltip
></h1
>
{/if}
<h1 class="p-4"
>Select a step kind <Tooltip
>Until being defined, this step acts as an identify function, returning as result its input
and assigning it a key 'previous_result' if the input is not a json object</Tooltip
></h1
>
<FlowInputs
shouldDisableTriggerScripts={parentModule !== undefined || previousModuleId !== undefined}
shouldDisableTriggerScripts={parentModule !== undefined ||
previousModule !== undefined ||
$selectedId == 'failure'}
on:loop={async () => {
const [module, state] = await createLoop(flowModule.id)
flowModule = module
@@ -101,7 +105,7 @@
failureModule={$selectedId === 'failure'}
/>
{:else if flowModule.value.type === 'rawscript' || flowModule.value.type === 'script'}
<FlowModuleComponent bind:flowModule {parentModule} {previousModuleId} />
<FlowModuleComponent bind:flowModule {parentModule} {previousModule} />
{/if}
{:else if flowModule.value.type === 'forloopflow'}
{#each flowModule.value.modules as submodule, index (index)}
@@ -125,7 +129,7 @@
{/if}
{#each flowModule.value.branches as branch, branchIndex (branchIndex)}
{#if $selectedId === `${flowModule?.id}-branch-${branchIndex}`}
<FlowBranchOneWrapper bind:branch parentModule={flowModule} {previousModuleId} />
<FlowBranchOneWrapper bind:branch parentModule={flowModule} {previousModule} />
{:else}
{#each branch.modules as submodule, index}
<svelte:self
@@ -123,21 +123,27 @@ function getFlowInput(
export function getStepPropPicker(
flowState: FlowState,
parentModule: FlowModule | undefined,
previousModuleId: string | undefined,
previousModule: FlowModule | undefined,
flow: Flow,
args: any
args: any,
approvers: boolean = false
): StepPropPicker {
const flowInput = getFlowInput(dfs(parentModule?.id, flow), flowState, args, flow.schema)
const previousResults = previousModuleId
? flowState[previousModuleId].previewResult
const previousResults = previousModule
? flowState[previousModule.id].previewResult
: flattenPreviousResult(flowInput)
const pickableProperties = {
flow_input: flowInput,
previous_result: previousResults
}
if (approvers && ((previousModule?.suspend?.required_events ?? 0) > 0)) {
pickableProperties["approvers"] = "The list of approvers"
}
return {
extraLib: buildExtraLib(objectToTsType(flowInput), objectToTsType(previousResults)),
pickableProperties: {
flow_input: flowInput,
previous_result: previousResults
}
pickableProperties
}
}
+10 -9
View File
@@ -166,7 +166,7 @@ export async function main(approver?: string) {
return wmill.getResumeEndpoints(approver)
}`
const ALL_INITIAL_CODE = [PYTHON_INIT_CODE, DENO_INIT_CODE, POSTGRES_INIT_CODE, DENO_INIT_CODE_TRIGGER, DENO_INIT_CODE_CLEAR, PYTHON_INIT_CODE_CLEAR, DENO_INIT_CODE_APPROVAL]
const ALL_INITIAL_CODE = [PYTHON_INIT_CODE, DENO_INIT_CODE, POSTGRES_INIT_CODE, DENO_INIT_CODE_TRIGGER, DENO_INIT_CODE_CLEAR, PYTHON_INIT_CODE_CLEAR, DENO_INIT_CODE_APPROVAL, DENO_FAILURE_MODULE_CODE]
export function isInitialCode(content: string): boolean {
for (const code of ALL_INITIAL_CODE) {
@@ -177,36 +177,37 @@ export function isInitialCode(content: string): boolean {
return false
}
export function initialCode(language: 'deno' | 'python3' | 'go', kind: Script.kind, subkind: 'pgsql' | 'flow' | 'script' | 'failure' | 'approval' | undefined): string {
export function initialCode(language: 'deno' | 'python3' | 'go', kind: Script.kind, subkind: 'pgsql' | 'flow' | 'script' | undefined): string {
if (language === 'deno') {
if (kind === 'trigger') {
return DENO_INIT_CODE_TRIGGER
} else if (kind === 'script') {
if (subkind === 'flow') {
return DENO_INIT_CODE_CLEAR
} else if (subkind === 'failure') {
return DENO_FAILURE_MODULE_CODE
} else if (subkind === 'approval') {
return DENO_INIT_CODE_APPROVAL
}
else if (subkind === 'pgsql') {
return POSTGRES_INIT_CODE
} else {
return DENO_INIT_CODE
}
} else {
} else if (kind === 'failure') {
return DENO_FAILURE_MODULE_CODE
} else if (kind === 'approval') {
return DENO_INIT_CODE_APPROVAL
}
else {
return DENO_INIT_CODE
}
} else if (language === 'python3') {
if (subkind === 'flow') {
return PYTHON_INIT_CODE_CLEAR
} else if (subkind === 'failure') {
} else if (kind === 'failure') {
return PYTHON_FAILURE_MODULE_CODE
} else {
return PYTHON_INIT_CODE
}
} else {
if (subkind === 'failure') {
if (kind === 'failure') {
return GO_FAILURE_MODULE_CODE
} else {
return GO_INIT_CODE
+1 -1
View File
@@ -276,7 +276,7 @@ export function groupBy<T>(
export function truncate(s: string, n: number, suffix: string = '...'): string {
if (!s) {
return suffix
return ''
}
if (s.length <= n) {
return s
@@ -45,15 +45,19 @@
})
async function getJob() {
const suspendedJobFlow = await JobService.getSuspendedJobFlow({
workspace: $page.params.workspace,
id: $page.params.job,
resumeId: new Number($page.params.resume).valueOf(),
signature: $page.params.hmac,
approver
})
job = suspendedJobFlow.job
currentApprovers = suspendedJobFlow.approvers
try {
const suspendedJobFlow = await JobService.getSuspendedJobFlow({
workspace: $page.params.workspace,
id: $page.params.job,
resumeId: new Number($page.params.resume).valueOf(),
signature: $page.params.hmac,
approver
})
job = suspendedJobFlow.job
currentApprovers = suspendedJobFlow.approvers
} catch (e) {
sendUserToast(e.message, true)
}
}
async function resume() {
+9 -35
View File
@@ -25,7 +25,6 @@
faScroll,
faFastForward
} from '@fortawesome/free-solid-svg-icons'
import Tooltip from '$lib/components/Tooltip.svelte'
import DisplayResult from '$lib/components/DisplayResult.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import CenteredPage from '$lib/components/CenteredPage.svelte'
@@ -33,10 +32,11 @@
import HighlightCode from '$lib/components/HighlightCode.svelte'
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import LogViewer from '$lib/components/LogViewer.svelte'
import { Button, ActionRow, Skeleton } from '$lib/components/common'
import { Button, ActionRow, Skeleton, Tab } from '$lib/components/common'
import FlowMetadata from '$lib/components/FlowMetadata.svelte'
import JobArgs from '$lib/components/JobArgs.svelte'
import FlowProgressBar from '$lib/components/flows/FlowProgressBar.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
let workspace_id_query: string | undefined = $page.url.searchParams.get('workspace') ?? undefined
let workspace_id: string | undefined
@@ -252,40 +252,14 @@
{#if job?.job_kind !== 'flow' && job?.job_kind !== 'flowpreview'}
<!-- Logs and outputs-->
<div class="mr-2 sm:mr-0 mt-12">
<div class="flex flex-col sm:flex-row text-base">
<button
class=" py-1 px-6 block border-gray-200 hover:bg-gray-50 {viewTab !== 'result'
? 'text-gray-500'
: 'text-gray-700 font-semibold '}"
on:click={() => (viewTab = 'result')}
<Tabs bind:selected={viewTab}>
<Tab value="result">Result</Tab>
<Tab value="logs">Logs</Tab>
<Tab value="code"
>{job?.job_kind == 'dependencies' ? 'Input Dependencies' : 'Code previewed'}</Tab
>
Result <Tooltip
>What is returned by the <span class="font-mono">main</span> function of the script,
stringified to JSON. Then for some specific cases, like having "png", "jpeg" or "file"
as sole key, they are displayed more richly. See
<a href="https://docs.windmill.dev/docs/reference#rich-display-rendering">here</a> for more
details.</Tooltip
>
</button>
<button
class="py-1 px-6 block border-gray-200 hover:bg-gray-50 {viewTab !== 'logs'
? 'text-gray-500'
: 'text-gray-700 font-semibold '}"
on:click={() => (viewTab = 'logs')}
>
Logs
</button>
{#if job && 'raw_code' in job && job.raw_code}
<button
class="py-1 px-6 block border-gray-200 hover:bg-gray-50 {viewTab !== 'code'
? 'text-gray-500'
: 'text-gray-700 font-semibold '}"
on:click={() => (viewTab = 'code')}
>
{job.job_kind == 'dependencies' ? 'Input Dependencies' : 'Code previewed'}
</button>
{/if}
</div>
</Tabs>
<Skeleton loading={!job} layout={[[5]]} />
{#if job}
<div class="flex flex-row border rounded-md p-3 max-h-1/2 overflow-auto">