mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-04 08:01:54 +00:00
feat(frontend): dataflow
This commit is contained in:
@@ -30,6 +30,8 @@
|
||||
import EmptyEdge from './renderers/edges/EmptyEdge.svelte'
|
||||
import { sugiyama, dagStratify, decrossOpt, coordCenter } from 'd3-dag'
|
||||
import { Expand } from 'lucide-svelte'
|
||||
import Toggle from '../Toggle.svelte'
|
||||
import DataflowEdge from './renderers/edges/DataflowEdge.svelte'
|
||||
|
||||
export let success: boolean | undefined = undefined
|
||||
export let modules: FlowModule[] | undefined = []
|
||||
@@ -54,10 +56,13 @@
|
||||
undefined
|
||||
)
|
||||
|
||||
let useDataflow: Writable<boolean | undefined> = writable<boolean | undefined>(false)
|
||||
|
||||
setContext<{
|
||||
selectedId: Writable<string | undefined>
|
||||
flowInputsStore: Writable<FlowInput | undefined>
|
||||
}>('FlowGraphContext', { selectedId, flowInputsStore })
|
||||
useDataflow: Writable<boolean | undefined>
|
||||
}>('FlowGraphContext', { selectedId, flowInputsStore, useDataflow })
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -155,7 +160,9 @@
|
||||
dispatch('selectIteration', { ...detail, moduleId: moduleId })
|
||||
}
|
||||
},
|
||||
success
|
||||
success,
|
||||
$useDataflow,
|
||||
$selectedId
|
||||
)
|
||||
|
||||
const nodes = writable<Node[]>([])
|
||||
@@ -182,7 +189,8 @@
|
||||
|
||||
const edgeTypes = {
|
||||
edge: BaseEdge,
|
||||
empty: EmptyEdge
|
||||
empty: EmptyEdge,
|
||||
dataflowedge: DataflowEdge
|
||||
} as any
|
||||
|
||||
const proOptions = { hideAttribution: true }
|
||||
@@ -232,6 +240,25 @@
|
||||
</ControlButton>
|
||||
{/if}
|
||||
</Controls>
|
||||
|
||||
<Controls
|
||||
position="top-left"
|
||||
orientation="horizontal"
|
||||
showLock={false}
|
||||
showZoom={false}
|
||||
showFitView={false}
|
||||
>
|
||||
<Toggle
|
||||
value={$useDataflow}
|
||||
on:change={() => {
|
||||
$useDataflow = !$useDataflow
|
||||
}}
|
||||
size="xs"
|
||||
options={{
|
||||
right: 'Dataflow'
|
||||
}}
|
||||
/>
|
||||
</Controls>
|
||||
</SvelteFlow>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
## Rendering
|
||||
|
||||
- Nodes [x]
|
||||
- Edges [x]
|
||||
- Insert button [x]
|
||||
- AI button [x]
|
||||
- Delete branch button [x]
|
||||
- Insert branch button [x]
|
||||
- Graph actions [x]
|
||||
|
||||
## Functionality
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { type Node, type Edge } from '@xyflow/svelte'
|
||||
import { getDependeeAndDependentComponents } from '../flows/flowExplorer'
|
||||
import { dfsByModule } from '../flows/previousResults'
|
||||
|
||||
export type GraphEventHandlers = {
|
||||
insert: (detail) => void
|
||||
@@ -16,7 +18,9 @@ export default function graphBuilder(
|
||||
extra: Record<string, any>,
|
||||
failureModule: FlowModule | undefined,
|
||||
eventHandlers: GraphEventHandlers,
|
||||
success: boolean | undefined
|
||||
success: boolean | undefined,
|
||||
useDataflow: boolean | undefined,
|
||||
selectedId: string | undefined
|
||||
): {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
@@ -49,7 +53,13 @@ export default function graphBuilder(
|
||||
|
||||
const parents: { [key: string]: string[] } = {}
|
||||
|
||||
function addEdge(sourceId: string, targetId: string, customId?: string, type?: string) {
|
||||
function addEdge(
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
customId?: string,
|
||||
type?: string,
|
||||
offset?: number
|
||||
) {
|
||||
parents[targetId] = [...(parents[targetId] ?? []), sourceId]
|
||||
|
||||
edges.push({
|
||||
@@ -59,13 +69,16 @@ export default function graphBuilder(
|
||||
type: type ?? 'edge',
|
||||
data: {
|
||||
insertable: extra.insertable,
|
||||
modules
|
||||
modules,
|
||||
sourceId,
|
||||
targetId,
|
||||
offset
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const inputNode: Node = {
|
||||
id: 'input',
|
||||
id: 'Input',
|
||||
position: { x: -1, y: -1 },
|
||||
type: 'input2',
|
||||
data: {
|
||||
@@ -280,5 +293,44 @@ export default function graphBuilder(
|
||||
}
|
||||
})
|
||||
|
||||
// DATAFLOW
|
||||
if (useDataflow && selectedId) {
|
||||
let deps = getDependeeAndDependentComponents(selectedId, modules ?? [], failureModule)
|
||||
|
||||
if (deps) {
|
||||
Object.entries(deps.dependees).forEach((x, i) => {
|
||||
const inputs = x[1]
|
||||
|
||||
inputs?.forEach((input, index) => {
|
||||
let pid = x[0]
|
||||
|
||||
if (input?.startsWith('flow_input.iter')) {
|
||||
const parent = dfsByModule(selectedId!, modules ?? [])?.pop()
|
||||
|
||||
if (parent?.id) {
|
||||
pid = parent.id
|
||||
}
|
||||
}
|
||||
|
||||
addEdge(
|
||||
pid,
|
||||
selectedId!,
|
||||
`dep-${pid}-${selectedId}-${input}-${index}`,
|
||||
'dataflowedge',
|
||||
i * 20
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Object.entries(deps.dependents).forEach((x, i) => {
|
||||
let pid = x[0]
|
||||
|
||||
console.log('offset', i * 10)
|
||||
|
||||
addEdge(selectedId!, pid, `dep-${selectedId}-${pid}-${i}`, 'dataflowedge', i * 10)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { nodes, edges }
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<script lang="ts">
|
||||
import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte'
|
||||
import type { FlowModule } from '$lib/gen/models/FlowModule'
|
||||
import {
|
||||
getBezierPath,
|
||||
BaseEdge,
|
||||
type Position,
|
||||
EdgeLabelRenderer
|
||||
// @ts-ignore
|
||||
} from '@xyflow/svelte'
|
||||
import { getBezierPath, BaseEdge, type Position, EdgeLabelRenderer } from '@xyflow/svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Writable } from 'svelte/store'
|
||||
|
||||
export let sourceX: number
|
||||
export let sourceY: number
|
||||
@@ -16,7 +12,6 @@
|
||||
export let targetY: number
|
||||
export let targetPosition: Position
|
||||
export let markerEnd: string | undefined = undefined
|
||||
export let style: string | undefined = undefined
|
||||
|
||||
export let data: {
|
||||
insertable: boolean
|
||||
@@ -41,10 +36,14 @@
|
||||
targetY - sourceY > 100
|
||||
? `${edgePath} ${getStraightLinePath({ sourceX, sourceY, targetX, targetY })}`
|
||||
: edgePath
|
||||
|
||||
const { useDataflow } = getContext<{
|
||||
useDataflow: Writable<boolean | undefined>
|
||||
}>('FlowGraphContext')
|
||||
</script>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
{#if data?.insertable}
|
||||
{#if data?.insertable && !$useDataflow}
|
||||
<div
|
||||
class="edgeButtonContainer nodrag nopan"
|
||||
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
|
||||
@@ -53,7 +52,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
</EdgeLabelRenderer>
|
||||
<BaseEdge path={completeEdge} {markerEnd} {style} />
|
||||
|
||||
<BaseEdge path={completeEdge} {markerEnd} class={$useDataflow ? 'hidden' : ''} />
|
||||
|
||||
<style>
|
||||
.edgeButtonContainer {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import type { FlowModule } from '$lib/gen/models/FlowModule'
|
||||
import { getBezierPath, BaseEdge, type Position, EdgeLabelRenderer } from '@xyflow/svelte'
|
||||
|
||||
export let sourceX: number
|
||||
export let sourceY: number
|
||||
export let sourcePosition: Position
|
||||
export let targetX: number
|
||||
export let targetY: number
|
||||
export let targetPosition: Position
|
||||
export let markerEnd: string | undefined = undefined
|
||||
|
||||
export let data: {
|
||||
insertable: boolean
|
||||
modules: FlowModule[]
|
||||
sourceId: string
|
||||
targetId: string
|
||||
offset: number
|
||||
}
|
||||
|
||||
$: [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY: targetY - sourceY > 100 ? targetY - 100 : sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
curvature: 0.1
|
||||
})
|
||||
|
||||
function getStraightLinePath({ sourceX, sourceY, targetX, targetY }) {
|
||||
return `M${sourceX},${sourceY} L${sourceX},${targetY - 100}`
|
||||
}
|
||||
|
||||
$: completeEdge =
|
||||
targetY - sourceY > 100
|
||||
? `${edgePath} ${getStraightLinePath({ sourceX, sourceY, targetX, targetY })}`
|
||||
: edgePath
|
||||
</script>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
{#if data?.insertable}
|
||||
<div
|
||||
class="edgeButtonContainer nodrag nopan bg-surface-selected p-1 border text-xs"
|
||||
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
|
||||
>
|
||||
{data.sourceId} -> {data.targetId}
|
||||
</div>
|
||||
{/if}
|
||||
</EdgeLabelRenderer>
|
||||
|
||||
<BaseEdge
|
||||
path={completeEdge}
|
||||
{markerEnd}
|
||||
style={`animation:dashdraw 0.5s linear infinite; stroke-dasharray: 5px;`}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.edgeButtonContainer {
|
||||
position: absolute;
|
||||
font-size: 12pt;
|
||||
/* everything inside EdgeLabelRenderer has no pointer events by default */
|
||||
/* if you have an interactive element, set pointer-events: all */
|
||||
pointer-events: all;
|
||||
}
|
||||
</style>
|
||||
@@ -2,20 +2,23 @@
|
||||
import {
|
||||
getBezierPath,
|
||||
BaseEdge,
|
||||
type EdgeProps
|
||||
type Position
|
||||
// @ts-ignore
|
||||
} from '@xyflow/svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Writable } from 'svelte/store'
|
||||
|
||||
type $$Props = EdgeProps
|
||||
export let sourceX: number
|
||||
export let sourceY: number
|
||||
export let sourcePosition: Position
|
||||
export let targetX: number
|
||||
export let targetY: number
|
||||
export let targetPosition: Position
|
||||
export let markerEnd: string | undefined = undefined
|
||||
|
||||
export let sourceX: $$Props['sourceX']
|
||||
export let sourceY: $$Props['sourceY']
|
||||
export let sourcePosition: $$Props['sourcePosition']
|
||||
export let targetX: $$Props['targetX']
|
||||
export let targetY: $$Props['targetY']
|
||||
export let targetPosition: $$Props['targetPosition']
|
||||
export let markerEnd: $$Props['markerEnd'] = undefined
|
||||
export let style: $$Props['style'] = undefined
|
||||
const { useDataflow } = getContext<{
|
||||
useDataflow: Writable<boolean | undefined>
|
||||
}>('FlowGraphContext')
|
||||
|
||||
$: [edgePath] = getBezierPath({
|
||||
sourceX,
|
||||
@@ -27,4 +30,4 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<BaseEdge path={edgePath} {markerEnd} {style} />
|
||||
<BaseEdge path={edgePath} {markerEnd} class={$useDataflow ? 'hidden' : ''} />
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
title="Add branch"
|
||||
on:click={() => {
|
||||
dispatch('insert', { modules: data.modules, index: idx, detail: 'move' })
|
||||
//data?.eventHandlers?.insert()
|
||||
}}
|
||||
type="button"
|
||||
disabled={data.wrapperId === data.moving}
|
||||
@@ -121,7 +122,7 @@
|
||||
<button
|
||||
class="rounded-full border hover:bg-surface-hover bg-surface p-1"
|
||||
on:click={() => {
|
||||
dispatch('addBranch')
|
||||
data?.eventHandlers?.newBranch(data.module)
|
||||
}}
|
||||
>
|
||||
<GitBranchPlus size={16} />
|
||||
|
||||
Reference in New Issue
Block a user