-
-
Notifications
You must be signed in to change notification settings - Fork 257
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples: add example for running a process tree
- Loading branch information
1 parent
6dd9514
commit 0ae7502
Showing
4 changed files
with
239 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<script setup lang="ts"> | ||
import type { NodeProps } from '@vue-flow/core' | ||
import { Handle } from '@vue-flow/core' | ||
const props = defineProps<NodeProps>() | ||
const bgColor = toRef(() => { | ||
if (props.data.hasError) { | ||
return '#f87171' | ||
} | ||
if (props.data.isFinished) { | ||
return '#10b981' | ||
} | ||
if (props.data.isRunning || props.data.isSkipped) { | ||
return '#6b7280' | ||
} | ||
return '#1a192b' | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div class="process-node" :style="{ backgroundColor: bgColor }"> | ||
<Handle type="target" :position="targetPosition" /> | ||
<Handle type="source" :position="sourcePosition" /> | ||
|
||
<div style="display: flex; align-items: center; gap: 8px"> | ||
<div v-if="data.isRunning" class="spinner" /> | ||
<span v-else-if="data.hasError">❌</span> | ||
<span v-else-if="data.isSkipped">🚧</span> | ||
<span v-else>📦</span> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
.process-node { | ||
padding: 10px; | ||
color: white; | ||
border: 1px solid #1a192b; | ||
border-radius: 99px; | ||
font-size: 10px; | ||
width: 15px; | ||
height: 15px; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
.spinner { | ||
border: 2px solid #f3f3f3; | ||
border-top: 2px solid #3498db; | ||
border-radius: 50%; | ||
width: 8px; | ||
height: 8px; | ||
animation: spin 1s linear infinite; | ||
} | ||
@keyframes spin { | ||
0% { | ||
transform: rotate(0deg); | ||
} | ||
100% { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,75 @@ | ||
import type { Elements, XYPosition } from '@vue-flow/core' | ||
import type { Edge, Node } from '@vue-flow/core' | ||
|
||
const position: XYPosition = { x: 0, y: 0 } | ||
const position = { x: 0, y: 0 } | ||
const type: string = 'process' | ||
|
||
const elements: Elements = [ | ||
export const initialNodes: Node[] = [ | ||
{ | ||
id: '1', | ||
type: 'input', | ||
label: 'input', | ||
label: 'Start', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '2', | ||
label: 'node 2', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '2a', | ||
label: 'node 2a', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '2b', | ||
label: 'node 2b', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '2c', | ||
label: 'node 2c', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '2d', | ||
label: 'node 2d', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '3', | ||
label: 'node 3', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '4', | ||
label: 'node 4', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '5', | ||
label: 'node 5', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '6', | ||
type: 'output', | ||
label: 'output', | ||
position, | ||
type, | ||
}, | ||
{ | ||
id: '7', | ||
position, | ||
type, | ||
}, | ||
{ id: '7', type: 'output', label: 'output', position: { x: 400, y: 450 } }, | ||
{ id: 'e12', source: '1', target: '2', type: 'smoothstep', animated: true }, | ||
{ id: 'e13', source: '1', target: '3', type: 'smoothstep', animated: true }, | ||
{ id: 'e22a', source: '2', target: '2a', type: 'smoothstep', animated: true }, | ||
{ id: 'e22b', source: '2', target: '2b', type: 'smoothstep', animated: true }, | ||
{ id: 'e22c', source: '2', target: '2c', type: 'smoothstep', animated: true }, | ||
{ id: 'e2c2d', source: '2c', target: '2d', type: 'smoothstep', animated: true }, | ||
|
||
{ id: 'e45', source: '4', target: '5', type: 'smoothstep', animated: true }, | ||
{ id: 'e56', source: '5', target: '6', type: 'smoothstep', animated: true }, | ||
{ id: 'e57', source: '5', target: '7', type: 'smoothstep', animated: true }, | ||
] | ||
|
||
export default elements | ||
export const initialEdges: Edge[] = [ | ||
{ id: 'e1-2', source: '1', target: '2', type: 'smoothstep', animated: true }, | ||
{ id: 'e1-3', source: '1', target: '3', type: 'smoothstep', animated: true }, | ||
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', animated: true }, | ||
{ id: 'e2-2b', source: '2', target: '2b', type: 'smoothstep', animated: true }, | ||
{ id: 'e2-2c', source: '2', target: '2c', type: 'smoothstep', animated: true }, | ||
{ id: 'e2c-2d', source: '2c', target: '2d', type: 'smoothstep', animated: true }, | ||
{ id: 'e4-5', source: '4', target: '5', type: 'smoothstep', animated: true }, | ||
{ id: 'e5-6', source: '5', target: '6', type: 'smoothstep', animated: true }, | ||
{ id: 'e5-7', source: '5', target: '7', type: 'smoothstep', animated: true }, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import type { Node } from '@vue-flow/core' | ||
import { useVueFlow } from '@vue-flow/core' | ||
|
||
/** | ||
* Composable to simulate running a process tree. | ||
* | ||
* It loops through each node, pretends to run an async process, and updates the node's data indicating whether the process has finished. | ||
* When one node finishes, the next one starts. | ||
* | ||
* When a node has multiple descendants, it will run them in parallel. | ||
*/ | ||
export function useRunProcess() { | ||
const { updateNodeData } = useVueFlow() | ||
|
||
const running = ref(false) | ||
const executedNodes = new Set<string>() | ||
|
||
async function runNode(node: { id: string }, dagreGraph: dagre.graphlib.Graph) { | ||
if (executedNodes.has(node.id)) { | ||
return | ||
} | ||
|
||
executedNodes.add(node.id) | ||
|
||
updateNodeData(node.id, { isRunning: true, isFinished: false, hasError: false }) | ||
|
||
// Simulate an async process with a random timeout between 1 and 3 seconds | ||
const delay = Math.floor(Math.random() * 2000) + 1000 | ||
await new Promise((resolve) => setTimeout(resolve, delay)) | ||
|
||
const children = dagreGraph.successors(node.id) as unknown as string[] | ||
|
||
// Randomly decide whether the node will throw an error | ||
const willThrowError = Math.random() < 0.15 | ||
|
||
if (willThrowError) { | ||
updateNodeData(node.id, { isRunning: false, hasError: true }) | ||
|
||
await skipDescendants(node.id, dagreGraph) | ||
return | ||
} | ||
|
||
updateNodeData(node.id, { isRunning: false, isFinished: true }) | ||
|
||
// Run the process on the children in parallel | ||
await Promise.all( | ||
children.map((id) => { | ||
return runNode({ id }, dagreGraph) | ||
}), | ||
) | ||
} | ||
|
||
async function run(nodes: Node[], dagreGraph: dagre.graphlib.Graph) { | ||
if (running.value) { | ||
return | ||
} | ||
|
||
reset(nodes) | ||
|
||
running.value = true | ||
|
||
// Get all starting nodes (nodes with no predecessors) | ||
const startingNodes = nodes.filter((node) => dagreGraph.predecessors(node.id)?.length === 0) | ||
|
||
// Run the process on all starting nodes in parallel | ||
await Promise.all(startingNodes.map((node) => runNode(node, dagreGraph))) | ||
|
||
running.value = false | ||
executedNodes.clear() | ||
} | ||
|
||
function reset(nodes: Node[]) { | ||
for (const node of nodes) { | ||
updateNodeData(node.id, { isRunning: false, isFinished: false, hasError: false, isSkipped: false }) | ||
} | ||
} | ||
|
||
async function skipDescendants(nodeId: string, dagreGraph: dagre.graphlib.Graph) { | ||
const children = dagreGraph.successors(nodeId) as unknown as string[] | ||
|
||
for (const child of children) { | ||
updateNodeData(child, { isRunning: false, isSkipped: true }) | ||
await skipDescendants(child, dagreGraph) | ||
} | ||
} | ||
|
||
return { run, running } | ||
} |