From c9ae5905a8dbd80aeb014bcefb466e03065782ce Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Wed, 14 Sep 2022 12:34:51 +0200 Subject: [PATCH] feat(frontend): Flow UX entire rework (#552) * feature(frontend): Flow rework WIP * feature(frontend): Right panel behavior done * feature(frontend): Split panel working * feature(frontend): Flows working * feature(frontend): Add inputs + adapt style + remove duplicate library * feature(frontend): remove old implementation * feature(frontend): revert package-lock * feature(frontend): revert old FlowBuilder component * feature(frontend): Fix margins + add remove button on the minimap * feature(frontend): Fix wording * feature(frontend): add PR UI comments * feature(frontend): Display the module title * feature(frontend): Previews working * feature(frontend): Fix schedule load + update * feature(frontend): fix build * feature(frontend): UI fix * fix just this step * feature(frontend): for loop iterator and skip failures * just this step * script helpers Co-authored-by: Ruben Fiszel --- frontend/src/lib/components/EditorBar.svelte | 2 +- .../src/lib/components/FlowBuilder.svelte | 211 +++++++++--------- frontend/src/lib/components/FlowEditor.svelte | 65 ------ .../src/lib/components/FlowPreview.svelte | 105 +++------ .../lib/components/FlowPreviewContent.svelte | 83 ++++++- .../lib/components/InputTransformForm.svelte | 2 +- frontend/src/lib/components/ModuleStep.svelte | 154 ------------- frontend/src/lib/components/RunForm.svelte | 4 +- .../src/lib/components/flows/FlowBox.svelte | 19 -- .../lib/components/flows/FlowBoxHeader.svelte | 18 -- .../lib/components/flows/FlowEditor.svelte | 25 +++ .../components/flows/FlowModuleHeader.svelte | 116 ---------- .../lib/components/flows/FlowSettings.svelte | 169 -------------- .../lib/components/flows/FlowTimeline.svelte | 186 --------------- .../components/flows/common/FlowCard.svelte | 12 + .../flows/common/FlowCardHeader.svelte | 31 +++ .../{ => content}/CopyFirstStepSchema.svelte | 4 +- .../{ => content}/DynamicInputHelpBox.svelte | 0 .../flows/content/FlowEditorPanel.svelte | 24 ++ .../flows/{ => content}/FlowInput.svelte | 14 +- .../flows/{ => content}/FlowInputs.svelte | 8 +- .../components/flows/content/FlowLoop.svelte | 36 +++ .../flows/content/FlowModule.svelte | 173 ++++++++++++++ .../flows/content/FlowModuleHeader.svelte | 70 ++++++ .../flows/content/FlowModuleWrapper.svelte | 48 ++++ .../flows/content/FlowSchedules.svelte | 20 ++ .../flows/content/FlowSettings.svelte | 50 +++++ .../src/lib/components/flows/flowState.ts | 63 +++--- .../lib/components/flows/flowStateUtils.ts | 22 +- .../src/lib/components/flows/flowStore.ts | 19 +- .../flows/header/FlowEditorHeader.svelte | 27 +++ .../flows/header/FlowPreviewButtons.svelte | 46 ++++ .../components/flows/header/FlowStatus.svelte | 19 ++ .../flows/map/FlowModuleSchemaItem.svelte | 61 +++++ .../flows/map/FlowModuleSchemaMap.svelte | 141 ++++++++++++ .../lib/components/flows/stepOpenedStore.ts | 8 - frontend/src/lib/components/flows/types.ts | 9 + frontend/src/lib/components/flows/utils.ts | 16 +- frontend/src/lib/script_helpers.ts | 8 +- frontend/src/routes/__layout@root.svelte | 2 +- frontend/src/routes/flows/add.svelte | 5 +- .../src/routes/flows/edit/[...path].svelte | 5 +- 42 files changed, 1097 insertions(+), 1003 deletions(-) delete mode 100644 frontend/src/lib/components/FlowEditor.svelte delete mode 100644 frontend/src/lib/components/ModuleStep.svelte delete mode 100644 frontend/src/lib/components/flows/FlowBox.svelte delete mode 100644 frontend/src/lib/components/flows/FlowBoxHeader.svelte create mode 100644 frontend/src/lib/components/flows/FlowEditor.svelte delete mode 100644 frontend/src/lib/components/flows/FlowModuleHeader.svelte delete mode 100644 frontend/src/lib/components/flows/FlowSettings.svelte delete mode 100644 frontend/src/lib/components/flows/FlowTimeline.svelte create mode 100644 frontend/src/lib/components/flows/common/FlowCard.svelte create mode 100644 frontend/src/lib/components/flows/common/FlowCardHeader.svelte rename frontend/src/lib/components/flows/{ => content}/CopyFirstStepSchema.svelte (66%) rename frontend/src/lib/components/flows/{ => content}/DynamicInputHelpBox.svelte (100%) create mode 100644 frontend/src/lib/components/flows/content/FlowEditorPanel.svelte rename frontend/src/lib/components/flows/{ => content}/FlowInput.svelte (57%) rename frontend/src/lib/components/flows/{ => content}/FlowInputs.svelte (91%) create mode 100644 frontend/src/lib/components/flows/content/FlowLoop.svelte create mode 100644 frontend/src/lib/components/flows/content/FlowModule.svelte create mode 100644 frontend/src/lib/components/flows/content/FlowModuleHeader.svelte create mode 100644 frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte create mode 100644 frontend/src/lib/components/flows/content/FlowSchedules.svelte create mode 100644 frontend/src/lib/components/flows/content/FlowSettings.svelte create mode 100644 frontend/src/lib/components/flows/header/FlowEditorHeader.svelte create mode 100644 frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte create mode 100644 frontend/src/lib/components/flows/header/FlowStatus.svelte create mode 100644 frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte create mode 100644 frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte delete mode 100644 frontend/src/lib/components/flows/stepOpenedStore.ts create mode 100644 frontend/src/lib/components/flows/types.ts diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index dfebf91124..5cbd1cf716 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -171,7 +171,7 @@ -
+
- - - - - -
- {#if step == 1} - - - {:else} - - - {/if} -
-
-
- - {#if initialPath && initialPath != $flowStore?.path} {initialPath} → {/if} - {$flowStore?.path} - -
- - - - {#if $flowStateStore} - {#if step === 1} - - + + + + + +
+ {#if step == 1} + + + {:else} + + + {/if} +
+
- - - {:else if step === 2} + + + {#if $flowStateStore} + {#if step === 1} + + {:else if step === 2} + - {/if} - {:else} -

Loading

+
{/if} -
+ {:else} + Loading... + {/if} - -{#if $flowStateStore && $flowStore} - - (previewOpen = !previewOpen)} /> - -{/if} diff --git a/frontend/src/lib/components/FlowEditor.svelte b/frontend/src/lib/components/FlowEditor.svelte deleted file mode 100644 index 450cca0be2..0000000000 --- a/frontend/src/lib/components/FlowEditor.svelte +++ /dev/null @@ -1,65 +0,0 @@ - - -{#if $flowStateStore} -
- -
- -
-
-
-{:else} -

Loading flow

-{/if} diff --git a/frontend/src/lib/components/FlowPreview.svelte b/frontend/src/lib/components/FlowPreview.svelte index 6fe7b41266..366e26f3b7 100644 --- a/frontend/src/lib/components/FlowPreview.svelte +++ b/frontend/src/lib/components/FlowPreview.svelte @@ -2,52 +2,41 @@ import type { Schema } from '$lib/common' import type { Flow } from '$lib/gen' import { sendUserToast, truncateRev } from '$lib/utils' - import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons' - import Icon from 'svelte-awesome' + import { flowStateStore, flowStateToFlow } from './flows/flowState' import { mapJobResultsToFlowState } from './flows/flowStateUtils' import { runFlowPreview } from './flows/utils' import FlowStatusViewer from './FlowStatusViewer.svelte' import RunForm from './RunForm.svelte' - import Tab from './common/tabs/Tab.svelte' - import TabContent from './common/tabs/TabContent.svelte' - import Tabs from './common/tabs/Tabs.svelte' - export let i: number + export let indexes: string export let flow: Flow export let schema: Schema - export let args: Record = {} + const [i, j] = indexes.split('-').map(Number) let stepArgs: Record = {} - - let tab: 'upto' | 'justthis' = 'upto' - let viewPreview = false - - let uptoText = - i >= flow.value.modules.length - 1 ? 'Preview whole flow' : 'Preview up to this step' let jobId: string export async function runPreview(args: any) { - viewPreview = true flow = flowStateToFlow($flowStateStore, flow) - let newFlow: Flow = - tab == 'upto' ? truncateFlow(flow) : setInputTransformFromArgs(extractStep(flow), args) + let newFlow: Flow = setInputTransformFromArgs(extractStep(flow), args) jobId = await runFlowPreview(args, newFlow) sendUserToast(`started preview ${truncateRev(jobId, 10)}`) } - function truncateFlow(flow: Flow): Flow { - const localFlow = JSON.parse(JSON.stringify(flow)) - localFlow.value.modules = flow.value.modules.slice(0, i + 1) - return localFlow - } - function extractStep(flow: Flow): Flow { const localFlow = JSON.parse(JSON.stringify(flow)) - localFlow.value.modules = flow.value.modules.slice(i, i + 1) + const mod = flow.value.modules[i].value + console.log(mod, j) + if (j != undefined && mod.type === 'forloopflow') { + localFlow.value.modules = mod.modules.slice(j, j + 1) + } else { + localFlow.value.modules = flow.value.modules.slice(i, i + 1) + } + console.log(localFlow) localFlow.schema = schema return localFlow } @@ -65,59 +54,21 @@ } - + runPreview(args)} + schedulable={false} + buttonText="Test just this step" + detailed={false} + args={stepArgs} +/> -{#if viewPreview} - {#if i != flow.value.modules.length} -
- - {uptoText} - Preview just this step - - - runPreview(args)} - schedulable={false} - buttonText={uptoText} - detailed={false} - bind:args - /> - - - runPreview(args)} - schedulable={false} - buttonText="Preview just this step" - detailed={false} - args={stepArgs} - /> - - - -
- {/if} - - {#if jobId} -
- mapJobResultsToFlowState(e.detail, tab, i)} - root={true} - /> -
- {/if} +{#if jobId} +
+ mapJobResultsToFlowState(e.detail, 'justthis', i, j)} + root={true} + /> +
{/if} diff --git a/frontend/src/lib/components/FlowPreviewContent.svelte b/frontend/src/lib/components/FlowPreviewContent.svelte index 73b729a1c1..281c7b7a5d 100644 --- a/frontend/src/lib/components/FlowPreviewContent.svelte +++ b/frontend/src/lib/components/FlowPreviewContent.svelte @@ -4,27 +4,70 @@ import { faClose, faPlay } from '@fortawesome/free-solid-svg-icons' import { Button } from 'flowbite-svelte' - import { createEventDispatcher, onDestroy } from 'svelte' + import { createEventDispatcher, getContext, onDestroy } from 'svelte' import Icon from 'svelte-awesome' - import { flowStateStore, flowStateToFlow } from './flows/flowState' + import { flowStateStore, flowStateToFlow, type FlowModuleSchema } from './flows/flowState' import { mapJobResultsToFlowState } from './flows/flowStateUtils' import { flowStore } from './flows/flowStore' + import type { FlowEditorContext } from './flows/types' import { runFlowPreview } from './flows/utils' import FlowStatusViewer from './FlowStatusViewer.svelte' import SchemaForm from './SchemaForm.svelte' - export let args: Record = {} + export let previewMode: 'upTo' | 'whole' + + let args: Record = {} let jobId: string | undefined = undefined let isValid: boolean = false let intervalState: 'idle' | 'canceled' | 'done' | 'running' = 'idle' - $: newFlow = flowStateToFlow($flowStateStore, $flowStore) - $: steps = newFlow?.value.modules.length ?? 0 + const { selectedId } = getContext('FlowEditorContext') + + function extractFlow(previewMode: 'upTo' | 'whole') { + if (previewMode === 'whole') { + return flowStateToFlow($flowStateStore, $flowStore) + } else { + const [parentIndex, childIndex] = $selectedId.split('-') + + if (childIndex === undefined) { + const modules = $flowStateStore.modules.slice(0, Number(parentIndex) + 1) + const flowState = { + modules: modules, + failureModule: $flowStateStore.failureModule + } + return flowStateToFlow(flowState, $flowStore) + } else { + const modules = $flowStateStore.modules.slice(0, Number(parentIndex) + 1) + const flowModuleSchemas: FlowModuleSchema[] = JSON.parse(JSON.stringify(modules)) + + const flowModuleSchema = flowModuleSchemas[modules.length - 1] + + flowModuleSchemas[modules.length - 1] = { + ...flowModuleSchemas[modules.length - 1], + childFlowModules: flowModuleSchema.childFlowModules!.slice(0, Number(childIndex) + 1) + } + + if (flowModuleSchema.flowModule.value.type === 'forloopflow') { + flowModuleSchema.flowModule.value.modules = + flowModuleSchema.flowModule.value.modules.slice(0, Number(childIndex) + 1) + + flowModuleSchemas[modules.length - 1].flowModule = flowModuleSchema.flowModule + } + + const flowState = { + modules: flowModuleSchemas, + failureModule: $flowStateStore.failureModule + } + return flowStateToFlow(flowState, $flowStore) + } + } + } const dispatch = createEventDispatcher() export async function runPreview(args: Record) { + const newFlow = extractFlow(previewMode) jobId = await runFlowPreview(args, newFlow) intervalState = 'running' @@ -37,19 +80,32 @@
-
-
+
+
-

Flow preview

+

+ Test preview - {previewMode === 'upTo' + ? `up to step ${$selectedId.split('-').join(',')}` + : ' whole flow'} +

-
-
- +
+
+ +
{#if intervalState === 'running'} -
-
-

- Move the focus outside of the text editor to recompute the input schema or press - Ctrl/Cmd+S -

-
- {/if} - {#if !shouldPick} -

Step inputs

- - {/if} - - {#if !shouldPick} -
-
- -
- {/if} -
- {/if} -
- diff --git a/frontend/src/lib/components/RunForm.svelte b/frontend/src/lib/components/RunForm.svelte index c286b05e03..162337bfd2 100644 --- a/frontend/src/lib/components/RunForm.svelte +++ b/frontend/src/lib/components/RunForm.svelte @@ -69,9 +69,7 @@ {#if !runnable.schema.properties || Object.keys(runnable.schema.properties).length === 0}
No arguments
{:else} -
- -
+ {/if} {:else}
No schema
diff --git a/frontend/src/lib/components/flows/FlowBox.svelte b/frontend/src/lib/components/flows/FlowBox.svelte deleted file mode 100644 index 8e3b14a4fc..0000000000 --- a/frontend/src/lib/components/flows/FlowBox.svelte +++ /dev/null @@ -1,19 +0,0 @@ - - -
- dispatch('clickheader')} {title}> - - - {#if slots.content} - - {/if} -
diff --git a/frontend/src/lib/components/flows/FlowBoxHeader.svelte b/frontend/src/lib/components/flows/FlowBoxHeader.svelte deleted file mode 100644 index 36a4e5edac..0000000000 --- a/frontend/src/lib/components/flows/FlowBoxHeader.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - -
dispatch('click')} - class="flex items-center justify-between flex-wrap px-6 py-2" - class:cursor-pointer={clickable} -> - {#if title} -

{title}

- {/if} - -
diff --git a/frontend/src/lib/components/flows/FlowEditor.svelte b/frontend/src/lib/components/flows/FlowEditor.svelte new file mode 100644 index 0000000000..0031b40b08 --- /dev/null +++ b/frontend/src/lib/components/flows/FlowEditor.svelte @@ -0,0 +1,25 @@ + + + + + +
+ + +
+ +
+
+ +
+ +
+
+
+
diff --git a/frontend/src/lib/components/flows/FlowModuleHeader.svelte b/frontend/src/lib/components/flows/FlowModuleHeader.svelte deleted file mode 100644 index 5878cae35f..0000000000 --- a/frontend/src/lib/components/flows/FlowModuleHeader.svelte +++ /dev/null @@ -1,116 +0,0 @@ - - -
-

- - {#if 'path' in mod.value && mod.value.path} - - {:else if 'language' in mod.value && mod.value.language} - Inline {mod.value.language} - {:else} - Select a script - {/if} - - {#if ('path' in mod.value && mod.value.path) || ('language' in mod.value && mod.value.language)} - undefined} - class="overflow-x-auto" - type="text" - bind:value={mod.summary} - placeholder="Summary" - /> - {/if} -

-
- -
undefined}> - {#if mod.value.type === 'script' && !shouldPick} - - - {/if} - - {#if mod.value.type === 'rawscript' && !shouldPick} - - {/if} - - {#if opened} - - {:else} - - {/if} -
- - -
Script {'path' in mod?.value ? mod?.value.path : ''}
-
- -
-
diff --git a/frontend/src/lib/components/flows/FlowSettings.svelte b/frontend/src/lib/components/flows/FlowSettings.svelte deleted file mode 100644 index 1b379f3da1..0000000000 --- a/frontend/src/lib/components/flows/FlowSettings.svelte +++ /dev/null @@ -1,169 +0,0 @@ - - - -
Import JSON
-
- -
-
- -
-
- - -
See JSON
-
- -
-
- - -
-
- - -
- { - jsonSetter.openModal() - }} - > - - Import from a JSON OpenFlow - - { - jsonViewer.openModal() - }} - > - - Export to a JSON OpenFlow - - { - const url = new URL('https://hub.windmill.dev/flows/add') - const openFlow = { - value: $flowStore.value, - summary: $flowStore.summary, - description: $flowStore.description, - schema: $flowStore.schema - } - url.searchParams.append('flow', btoa(JSON.stringify(openFlow))) - window.open(url, '_blank')?.focus() - }} - > - - Publish to Hub - -
-
-
-
- -
-
- -
- Flow permissions depend on their path. Select the group all - to share your flow, and user to keep it private. - docs -
-
- -