UX flow improvements + fix collecting results for loops in frontend + fix Identity behavior in backend

This commit is contained in:
Ruben Fiszel
2022-10-27 18:50:32 +02:00
parent 79e8b1ff75
commit 8475e31740
20 changed files with 147 additions and 130 deletions
+8
View File
@@ -1048,6 +1048,14 @@ async fn push_next_flow_job(
)
.await?
}
FlowModuleValue::Identity => match last_result.clone() {
Value::Object(m) => m,
v @ _ => {
let mut m = Map::new();
m.insert("previous_result".to_string(), v);
m
}
},
_ => {
/* embedded flow input is augmented with embedding flow input */
if let Some(value) = &flow_job.args {
+1 -3
View File
@@ -1,6 +1,4 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
import { setInputCat as computeInputCat, type InputCat } from '$lib/utils'
@@ -155,7 +153,7 @@
</span>
{#if seeEditable}
<div transition:slide class="mt-2">
<div class="mt-2">
<label class="text-gray-700">
Description
<textarea rows="1" bind:value={description} placeholder="Edit description" />
@@ -1,4 +1,4 @@
<div class="py-6">
<div class="pb-6">
<div class="max-w-6xl mx-auto px-4 sm:px-6 md:px-8">
<slot />
</div>
@@ -1,3 +1,7 @@
<script context="module" lang="ts">
export const EDITOR_BAR_WIDTH_THRESHOLD = 1044
</script>
<script lang="ts">
import { ResourceService, ScriptService, VariableService } from '$lib/gen'
import { getScriptByPath, loadHubScripts, sendUserToast } from '$lib/utils'
@@ -162,7 +162,9 @@
<div class="flex flex-col flex-1 h-full">
<!-- Nav between steps-->
<div class="justify-between flex flex-row w-full my-2 px-4 space-x-4 h-10">
<div
class="justify-between flex flex-row w-full my-2 px-4 space-x-4 h-10 overflow-x-auto scrollbar-hidden"
>
<div id="flow_title" class="flex justify-between items-center">
<button class="flex flex-row items-center w-full h-full" on:click={() => select('settings')}>
<span class="font-mono text-sm"> {$flowStore.path}</span>
@@ -10,6 +10,7 @@
import { runFlowPreview } from './flows/utils'
import SchemaForm from './SchemaForm.svelte'
import FlowStatusViewer from '../components/FlowStatusViewer.svelte'
import { flowStateStore } from './flows/flowState'
export let previewMode: 'upTo' | 'whole'
export let open: boolean
@@ -160,7 +161,11 @@
<div class="h-full overflow-y-auto mb-16 grow">
{#if jobId}
<FlowStatusViewer {jobId} on:jobsLoaded={(e) => onJobsLoaded(e.detail)} />
<FlowStatusViewer
flowState={$flowStateStore}
{jobId}
on:jobsLoaded={(e) => onJobsLoaded(e.detail)}
/>
{/if}
</div>
</div>
@@ -7,13 +7,14 @@
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import { onDestroy } from 'svelte'
import { flowStateStore } from './flows/flowState'
import Button from './common/button/Button.svelte'
import type { FlowState } from './flows/flowState'
import { Button } from './common'
const dispatch = createEventDispatcher()
export let jobId: string
export let flowState: FlowState | undefined = undefined
export let flowJobIds:
| {
moduleId: string
@@ -84,7 +85,7 @@
}
}}
>
#{j}: {loopJobId}
#{j + 1}: {loopJobId}
<Icon
class="ml-2"
@@ -94,11 +95,21 @@
</Button>
<div class="border p-6" class:hidden={forloop_selected != loopJobId}>
<svelte:self
{flowState}
jobId={loopJobId}
on:jobsLoaded={(e) => {
console.log('loop')
if (flowJobIds?.moduleId) {
$flowStateStore[flowJobIds.moduleId].previewResult = e.detail.result
$flowStateStore[flowJobIds.moduleId].previewArgs = e.detail.args
if (flowState) {
if (
!flowState[flowJobIds.moduleId].previewResult ||
!Array.isArray(flowState[flowJobIds.moduleId].previewResult)
) {
flowState[flowJobIds.moduleId].previewResult = []
}
flowState[flowJobIds.moduleId].previewResult[j] = e.detail.result
flowState[flowJobIds.moduleId].previewArgs = e.detail.args
}
}
}}
/>
@@ -132,6 +143,7 @@
<li class="w-full border p-6 space-y-2">
{#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)}
<svelte:self
{flowState}
jobId={mod.job}
flowJobIds={mod.flow_jobs
? {
@@ -140,9 +152,11 @@
}
: undefined}
on:jobsLoaded={(e) => {
if (mod.id) {
$flowStateStore[mod.id].previewResult = e.detail.result
$flowStateStore[mod.id].previewArgs = e.detail.args
if (mod.id && (mod.flow_jobs ?? []).length == 0) {
if (flowState) {
flowState[mod.id].previewResult = e.detail.result
flowState[mod.id].previewArgs = e.detail.args
}
}
}}
/>
+7
View File
@@ -29,6 +29,8 @@
export let kind: PathKind
let inputP: HTMLInputElement | undefined = undefined
const dispatch = createEventDispatcher()
let groups: Group[] = []
@@ -43,6 +45,10 @@
return path
}
export function focus() {
inputP?.focus()
}
function handleKeyUp(event: KeyboardEvent) {
const key = event.key
@@ -200,6 +206,7 @@
</span>
<input
autofocus
bind:this={inputP}
autocomplete="off"
on:keyup={handleKeyUp}
bind:value={meta.name}
@@ -29,6 +29,9 @@
let pathError = ''
let summaryC: HTMLTextAreaElement | undefined = undefined
let pathC: Path | undefined = undefined
$: setQueryWithoutLoad($page.url, 'state', encodeState(script))
$: step = Number($page.url.searchParams.get('step')) || 1
@@ -101,7 +104,10 @@
variant="contained"
color="light"
size="xs"
on:click={() => changeStep(1)}
on:click={async () => {
await changeStep(1)
setTimeout(() => pathC?.focus(), 100)
}}
>
{script.path}
</Button>
@@ -111,7 +117,10 @@
variant="contained"
color="light"
size="xs"
on:click={() => changeStep(1)}
on:click={async () => {
await changeStep(1)
setTimeout(() => summaryC?.focus(), 100)
}}
>
<div class="max-w-[10em] !truncate">
{script.summary == '' || !script.summary ? 'No summary' : script.summary}
@@ -119,10 +128,26 @@
</Button>
</div>
<div class="flex flex-row-reverse ml-2">
{#if step != 3}
<div class="flex flex-row gap-x-2">
<Button
size="sm"
variant={step == 1 ? 'border' : 'contained'}
disabled={step === 1 && pathError !== ''}
btnClasses={step == 3 ? 'invisible' : ''}
on:click={editScript}>Save (commit)</Button
>
<Button
variant="border"
size="sm"
btnClasses={step == 1 ? 'invisible' : ''}
on:click={() => changeStep(step - 1)}
>
Back
</Button>
{#if step < 3}
<Button
size="sm"
btnClasses={step == 3 ? 'invisible' : ''}
disabled={step === 1 && pathError !== ''}
on:click={() => changeStep(step + 1)}
>
@@ -131,28 +156,6 @@
{:else}
<Button size="sm" on:click={editScript}>Save</Button>
{/if}
{#if step > 1}
<Button
variant="border"
size="sm"
btnClasses="mr-2"
on:click={() => changeStep(step - 1)}
>
Back
</Button>
{/if}
{#if step == 2}
<Button
variant="border"
size="sm"
btnClasses="mr-2"
on:click={async () => {
editScript()
}}
>
Save (commit)
</Button>
{/if}
</div>
</div>
</div>
@@ -163,6 +166,7 @@
<div class="space-y-6">
<h2 class="border-b pb-1 mt-4">Path & Summary</h2>
<Path
bind:this={pathC}
bind:error={pathError}
bind:path={script.path}
{initialPath}
@@ -178,8 +182,9 @@
</div>
</Path>
<label class="block ">
<span class="text-gray-700">Summary <Required required={false} /></span>
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
<textarea
bind:this={summaryC}
bind:value={script.summary}
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300
focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
+26 -25
View File
@@ -11,11 +11,12 @@
import SchemaForm from './SchemaForm.svelte'
import LogPanel from './scriptEditor/LogPanel.svelte'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import EditorBar from './EditorBar.svelte'
import EditorBar, { EDITOR_BAR_WIDTH_THRESHOLD } from './EditorBar.svelte'
import TestJobLoader from './TestJobLoader.svelte'
import { onMount } from 'svelte'
import { Button, Kbd } from './common'
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
import Tooltip from './Tooltip.svelte'
// Exported
export let schema: Schema = emptySchema()
@@ -25,6 +26,8 @@
let websocketAlive = { pyright: false, black: false, deno: false, go: false }
let width = 1200
// Internal state
let editor: Editor
@@ -98,26 +101,22 @@
<svelte:window on:keydown={onKeyDown} />
<div class="border-b-2 shadow-sm p-1 pr-4">
<div class="flex justify-between">
<EditorBar {editor} {lang} {websocketAlive} />
<div class="border-b-2 shadow-sm p-1 pr-4" bind:clientWidth={width}>
<div class="flex justify-between space-x-2">
<EditorBar iconOnly={width < EDITOR_BAR_WIDTH_THRESHOLD} {editor} {lang} {websocketAlive} />
<div class="flex divide-x">
<div>
<Button
target="_blank"
href="https://github.com/windmill-labs/windmill-gh-action-deploy"
color="light"
size="xs"
btnClasses="mr-1"
startIcon={{
icon: faGithub
}}
>
Sync from Github
</Button>
</div>
</div>
<Button
target="_blank"
href="https://github.com/windmill-labs/windmill-gh-action-deploy"
color="light"
size="sm"
btnClasses="mr-1 hidden md:block"
startIcon={{
icon: faGithub
}}
>
Sync from Github
</Button>
</div>
</div>
<SplitPanesWrapper>
@@ -150,12 +149,14 @@
<Pane size={40} minSize={10}>
<Splitpanes horizontal>
<Pane size={30}>
<div class="p-4">
<div class="w-full bg-gray-100 px-2 text-sm"
>Preview <Tooltip>
To recompute the input schema press <Kbd>Ctrl/Cmd</Kbd> + <Kbd>S</Kbd> or move the focus
outside of the text editor
</Tooltip></div
>
<div class="px-2">
<div class="break-all relative font-sans">
<p class="items-baseline break-normal text-sm text-gray-600 hidden md:block mb-3">
To recompute the input schema press <Kbd>Ctrl/Cmd</Kbd> + <Kbd>S</Kbd> or move the focus
outside of the text editor
</p>
<SchemaForm {schema} bind:args bind:isValid />
</div>
</div>
@@ -9,6 +9,7 @@
import Highlight from 'svelte-highlight'
import json from 'svelte-highlight/languages/json'
import SvelteMarkdown from 'svelte-markdown'
import { Alert } from './common'
export let schema: Schema
export let summary: string
@@ -20,7 +21,7 @@
</script>
<div class="w-full">
<h1 class="mb-4">UI customisation</h1>
<h1 class="my-4">UI customisation</h1>
<Tabs selected="ui">
<Tab value="ui">UI</Tab>
@@ -49,15 +50,13 @@
</div>
</label>
</div>
<h2 class="border-b pb-1 mt-6">Arguments</h2>
<div class="bg-blue-100 border-l-4 border-blue-600 text-blue-700 p-4 m-4" role="alert">
<p class="font-bold">Synchronized with main signature</p>
<p>
Argument names, being required or not, and default values are derived from the main
signature of step 2 and cannot be edited directly. Change the main signature to edit
them.
</p>
</div>
<h2 class="border-b pb-1 my-4">Arguments</h2>
<Alert type="info" title="Synchronized with main signature">
Argument names, being required or not, and default values are derived from the main
signature of step 2 and cannot be edited directly. Change the main signature to edit them.
</Alert>
<div class="mt-4" />
<SchemaForm {schema} editableSchema={true} />
</TabContent>
<TabContent value="jsonschema">
+2 -4
View File
@@ -2,7 +2,6 @@
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import { createPopperActions } from 'svelte-popperjs'
import { fade } from 'svelte/transition'
const [popperRef, popperContent] = createPopperActions({
placement: 'auto'
})
@@ -17,7 +16,7 @@
})
const extraOpts = {
modifiers: [
betterPreventOverflow({ padding: 50 }),
betterPreventOverflow({ padding: 10 }),
{ name: 'offset', options: { offset: [8, 8] } },
{
name: 'arrow',
@@ -36,7 +35,7 @@
showTooltip = true
}
function close() {
timeout = setTimeout(() => (showTooltip = false), 200)
timeout = setTimeout(() => (showTooltip = false), 100)
}
</script>
@@ -49,7 +48,6 @@
</button>
{#if showTooltip}
<div
transition:fade
id="tooltip"
use:popperContent={extraOpts}
on:mouseenter={open}
@@ -19,7 +19,7 @@
<Button
size="sm"
color="light"
btnClasses={selectedIndex - 1 === index ? 'text-gray-800' : '!text-gray-500'}
btnClasses={selectedIndex - 1 === index ? 'text-gray-800 !font-bold' : '!text-gray-500'}
on:click={() => dispatch('select', { index })}
disabled={selectedIndex - 1 !== index ? disabled : false}
>
@@ -34,7 +34,7 @@
</script>
<div
class="border-b border-gray-200 flex flex-row whitespace-nowrap overflow-y-auto {$$props.class}"
class="border-b border-gray-200 flex flex-row whitespace-nowrap overflow-y-auto scrollbar-hidden {$$props.class}"
>
<slot />
</div>
@@ -1,16 +1,9 @@
<script context="module" lang="ts">
export type FlowModuleWidthContext = {
width: Writable<number>
threshold: number
}
</script>
<script lang="ts">
import { Pane, Splitpanes } from 'svelte-splitpanes'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Editor from '$lib/components/Editor.svelte'
import EditorBar from '$lib/components/EditorBar.svelte'
import EditorBar, { EDITOR_BAR_WIDTH_THRESHOLD } from '$lib/components/EditorBar.svelte'
import ModulePreview from '$lib/components/ModulePreview.svelte'
import { createScriptFromInlineScript, fork } from '$lib/components/flows/flowStateUtils'
import { flowStore } from '$lib/components/flows/flowStore'
@@ -24,12 +17,13 @@
import { afterUpdate, getContext, setContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import { loadSchemaFromModule } from '../utils'
import { writable, type Writable } from 'svelte/store'
import FlowModuleScript from './FlowModuleScript.svelte'
import FlowModuleEarlyStop from './FlowModuleEarlyStop.svelte'
import FlowModuleSuspend from './FlowModuleSuspend.svelte'
import FlowRetries from './FlowRetries.svelte'
import { getStepPropPicker } from '../previousResults'
import Tooltip from '$lib/components/Tooltip.svelte'
import { Kbd } from '$lib/components/common'
const { selectedId, previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
@@ -47,6 +41,8 @@
let panes: HTMLElement
let totalTopGap = 0
let width = 1200
let inputTransforms: Record<string, any> =
flowModule.value.type === 'rawscript' || flowModule.value.type === 'script'
? flowModule.value.input_transforms
@@ -85,14 +81,6 @@
}
}
export const FLOW_MODULE_WIDTH_THRESHOLD = 768
const width = writable<number>(0)
setContext<FlowModuleWidthContext>('FlowModuleWidth', {
width,
threshold: FLOW_MODULE_WIDTH_THRESHOLD
})
afterUpdate(() => {
totalTopGap = 0
if (!(wrapper && panes)) return
@@ -106,7 +94,7 @@
<svelte:window on:keydown={onKeyDown} />
{#if flowModule.value.type === 'rawscript' || flowModule.value.type === 'script'}
<div class="h-full" bind:this={wrapper} bind:clientWidth={$width}>
<div class="h-full" bind:this={wrapper} bind:clientWidth={width}>
<FlowCard bind:flowModule>
<svelte:fragment slot="header">
<FlowModuleHeader
@@ -137,7 +125,7 @@
{editor}
lang={flowModule.value['language'] ?? 'deno'}
{websocketAlive}
iconOnly={$width < FLOW_MODULE_WIDTH_THRESHOLD}
iconOnly={width < 768}
/>
</div>
{/if}
@@ -173,7 +161,12 @@
</Pane>
<Pane size={50} minSize={20}>
<Tabs bind:selected>
<Tab value="inputs">Inputs</Tab>
<Tab value="inputs"
><Tooltip>
Move the focus outside of the text editor to recompute the inputs or press
<Kbd>Ctrl/Cmd</Kbd> + <Kbd>S</Kbd>
</Tooltip>Inputs</Tab
>
<Tab value="test">Test</Tab>
<Tab value="retries">Retries</Tab>
{#if !$selectedId.includes('failure')}
@@ -188,11 +181,6 @@
priorId={previousModuleId}
pickableProperties={stepPropPicker.pickableProperties}
>
<p class="items-baseline text-xs text-gray-700 italic hidden md:block mb-2">
Move the focus outside of the text editor to recompute the inputs or press
Ctrl/Cmd+S
</p>
<SchemaForm
schema={$flowStateStore[$selectedId].schema}
inputTransform={true}
@@ -1,5 +1,6 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import { EDITOR_BAR_WIDTH_THRESHOLD } from '$lib/components/EditorBar.svelte'
import type { FlowModule } from '$lib/gen'
import { classNames } from '$lib/utils'
import {
@@ -9,22 +10,22 @@
faSave,
faStop
} from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher, getContext } from 'svelte'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import { isEmptyFlowModule } from '../utils'
import type { FlowModuleWidthContext } from './FlowModuleComponent.svelte'
export let module: FlowModule
const dispatch = createEventDispatcher()
const { width, threshold } = getContext<FlowModuleWidthContext>('FlowModuleWidth')
let width = 0
$: shouldPick = isEmptyFlowModule(module)
$: iconOnly = $width < threshold
$: iconOnly = width < EDITOR_BAR_WIDTH_THRESHOLD
$: moduleRetry = module.retry?.constant || module.retry?.exponential
</script>
<div class="flex flex-row space-x-2">
<div class="flex flex-row space-x-2" bind:clientWidth={width}>
{#if !shouldPick}
<span
class={classNames('badge', module.stop_after_if ? 'badge-on' : 'badge-off')}
@@ -44,7 +44,7 @@
</div>
</FlowModuleSchemaItem>
<div class="flex flex-row w-full">
<div class="w-8 shrink-0 " />
<div class="w-8 shrink-0 line" />
<div class="grow my-4 overflow-auto">
<div class="w-full pr-1">
<FlowModuleSchemaMap bind:modules={mod.value.modules} color="orange" />
@@ -96,11 +96,11 @@ function getFlowInput(
if (parentModule.value.type === 'forloopflow') {
return {
...parentFlowInput,
iter: {
value: "Iteration's value",
index: "Iteration's index"
}
},
...parentFlowInput,
}
} else {
// Branches
@@ -52,7 +52,7 @@
export let isCollapsed: boolean = false
</script>
<div class="flex-1 flex flex-col py-4 overflow-x-hidden scrollbar-hide">
<div class="flex-1 flex flex-col py-4 overflow-x-hidden scrollbar-hidden">
<nav class="h-full flex justify-between flex-col px-2">
<div class="space-y-2">
{#each mainMenuLinks as menuLink}
@@ -71,16 +71,3 @@
</div>
</nav>
</div>
<style>
/* For Webkit-based browsers (Chrome, Safari and Opera) */
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* For IE, Edge and Firefox */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
</style>
+1 -1
View File
@@ -168,7 +168,7 @@
</ActionRow>
{/if}
<CenteredPage>
<div class="flex flex-row flex-wrap justify-between items-center gap-4 pb-4">
<div class="flex flex-row flex-wrap justify-between items-center gap-4 py-4">
<h1>
<div>
{#if job}