feat: expandable subflows in flows (#4683)

* all

* nit right panel flow props

* nits
This commit is contained in:
Ruben Fiszel
2024-11-11 10:05:18 +01:00
committed by GitHub
parent 02170032af
commit d44976f35e
16 changed files with 367 additions and 115 deletions
@@ -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()
</script>
@@ -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}
@@ -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<Record<string, GraphModuleState>>[] = []
export let subflowParentsDurationStatuses: Writable<Record<string, DurationStatus>>[] = []
let jobResults: any[] =
flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? []
@@ -83,6 +88,7 @@
let localModuleStates: Writable<Record<string, GraphModuleState>> = writable({})
let localDurationStatuses: Writable<Record<string, DurationStatus>> = writable({})
let expandedSubflows: Record<string, FlowModule[]> = {}
export let job: Job | undefined = undefined
@@ -94,6 +100,24 @@
// }
// }
function updateModuleStates(
moduleState: Writable<Record<string, GraphModuleState>>,
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<GraphModuleState>,
@@ -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, FlowModule[]>
): 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)
}
</script>
{#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}
/>
</div>
<div
@@ -1033,7 +1127,10 @@
aggregateWaitTime={job?.aggregate_wait_time_ms}
flowDone={job?.['success'] != undefined}
bind:this={flowTimeline}
flowModules={dfs(job.raw_flow?.modules ?? [], (x) => x.id)}
flowModules={allModulesForTimeline(
job?.raw_flow?.modules ?? [],
expandedSubflows ?? {}
)}
durationStatuses={localDurationStatuses}
/>
{:else if rightColumnSelect == 'node_status'}
@@ -178,7 +178,7 @@
{/if}
<div class="px-2 py-2 grid grid-cols-6 w-full">
<div class="truncate">{k}</div>
<div class="truncate">{k.startsWith('subflow:') ? k.substring(8) : k}</div>
<div class="col-span-5 flex min-h-6">
{#if min && total}
<div class="flex flex-col gap-2 w-full p-2 ml-4">
@@ -78,6 +78,10 @@
newItem={newFlow}
isFlow={true}
/>
{:else if $selectedId.startsWith('subflow:')}
<div class="p-4"
>Selected step is witin an expanded subflow and is not directly editable in the flow editor</div
>
{:else}
{@const dup = checkDup($flowStore.value.modules)}
{#if dup}
@@ -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 @@
</div>
{/if}
{:else if flowModule.value.type === 'flow'}
<FlowPathViewer path={flowModule.value.path} />
{#key forceReload}
<FlowPathViewer path={flowModule.value.path} />
{/key}
{/if}
</Pane>
<Pane bind:size={editorSettingsPanelSize} minSize={20}>
@@ -5,10 +5,12 @@
import {
Bed,
Database,
ExternalLink,
Gauge,
GitFork,
Pen,
PhoneIncoming,
RefreshCcw,
Repeat,
Save,
Square,
@@ -139,6 +141,32 @@
</Button>
{/if}
{/if}
{#if module.value.type === 'flow'}
<Button
size="xs"
color="light"
on:click={async () => {
if (module.value.type == 'flow') {
window.open(`/flows/edit/${module.value.path}`, '_blank', 'noopener,noreferrer')
}
}}
startIcon={{ icon: Pen }}
iconOnly={false}
>
Edit <ExternalLink size={12} />
</Button>
<Button
size="xs"
color="light"
on:click={async () => {
dispatch('reload')
}}
startIcon={{
icon: RefreshCcw
}}
iconOnly={true}
/>
{/if}
<div class="px-0.5" />
{#if module.value.type === 'rawscript'}
<FlowModuleWorkerTagSelect bind:tag={module.value.tag} />
@@ -276,8 +276,13 @@
</Popover>
<div class="flex items-center space-x-2 relative max-w-[25%]" bind:clientWidth={idBadgeWidth}>
{#if id && id !== 'preprocessor' && !id.startsWith('failure')}
<Badge color="indigo" wrapperClass="max-w-full" baseClass="max-w-full truncate" title={id}>
{#if id && id !== 'preprocessor' && !id.startsWith('failure') && !id.startsWith('subflow:')}
<Badge
color="indigo"
wrapperClass="max-w-full"
baseClass="max-w-full truncate !px-1"
title={id}
>
<span class="max-w-full text-2xs truncate">{id}</span></Badge
>
{#if deletable}
@@ -290,6 +295,10 @@ hover:border-blue-700 hover:!visible {hover ? '' : '!hidden'}"
title="Edit Id"><Pencil size={14} /></button
>
{/if}
{:else if id?.startsWith('subflow:')}
<Badge color="blue" wrapperClass="max-w-full" baseClass="!px-1" title={id}>
<span class="max-w-full text-2xs truncate">{id.substring('subflow:'.length)}</span></Badge
>
{/if}
</div>
</div>
@@ -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)
@@ -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:
@@ -54,7 +54,7 @@
<div class="truncate text-2xs text-center"><pre>{preLabel}</pre></div>
{/if}
</div>
{#if id && !hideId}
{#if id && !hideId && !id?.startsWith('subflow:')}
<div class="flex items-center shrink min-w-0">
<Badge color="indigo" wrapperClass="w-full" baseClass="max-w-full" title={id}>
<span class="max-w-full text-2xs truncate">{id}</span>
@@ -1,5 +1,5 @@
<script lang="ts">
import { type FlowModule } from '../../gen'
import { FlowService, type FlowModule } from '../../gen'
import { NODE, type GraphModuleState } from '.'
import { createEventDispatcher, getContext, onDestroy, onMount, setContext } from 'svelte'
@@ -39,6 +39,8 @@
import FlowYamlEditor from '../flows/header/FlowYamlEditor.svelte'
import BranchOneEndNode from './renderers/nodes/branchOneEndNode.svelte'
import type { TriggerContext } from '../triggers'
import { workspaceStore } from '$lib/stores'
import SubflowBound from './renderers/nodes/SubflowBound.svelte'
export let success: boolean | undefined = undefined
export let modules: FlowModule[] | undefined = []
@@ -65,6 +67,7 @@
undefined
)
export let triggerNode = false
export let workspace: string = $workspaceStore ?? 'NO_WORKSPACE'
let useDataflow: Writable<boolean | undefined> = writable<boolean | undefined>(false)
@@ -85,6 +88,8 @@
let simplifiableFlow: SimplifiableFlow | undefined = undefined
export let expandedSubflows: Record<string, FlowModule[]> = {}
if (triggerContext && allowSimplifiedPoll) {
if (isSimplifiable(modules)) {
triggerContext?.simplifiedPoll?.set(true)
@@ -201,6 +206,15 @@
},
simplifyFlow: (detail) => {
triggerContext?.simplifiedPoll.set(detail)
},
expandSubflow: async (id: string, path: string) => {
const flow = await FlowService.getFlowByPath({ workspace: workspace, path })
expandedSubflows[id] = flow.value.modules
expandedSubflows = expandedSubflows
},
minimizeSubflow: (id: string) => {
delete expandedSubflows[id]
expandedSubflows = expandedSubflows
}
}
@@ -222,7 +236,8 @@
$selectedId,
moving,
simplifiableFlow,
triggerNode ? path : undefined
triggerNode ? path : undefined,
expandedSubflows
)
const nodes = writable<Node[]>([])
@@ -230,69 +245,6 @@
let height = 0
// function removeInputNode(nodes, edges, id) {
// const inputNode = nodes.find((node) => node.id === id)
// if (!inputNode) return { nodes, edges }
// // Find edges connected to the input node
// const connectedEdges = edges.filter((edge) => edge.source === id || edge.target === id)
// // Remove the input node
// let updatedNodes = nodes.filter((node) => node.id !== id)
// // Remove edges connected to the input node
// let updatedEdges = edges.filter((edge) => edge.source !== id && edge.target !== id)
// // Create new edges from the input node's parent to its children
// const inputEdges = connectedEdges.filter((edge) => edge.target === id)
// const outputEdges = connectedEdges.filter((edge) => edge.source === id)
// inputEdges.forEach((inputEdge) => {
// outputEdges.forEach((outputEdge) => {
// const newEdge = {
// id: `edge:${inputEdge.source}->${outputEdge.target}`,
// source: inputEdge.source,
// target: outputEdge.target,
// type: 'empty',
// data: {
// ...outputEdge.data,
// sourceId: inputEdge.source,
// targetId: outputEdge.target
// }
// }
// updatedEdges.push(newEdge)
// })
// })
// // Update parent ids of the nodes
// updatedNodes = updatedNodes.map((node) => {
// if (node.data && node.data.parentIds && node.data.parentIds.includes(id)) {
// const updatedParentIds = node.data.parentIds.filter((parentId) => parentId !== id)
// if (inputNode.data && inputNode.data.parentIds) {
// updatedParentIds.push(...inputNode.data.parentIds)
// }
// return {
// ...node,
// data: {
// ...node.data,
// parentIds: [...new Set(updatedParentIds)] // Remove duplicates
// }
// }
// }
// return node
// })
// return { nodes: updatedNodes, edges: updatedEdges }
// }
// function processGraph(graph, simplifiable) {
// let newGraph = { nodes: graph.nodes, edges: graph.edges }
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, 'Input')
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, simplifiable.forLoopNode.id)
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, simplifiable.triggerNode.id)
// return newGraph
// }
function isSimplifiable(modules: FlowModule[] | undefined): boolean {
if (!modules || modules?.length !== 2) {
return false
@@ -313,7 +265,7 @@
$nodes = layoutNodes(newGraph.nodes)
$edges = newGraph.edges
height = Math.max(...$nodes.map((n) => n.position.y + NODE.height + 40), minHeight)
height = Math.max(...$nodes.map((n) => n.position.y + NODE.height + 100), minHeight)
}
$: (graph || allowSimplifiedPoll) && updateStores()
@@ -330,6 +282,7 @@
whileLoopEnd: ForLoopEndNode,
branchOneStart: BranchOneStart,
branchOneEnd: BranchOneEndNode,
subflowBound: SubflowBound,
noBranch: NoBranchNode,
trigger: TriggersNode
} as any
+111 -26
View File
@@ -15,6 +15,8 @@ export type GraphEventHandlers = {
selectedIteration: (detail, moduleId: string) => void
changeId: (newId: string) => void
simplifyFlow: (detail: boolean) => void
expandSubflow: (id: string, path: string) => void
minimizeSubflow: (id: string) => void
}
export type SimplifiableFlow = { simplifiedFlow: boolean }
@@ -26,6 +28,10 @@ export function isTriggerStep(module: FlowModule | undefined): boolean {
module.value.is_trigger === true
)
}
export function buildPrefix(prefix: string | undefined, id: string): string {
return (prefix ?? '') + id + ':'
}
export function graphBuilder(
modules: FlowModule[] | undefined,
extra: Record<string, any>,
@@ -37,7 +43,8 @@ export function graphBuilder(
selectedId: string | undefined,
moving: string | undefined,
simplifiableFlow: SimplifiableFlow | undefined,
flowPathForTriggerNode: string | undefined
flowPathForTriggerNode: string | undefined,
expandedSubflows: Record<string, FlowModule[]>
// triggerProps?: {
// path?: string
// flowIsSimplifiable?: boolean
@@ -61,6 +68,10 @@ export function graphBuilder(
throw new Error(`Duplicated node detected: ${module.id}`)
}
if (module.id.startsWith('subflow:')) {
extra.insertable = false
}
nodes.push({
id: module.id,
data: {
@@ -114,6 +125,7 @@ export function graphBuilder(
function addEdge(
sourceId: string,
targetId: string,
prefix: string | undefined,
options?: {
disableInsert?: boolean
customId?: string
@@ -154,7 +166,7 @@ export function graphBuilder(
// If the index is -1, it means that the target module is not in the modules array, so we set it to the length of the array
index: index >= 0 ? index : mods?.length ?? 0,
...extra,
insertable: extra.insertable && !options?.disableInsert
insertable: extra.insertable && !options?.disableInsert && prefix == undefined
}
})
}
@@ -188,11 +200,11 @@ export function graphBuilder(
}
nodes.push(triggerNode)
if (preprocessorModule != null && preprocessorModule != undefined) {
addEdge('Trigger', preprocessorModule.id, {
addEdge('Trigger', preprocessorModule.id, undefined, {
type: 'empty'
})
} else {
addEdge('Trigger', 'Input', {
addEdge('Trigger', 'Input', undefined, {
type: 'empty'
})
}
@@ -220,16 +232,25 @@ export function graphBuilder(
beforeNode: Node,
nextNode: Node | undefined,
simplifiedTriggerView: boolean,
prefix: string | undefined,
currentOffset = 0,
disableMoveIds: string[] = [],
parentIndex?: string,
branchChosen?: boolean
) {
if (prefix != undefined) {
modules.forEach((m) => {
if (!m['oid']) {
m['oid'] = m.id
}
m.id = 'subflow:' + prefix + m['oid']
})
}
let previousId: string | undefined = undefined
if (modules.length === 0) {
if (nextNode) {
addEdge(beforeNode.id, nextNode.id, {
addEdge(beforeNode.id, nextNode.id, prefix, {
subModules: modules,
disableMoveIds
})
@@ -239,8 +260,8 @@ export function graphBuilder(
const localDisableMoveIds = [...disableMoveIds, module.id]
// Add the edge between the previous node and the current one
if (index > 0 && previousId) {
addEdge(previousId, module.id, {
if (index > 0 && previousId && expandedSubflows[module.id] == undefined) {
addEdge(previousId, module.id, prefix, {
subModules: modules,
disableMoveIds
})
@@ -284,10 +305,10 @@ export function graphBuilder(
nodes.push(startNode)
addEdge(module.id, startNode.id, {
addEdge(module.id, startNode.id, prefix, {
type: 'empty'
})
addEdge(startNode.id, endNode.id, {
addEdge(startNode.id, endNode.id, prefix, {
type: 'empty'
})
} else {
@@ -311,7 +332,7 @@ export function graphBuilder(
nodes.push(startNode)
addEdge(module.id, startNode.id, {
addEdge(module.id, startNode.id, prefix, {
type: 'empty'
})
@@ -320,6 +341,7 @@ export function graphBuilder(
startNode,
endNode,
false,
prefix,
currentOffset,
localDisableMoveIds,
parentIndex ? `${parentIndex}-${index}-${branchIndex}` : `${index}-${branchIndex}`
@@ -349,11 +371,11 @@ export function graphBuilder(
}
if (!simplifiedTriggerView) {
addEdge(module.id, startNode.id, {
addEdge(module.id, startNode.id, prefix, {
type: 'empty'
})
} else if (previousId) {
addEdge(previousId, startNode.id, {
addEdge(previousId, startNode.id, prefix, {
type: 'empty'
})
}
@@ -383,6 +405,7 @@ export function graphBuilder(
startNode,
endNode,
false,
prefix,
currentOffset + 25,
localDisableMoveIds,
parentIndex
@@ -406,7 +429,7 @@ export function graphBuilder(
position: { x: -1, y: -1 },
type: 'whileLoopStart'
}
addEdge(module.id, startNode.id, {
addEdge(module.id, startNode.id, prefix, {
type: 'empty'
})
@@ -427,6 +450,7 @@ export function graphBuilder(
startNode,
endNode,
false,
prefix,
currentOffset + 25,
localDisableMoveIds,
parentIndex
@@ -465,7 +489,7 @@ export function graphBuilder(
nodes.push(defaultBranch)
addEdge(module.id, defaultBranch.id, { type: 'empty' })
addEdge(module.id, defaultBranch.id, prefix, { type: 'empty' })
const branchChosen = extra.flowModuleStates?.[module.id]?.branchChosen
processModules(
@@ -473,6 +497,7 @@ export function graphBuilder(
defaultBranch,
endNode,
false,
prefix,
currentOffset,
localDisableMoveIds,
parentIndex ? `${parentIndex}-${index}` : index.toString(),
@@ -500,13 +525,14 @@ export function graphBuilder(
nodes.push(startNode)
addEdge(module.id, startNode.id, { type: 'empty' })
addEdge(module.id, startNode.id, prefix, { type: 'empty' })
processModules(
branch.modules,
startNode,
endNode,
false,
prefix,
currentOffset,
localDisableMoveIds,
parentIndex ? `${parentIndex}-${index}` : index.toString(),
@@ -516,13 +542,72 @@ export function graphBuilder(
previousId = endNode.id
} else {
addNode(module, currentOffset, 'module', modules)
let expanded = expandedSubflows[module.id]
if (expanded) {
const startId = `${module.id}-subflow-start`
const idWithoutPrefix = module.id.startsWith('subflow:')
? module.id.substring(8)
: module.id
const startNode = {
id: startId,
data: {
offset: currentOffset,
label: `Start of subflow ${idWithoutPrefix}`,
id: startId,
subflowId: module.id,
modules: modules,
eventHandlers: eventHandlers,
...extra
},
position: { x: -1, y: -1 },
type: 'subflowBound'
}
previousId = module.id
nodes.push(startNode)
if (previousId) {
addEdge(previousId!, startNode.id, prefix, { type: 'empty' })
} else {
addEdge(beforeNode.id, startNode.id, prefix, { type: 'empty' })
}
const endId = `${module.id}-subflow-end`
const endNode = {
id: endId,
data: {
offset: currentOffset,
label: `End of subflow ${idWithoutPrefix}`,
id: endId,
subflowId: module.id,
modules: modules,
eventHandlers: eventHandlers,
...extra
},
position: { x: -1, y: -1 },
type: 'subflowBound'
}
nodes.push(endNode)
processModules(
expanded,
startNode,
endNode,
false,
buildPrefix(prefix, module['oid'] ?? module.id),
currentOffset,
localDisableMoveIds
)
previousId = endNode.id
} else {
addNode(module, currentOffset, 'module', modules)
previousId = module.id
}
}
if (index === 0) {
addEdge(beforeNode.id, module.id, {
if (index === 0 && expandedSubflows[module.id] == undefined) {
addEdge(beforeNode.id, module.id, prefix, {
subModules: modules,
disableMoveIds,
disableInsert: simplifiedTriggerView
@@ -530,7 +615,7 @@ export function graphBuilder(
}
if (index === modules.length - 1 && previousId && nextNode) {
addEdge(previousId, nextNode.id, {
addEdge(previousId, nextNode.id, prefix, {
subModules: modules,
disableMoveIds
})
@@ -540,9 +625,9 @@ export function graphBuilder(
}
if (simplifiableFlow?.simplifiedFlow === true && triggerNode) {
processModules(modules, triggerNode, undefined, true)
processModules(modules, triggerNode, undefined, true, undefined)
} else {
processModules(modules, inputNode, resultNode, false)
processModules(modules, inputNode, resultNode, false, undefined)
}
if (failureModule) {
@@ -558,14 +643,14 @@ export function graphBuilder(
Object.entries(toAdd).forEach((x) => {
addNode({ ...failureModule, id: x[1] }, 0, 'module')
addEdge(x[0], x[1], { type: 'empty' })
addEdge(x[0], x[1], undefined, { type: 'empty' })
})
}
if (preprocessorModule) {
addNode(preprocessorModule, 0, 'module')
const id = JSON.parse(JSON.stringify(preprocessorModule.id))
addEdge(id, 'Input', { type: 'empty' })
addEdge(id, 'Input', undefined, { type: 'empty' })
}
if (failureModule && !extra.flowModuleStates) {
@@ -598,7 +683,7 @@ export function graphBuilder(
}
}
addEdge(pid, selectedId!, {
addEdge(pid, selectedId!, undefined, {
customId: `dep-${pid}-${selectedId}-${input}-${index}`,
type: 'dataflowedge'
})
@@ -608,7 +693,7 @@ export function graphBuilder(
Object.entries(deps.dependents).forEach((x, i) => {
let pid = x[0]
addEdge(selectedId!, pid, {
addEdge(selectedId!, pid, undefined, {
customId: `dep-${selectedId}-${pid}-${i}`,
type: 'dataflowedge'
})
@@ -1,7 +1,7 @@
<script lang="ts">
import MapItem from '$lib/components/flows/map/MapItem.svelte'
import type { FlowModule, FlowModuleValue } from '$lib/gen'
import { GitBranchPlus } from 'lucide-svelte'
import { GitBranchPlus, Maximize2 } from 'lucide-svelte'
import NodeWrapper from './NodeWrapper.svelte'
import type { GraphEventHandlers } from '../../graphBuilder'
import type { GraphModuleState } from '../../model'
@@ -46,6 +46,19 @@
</script>
<NodeWrapper offset={data.offset} let:darkMode>
{#if data.module.value.type == 'flow'}
<button
title="Unexpand subflow"
class="z-50 absolute -top-[10px] right-[25px] rounded-full h-[20px] w-[20px] center-center text-primary bg-surface duration-150 hover:bg-surface-hover"
on:click|preventDefault|stopPropagation={() => {
if (data.module.value.type == 'flow') {
data.eventHandlers.expandSubflow(data.module.id, data.module.value.path)
}
}}
>
<Maximize2 size={12} />
</button>
{/if}
<MapItem
mod={data.module}
insertable={data.insertable}
@@ -0,0 +1,45 @@
<script lang="ts">
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<string, GraphModuleState> | undefined
subflowId: string
id: string
modules: FlowModule[]
selected: boolean
eventHandlers: GraphEventHandlers
offset: number
}
</script>
<NodeWrapper let:darkMode offset={data.offset}>
<VirtualItem
label={data.label}
preLabel={data.preLabel}
selectable
selected={data.selected}
bgColor={getStateColor(undefined, darkMode)}
borderColor={undefined}
on:select={() => {
data.eventHandlers.select(data.id)
}}
/>
<button
title="Unexpand subflow"
class="z-50 absolute -top-[10px] right-[25px] rounded-full h-[20px] w-[20px] center-center text-primary bg-surface duration-150 hover:bg-surface-hover"
on:click|preventDefault|stopPropagation={() => {
data.eventHandlers.minimizeSubflow(data.subflowId)
}}
>
<Minimize2 size={12} />
</button>
</NodeWrapper>
@@ -284,6 +284,7 @@
<h2 class="mt-10">Flow details</h2>
<div class="border border-gray-700">
<FlowGraphV2
workspace={job.workspace_id}
triggerNode={false}
modules={job.raw_flow?.modules}
failureModule={job.raw_flow?.failure_module}
+8 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts">
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 }
</script>
<FlowGraphV2 triggerNode={false} {modules} {failureModule} {preprocessorModule} />
<FlowGraphV2
workspace={$workspaceStore}
triggerNode={false}
{modules}
{failureModule}
{preprocessorModule}
/>
<a
download="flow.json"
href={'data:text/json;charset=utf-8,' +