From d44976f35e45ade510d1ec220b5a1503e11f3db9 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 11 Nov 2024 10:05:18 +0100 Subject: [PATCH] feat: expandable subflows in flows (#4683) * all * nit right panel flow props * nits --- .../src/lib/components/FlowGraphViewer.svelte | 3 + .../components/FlowStatusViewerInner.svelte | 125 ++++++++++++++-- .../src/lib/components/FlowTimeline.svelte | 2 +- .../flows/content/FlowEditorPanel.svelte | 4 + .../flows/content/FlowModuleComponent.svelte | 8 +- .../flows/content/FlowModuleHeader.svelte | 28 ++++ .../flows/map/FlowModuleSchemaItem.svelte | 13 +- .../flows/map/FlowModuleSchemaMap.svelte | 4 +- .../lib/components/flows/map/MapItem.svelte | 1 - .../components/flows/map/VirtualItem.svelte | 2 +- .../lib/components/graph/FlowGraphV2.svelte | 85 +++-------- .../src/lib/components/graph/graphBuilder.ts | 137 ++++++++++++++---- .../graph/renderers/nodes/ModuleNode.svelte | 15 +- .../graph/renderers/nodes/SubflowBound.svelte | 45 ++++++ .../[job]/[resume]/[hmac]/+page.svelte | 1 + frontend/src/routes/view_graph/+page.svelte | 9 +- 16 files changed, 367 insertions(+), 115 deletions(-) create mode 100644 frontend/src/lib/components/graph/renderers/nodes/SubflowBound.svelte diff --git a/frontend/src/lib/components/FlowGraphViewer.svelte b/frontend/src/lib/components/FlowGraphViewer.svelte index 1ecef2943f..326e1275b6 100644 --- a/frontend/src/lib/components/FlowGraphViewer.svelte +++ b/frontend/src/lib/components/FlowGraphViewer.svelte @@ -7,6 +7,7 @@ import FlowGraphViewerStep from './FlowGraphViewerStep.svelte' import FlowGraphV2 from './graph/FlowGraphV2.svelte' import { dfs } from './flows/dfs' + import { workspaceStore } from '$lib/stores' export let flow: { summary: string @@ -22,6 +23,7 @@ export let noGraph = false export let triggerNode = false export let stepDetail: FlowModule | string | undefined = undefined + export let workspace: string | undefined = $workspaceStore const dispatch = createEventDispatcher() @@ -37,6 +39,7 @@ path={flow?.path} {download} minHeight={400} + {workspace} modules={flow?.value?.modules} failureModule={flow?.value?.failure_module} preprocessorModule={flow?.value?.preprocessor_module} diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index ec6982a5df..2c9aa54461 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -29,6 +29,7 @@ import Alert from './common/alert/Alert.svelte' import FlowGraphViewerStep from './FlowGraphViewerStep.svelte' import FlowGraphV2 from './graph/FlowGraphV2.svelte' + import { buildPrefix } from './graph/graphBuilder' const dispatch = createEventDispatcher() @@ -74,6 +75,10 @@ export let wideResults = false export let hideFlowResult = false + export let workspace: string | undefined = $workspaceStore + export let prefix: string | undefined = undefined + export let subflowParentsGlobalModuleStates: Writable>[] = [] + export let subflowParentsDurationStatuses: Writable>[] = [] let jobResults: any[] = flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? [] @@ -83,6 +88,7 @@ let localModuleStates: Writable> = writable({}) let localDurationStatuses: Writable> = writable({}) + let expandedSubflows: Record = {} export let job: Job | undefined = undefined @@ -94,6 +100,24 @@ // } // } + function updateModuleStates( + moduleState: Writable>, + key: string, + newValue: GraphModuleState, + keepType: boolean | undefined + ) { + moduleState.update((x) => { + if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) { + newValue.type = x[key].type + } + x[key] = newValue + return x + }) + } + + function buildSubflowKey(key: string, prefix: string | undefined) { + return prefix ? 'subflow:' + prefix + key : key + } function setModuleState( key: string, value: Partial, @@ -102,15 +126,14 @@ ) { let newValue = { ...($localModuleStates[key] ?? {}), ...value } if (!deepEqual($localModuleStates[key], value) || force) { - ;[localModuleStates, ...globalModuleStates].forEach((s) => { - s.update((x) => { - if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) { - newValue.type = x[key].type - } - x[key] = newValue - return x - }) - }) + ;[localModuleStates, ...globalModuleStates].forEach((s) => + updateModuleStates(s, key, newValue, keepType) + ) + if (prefix) { + subflowParentsGlobalModuleStates.forEach((s) => + updateModuleStates(s, buildSubflowKey(key, prefix), newValue, keepType) + ) + } } } @@ -124,6 +147,14 @@ return x }) }) + if (prefix) { + subflowParentsDurationStatuses.forEach((s) => { + s.update((x) => { + x[buildSubflowKey(key, prefix)].byJob[id] = value + return x + }) + }) + } } } @@ -131,15 +162,25 @@ if ($localDurationStatuses[modId] == undefined) { $localDurationStatuses[modId] = { byJob: {} } } - let prefixed = modId globalDurationStatuses.forEach((x) => x.update((x) => { - if (x[prefixed] == undefined) { - x[prefixed] = { byJob: {} } + if (x[modId] == undefined) { + x[modId] = { byJob: {} } } return x }) ) + if (prefix) { + subflowParentsDurationStatuses.forEach((x) => + x.update((x) => { + let key = buildSubflowKey(modId, prefix) + if (x[key] == undefined) { + x[key] = { byJob: {} } + } + return x + }) + ) + } } let innerModules: FlowStatusModule[] = [] @@ -368,7 +409,7 @@ if (globalRefreshes) { let modId = flowJobIds?.moduleId if (modId) { - globalRefreshes[modId] = async (loopJob) => { + globalRefreshes[buildSubflowKey(modId, prefix)] = async (loopJob) => { setIteration(loopJob.index, loopJob.job, false, modId ?? '') refresh(true, loopJob) } @@ -631,6 +672,36 @@ }) } } + + function allModulesForTimeline( + modules: FlowModule[], + expandedSubflows: Record + ): string[] { + const ids = dfs(modules, (x) => x.id) + + function rec(ids: string[], prefix: string | undefined): string[] { + return ids.concat( + ids.flatMap((id) => { + let fms = expandedSubflows[id] + let oid = id.split(':').pop() + if (!oid) { + return [] + } + let nprefix = buildPrefix(prefix, oid) + return fms + ? rec( + dfs(fms, (x) => + x.id.startsWith('subflow:') ? x.id : buildSubflowKey(x.id, nprefix) + ), + nprefix + ) + : [] + }) + ) + } + + return rec(ids, undefined) + } {#if notAnonynmous} @@ -803,6 +874,9 @@ job={storedListJobs[j]} globalModuleStates={[localModuleStates, ...globalModuleStates]} globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} render={forloop_selected == loopJobId && selected == 'sequence' && render} reducedPolling={flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20} {workspaceId} @@ -880,6 +954,9 @@ {childFlow} globalModuleStates={[localModuleStates, ...globalModuleStates]} globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} render={failedRetry == retry_selected && render} reducedPolling={false} {workspaceId} @@ -895,6 +972,17 @@ bind:refresh={recursiveRefresh[mod.job ?? '']} globalModuleStates={[]} globalDurationStatuses={[]} + prefix={buildPrefix(prefix, mod.id ?? '')} + subflowParentsGlobalModuleStates={[ + localModuleStates, + ...globalModuleStates, + ...subflowParentsGlobalModuleStates + ]} + subflowParentsDurationStatuses={[ + localDurationStatuses, + ...globalDurationStatuses, + ...subflowParentsDurationStatuses + ]} render={selected == 'sequence' && render} {workspaceId} jobId={mod.job} @@ -915,6 +1003,9 @@ globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} render={selected == 'sequence' && render} {workspaceId} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} jobId={mod.job} innerModule={mod.flow_jobs ? job.raw_flow?.modules[i]?.value : undefined} flowJobIds={mod.flow_jobs @@ -973,6 +1064,7 @@ minHeight={wrapperHeight} success={jobId != undefined && isSuccess(job?.['success'])} flowModuleStates={$localModuleStates} + bind:expandedSubflows on:select={(e) => { if (rightColumnSelect != 'node_definition') { rightColumnSelect = 'node_status' @@ -1004,11 +1096,13 @@ selectedForloopIndex: detail.index }) globalRefreshes[detail.moduleId]?.({ job: detail.id, index: detail.index }) + // console.log('selectedIteration', prefix, detail.moduleId, globalRefreshes) }} modules={job.raw_flow?.modules ?? []} failureModule={job.raw_flow?.failure_module} preprocessorModule={job.raw_flow?.preprocessor_module} allowSimplifiedPoll={false} + {workspace} />
x.id)} + flowModules={allModulesForTimeline( + job?.raw_flow?.modules ?? [], + expandedSubflows ?? {} + )} durationStatuses={localDurationStatuses} /> {:else if rightColumnSelect == 'node_status'} diff --git a/frontend/src/lib/components/FlowTimeline.svelte b/frontend/src/lib/components/FlowTimeline.svelte index 3f918c8c6b..4027eca9c3 100644 --- a/frontend/src/lib/components/FlowTimeline.svelte +++ b/frontend/src/lib/components/FlowTimeline.svelte @@ -178,7 +178,7 @@ {/if}
-
{k}
+
{k.startsWith('subflow:') ? k.substring(8) : k}
{#if min && total}
diff --git a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte index 4993afa0f9..4da8380fa6 100644 --- a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte +++ b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte @@ -78,6 +78,10 @@ newItem={newFlow} isFlow={true} /> +{:else if $selectedId.startsWith('subflow:')} +
Selected step is witin an expanded subflow and is not directly editable in the flow editor
{:else} {@const dup = checkDup($flowStore.value.modules)} {#if dup} diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 790758ca0e..97801d24ab 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -253,6 +253,10 @@ forceReload++ await reload(flowModule) } + if (flowModule.value.type == 'flow') { + forceReload++ + await reload(flowModule) + } }} on:createScriptFromInlineScript={async () => { const [module, state] = await createScriptFromInlineScript( @@ -361,7 +365,9 @@
{/if} {:else if flowModule.value.type === 'flow'} - + {#key forceReload} + + {/key} {/if} diff --git a/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte b/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte index 434cf2451f..a044fadada 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte @@ -5,10 +5,12 @@ import { Bed, Database, + ExternalLink, Gauge, GitFork, Pen, PhoneIncoming, + RefreshCcw, Repeat, Save, Square, @@ -139,6 +141,32 @@ {/if} {/if} + {#if module.value.type === 'flow'} + + {/if} + {:else if id?.startsWith('subflow:')} + + {id.substring('subflow:'.length)} {/if}
diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index 28167493cf..220b597384 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -26,7 +26,7 @@ import { getDependentComponents } from '../flowExplorer' import type { FlowCopilotContext } from '$lib/components/copilot/flow' import { fade } from 'svelte/transition' - import { copilotInfo, tutorialsToDo } from '$lib/stores' + import { copilotInfo, tutorialsToDo, workspaceStore } from '$lib/stores' import FlowTutorials from '$lib/components/FlowTutorials.svelte' import { ignoredTutorials } from '$lib/components/tutorials/ignoredTutorials' @@ -44,6 +44,7 @@ export let disableSettings = false export let newFlow: boolean = false export let smallErrorHandler = false + export let workspace: string | undefined = $workspaceStore let flowTutorials: FlowTutorials | undefined = undefined @@ -345,6 +346,7 @@ preprocessorModule={$flowStore.value?.preprocessor_module} {selectedId} {flowInputsStore} + {workspace} on:delete={({ detail }) => { let e = detail.detail dependents = getDependentComponents(e.id, $flowStore) diff --git a/frontend/src/lib/components/flows/map/MapItem.svelte b/frontend/src/lib/components/flows/map/MapItem.svelte index ebd4ffdb9b..05cfc49d90 100644 --- a/frontend/src/lib/components/flows/map/MapItem.svelte +++ b/frontend/src/lib/components/flows/map/MapItem.svelte @@ -20,7 +20,6 @@ export let modules: FlowModule[] export let moving: string | undefined = undefined export let duration_ms: number | undefined = undefined - export let isTrigger: boolean = false export let retries: number | undefined = undefined export let flowJobs: diff --git a/frontend/src/lib/components/flows/map/VirtualItem.svelte b/frontend/src/lib/components/flows/map/VirtualItem.svelte index a2e022e9ef..3f5350b7ed 100644 --- a/frontend/src/lib/components/flows/map/VirtualItem.svelte +++ b/frontend/src/lib/components/flows/map/VirtualItem.svelte @@ -54,7 +54,7 @@
{preLabel}
{/if}
- {#if id && !hideId} + {#if id && !hideId && !id?.startsWith('subflow:')}
{id} diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index caf28e43ce..d09d22cac7 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -1,5 +1,5 @@ + {#if data.module.value.type == 'flow'} + + {/if} + import VirtualItem from '$lib/components/flows/map/VirtualItem.svelte' + import NodeWrapper from './NodeWrapper.svelte' + import { Minimize2 } from 'lucide-svelte' + import type { GraphModuleState } from '../../model' + import { getStateColor } from '../../util' + import type { FlowModule } from '$lib/gen' + import type { GraphEventHandlers } from '../../graphBuilder' + + export let data: { + label: string + preLabel: string | undefined + insertable: boolean + flowModuleStates: Record | undefined + subflowId: string + id: string + modules: FlowModule[] + selected: boolean + eventHandlers: GraphEventHandlers + offset: number + } + + + + { + data.eventHandlers.select(data.id) + }} + /> + + diff --git a/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte b/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte index 2175c75009..eedeca636c 100644 --- a/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte +++ b/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte @@ -284,6 +284,7 @@

Flow details

import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte' + import { workspaceStore } from '$lib/stores' import { decodeState } from '$lib/utils' let content = localStorage.getItem('svelvet') @@ -8,7 +9,13 @@ : { modules: [], failureModule: undefined, preprocessorModule: undefined } - +