Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins
commit
566dc9b4a8
@ -1,128 +0,0 @@
|
|||||||
import { parseDSL } from './graph-to-log-struct-2'
|
|
||||||
|
|
||||||
describe('parseDSL', () => {
|
|
||||||
it('should parse plain nodes correctly', () => {
|
|
||||||
const dsl = 'plainNode1 -> plainNode2'
|
|
||||||
const result = parseDSL(dsl)
|
|
||||||
expect(result).toEqual([
|
|
||||||
{ id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: {}, status: 'succeeded' },
|
|
||||||
{ id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: {}, status: 'succeeded' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse retry nodes correctly', () => {
|
|
||||||
const dsl = '(retry, retryNode, 3)'
|
|
||||||
const result = parseDSL(dsl)
|
|
||||||
expect(result).toEqual([
|
|
||||||
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'succeeded' },
|
|
||||||
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'retry' },
|
|
||||||
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'retry' },
|
|
||||||
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'retry' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse iteration nodes correctly', () => {
|
|
||||||
const dsl = '(iteration, iterationNode, plainNode1 -> plainNode2)'
|
|
||||||
const result = parseDSL(dsl)
|
|
||||||
expect(result).toEqual([
|
|
||||||
{ id: 'iterationNode', node_id: 'iterationNode', title: 'iterationNode', node_type: 'iteration', execution_metadata: {}, status: 'succeeded' },
|
|
||||||
{ id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0 }, status: 'succeeded' },
|
|
||||||
{ id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0 }, status: 'succeeded' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse parallel nodes correctly', () => {
|
|
||||||
const dsl = '(parallel, parallelNode, nodeA, nodeB -> nodeC)'
|
|
||||||
const result = parseDSL(dsl)
|
|
||||||
expect(result).toEqual([
|
|
||||||
{ id: 'parallelNode', node_id: 'parallelNode', title: 'parallelNode', execution_metadata: { parallel_id: 'parallelNode' }, status: 'succeeded' },
|
|
||||||
{ id: 'nodeA', node_id: 'nodeA', title: 'nodeA', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'nodeA' }, status: 'succeeded' },
|
|
||||||
{ id: 'nodeB', node_id: 'nodeB', title: 'nodeB', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'nodeB' }, status: 'succeeded' },
|
|
||||||
{ id: 'nodeC', node_id: 'nodeC', title: 'nodeC', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'nodeB' }, status: 'succeeded' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
it('should handle nested parallel nodes', () => {
|
|
||||||
const dsl = '(parallel, outerParallel, (parallel, innerParallel, plainNode1 -> plainNode2) -> plainNode3)'
|
|
||||||
const result = parseDSL(dsl)
|
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
|
||||||
id: 'outerParallel',
|
|
||||||
node_id: 'outerParallel',
|
|
||||||
title: 'outerParallel',
|
|
||||||
execution_metadata: { parallel_id: 'outerParallel' },
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'innerParallel',
|
|
||||||
node_id: 'innerParallel',
|
|
||||||
title: 'innerParallel',
|
|
||||||
execution_metadata: { parallel_id: 'outerParallel', parallel_start_node_id: 'innerParallel' },
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'plainNode1',
|
|
||||||
node_id: 'plainNode1',
|
|
||||||
title: 'plainNode1',
|
|
||||||
execution_metadata: {
|
|
||||||
parallel_id: 'innerParallel',
|
|
||||||
parallel_start_node_id: 'plainNode1',
|
|
||||||
parent_parallel_id: 'outerParallel',
|
|
||||||
parent_parallel_start_node_id: 'innerParallel',
|
|
||||||
},
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'plainNode2',
|
|
||||||
node_id: 'plainNode2',
|
|
||||||
title: 'plainNode2',
|
|
||||||
execution_metadata: {
|
|
||||||
parallel_id: 'innerParallel',
|
|
||||||
parallel_start_node_id: 'plainNode1',
|
|
||||||
parent_parallel_id: 'outerParallel',
|
|
||||||
parent_parallel_start_node_id: 'innerParallel',
|
|
||||||
},
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'plainNode3',
|
|
||||||
node_id: 'plainNode3',
|
|
||||||
title: 'plainNode3',
|
|
||||||
execution_metadata: {
|
|
||||||
parallel_id: 'outerParallel',
|
|
||||||
parallel_start_node_id: 'plainNode3',
|
|
||||||
},
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
// iterations not support nested iterations
|
|
||||||
// it('should handle nested iterations', () => {
|
|
||||||
// const dsl = '(iteration, outerIteration, (iteration, innerIteration -> plainNode1 -> plainNode2))'
|
|
||||||
// const result = parseDSL(dsl)
|
|
||||||
// expect(result).toEqual([
|
|
||||||
// { id: 'outerIteration', node_id: 'outerIteration', title: 'outerIteration', node_type: 'iteration', execution_metadata: {}, status: 'succeeded' },
|
|
||||||
// { id: 'innerIteration', node_id: 'innerIteration', title: 'innerIteration', node_type: 'iteration', execution_metadata: { iteration_id: 'outerIteration', iteration_index: 0 }, status: 'succeeded' },
|
|
||||||
// { id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: { iteration_id: 'innerIteration', iteration_index: 0 }, status: 'succeeded' },
|
|
||||||
// { id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: { iteration_id: 'innerIteration', iteration_index: 0 }, status: 'succeeded' },
|
|
||||||
// ])
|
|
||||||
// })
|
|
||||||
|
|
||||||
it('should handle nested iterations within parallel nodes', () => {
|
|
||||||
const dsl = '(parallel, parallelNode, (iteration, iterationNode, plainNode1, plainNode2))'
|
|
||||||
const result = parseDSL(dsl)
|
|
||||||
expect(result).toEqual([
|
|
||||||
{ id: 'parallelNode', node_id: 'parallelNode', title: 'parallelNode', execution_metadata: { parallel_id: 'parallelNode' }, status: 'succeeded' },
|
|
||||||
{ id: 'iterationNode', node_id: 'iterationNode', title: 'iterationNode', node_type: 'iteration', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'iterationNode' }, status: 'succeeded' },
|
|
||||||
{ id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0, parallel_id: 'parallelNode', parallel_start_node_id: 'iterationNode' }, status: 'succeeded' },
|
|
||||||
{ id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0, parallel_id: 'parallelNode', parallel_start_node_id: 'iterationNode' }, status: 'succeeded' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error for unknown node types', () => {
|
|
||||||
const dsl = '(unknown, nodeId)'
|
|
||||||
expect(() => parseDSL(dsl)).toThrowError('Unknown nodeType: unknown')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,304 +0,0 @@
|
|||||||
type IterationInfo = { iterationId: string; iterationIndex: number }
|
|
||||||
type NodePlain = { nodeType: 'plain'; nodeId: string; } & Partial<IterationInfo>
|
|
||||||
type NodeComplex = { nodeType: string; nodeId: string; params: (NodePlain | (NodeComplex & Partial<IterationInfo>) | Node[] | number)[] } & Partial<IterationInfo>
|
|
||||||
type Node = NodePlain | NodeComplex
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a DSL string into an array of node objects.
|
|
||||||
* @param dsl - The input DSL string.
|
|
||||||
* @returns An array of parsed nodes.
|
|
||||||
*/
|
|
||||||
function parseDSL(dsl: string): NodeData[] {
|
|
||||||
return convertToNodeData(parseTopLevelFlow(dsl).map(nodeStr => parseNode(nodeStr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Splits a top-level flow string by "->", respecting nested structures.
|
|
||||||
* @param dsl - The DSL string to split.
|
|
||||||
* @returns An array of top-level segments.
|
|
||||||
*/
|
|
||||||
function parseTopLevelFlow(dsl: string): string[] {
|
|
||||||
const segments: string[] = []
|
|
||||||
let buffer = ''
|
|
||||||
let nested = 0
|
|
||||||
|
|
||||||
for (let i = 0; i < dsl.length; i++) {
|
|
||||||
const char = dsl[i]
|
|
||||||
if (char === '(') nested++
|
|
||||||
if (char === ')') nested--
|
|
||||||
if (char === '-' && dsl[i + 1] === '>' && nested === 0) {
|
|
||||||
segments.push(buffer.trim())
|
|
||||||
buffer = ''
|
|
||||||
i++ // Skip the ">" character
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buffer += char
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (buffer.trim())
|
|
||||||
segments.push(buffer.trim())
|
|
||||||
|
|
||||||
return segments
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a single node string.
|
|
||||||
* If the node is complex (e.g., has parentheses), it extracts the node type, node ID, and parameters.
|
|
||||||
* @param nodeStr - The node string to parse.
|
|
||||||
* @param parentIterationId - The ID of the parent iteration node (if applicable).
|
|
||||||
* @returns A parsed node object.
|
|
||||||
*/
|
|
||||||
function parseNode(nodeStr: string, parentIterationId?: string): Node {
|
|
||||||
// Check if the node is a complex node
|
|
||||||
if (nodeStr.startsWith('(') && nodeStr.endsWith(')')) {
|
|
||||||
const innerContent = nodeStr.slice(1, -1).trim() // Remove outer parentheses
|
|
||||||
let nested = 0
|
|
||||||
let buffer = ''
|
|
||||||
const parts: string[] = []
|
|
||||||
|
|
||||||
// Split the inner content by commas, respecting nested parentheses
|
|
||||||
for (let i = 0; i < innerContent.length; i++) {
|
|
||||||
const char = innerContent[i]
|
|
||||||
if (char === '(') nested++
|
|
||||||
if (char === ')') nested--
|
|
||||||
|
|
||||||
if (char === ',' && nested === 0) {
|
|
||||||
parts.push(buffer.trim())
|
|
||||||
buffer = ''
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buffer += char
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parts.push(buffer.trim())
|
|
||||||
|
|
||||||
// Extract nodeType, nodeId, and params
|
|
||||||
const [nodeType, nodeId, ...paramsRaw] = parts
|
|
||||||
const params = parseParams(paramsRaw, nodeType === 'iteration' ? nodeId.trim() : parentIterationId)
|
|
||||||
const complexNode = {
|
|
||||||
nodeType: nodeType.trim(),
|
|
||||||
nodeId: nodeId.trim(),
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
if (parentIterationId) {
|
|
||||||
(complexNode as any).iterationId = parentIterationId;
|
|
||||||
(complexNode as any).iterationIndex = 0 // Fixed as 0
|
|
||||||
}
|
|
||||||
return complexNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not a complex node, treat it as a plain node
|
|
||||||
const plainNode: NodePlain = { nodeType: 'plain', nodeId: nodeStr.trim() }
|
|
||||||
if (parentIterationId) {
|
|
||||||
plainNode.iterationId = parentIterationId
|
|
||||||
plainNode.iterationIndex = 0 // Fixed as 0
|
|
||||||
}
|
|
||||||
return plainNode
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses parameters of a complex node.
|
|
||||||
* Supports nested flows and complex sub-nodes.
|
|
||||||
* Adds iteration-specific metadata recursively.
|
|
||||||
* @param paramParts - The parameters string split by commas.
|
|
||||||
* @param iterationId - The ID of the iteration node, if applicable.
|
|
||||||
* @returns An array of parsed parameters (plain nodes, nested nodes, or flows).
|
|
||||||
*/
|
|
||||||
function parseParams(paramParts: string[], iterationId?: string): (Node | Node[] | number)[] {
|
|
||||||
return paramParts.map((part) => {
|
|
||||||
if (part.includes('->')) {
|
|
||||||
// Parse as a flow and return an array of nodes
|
|
||||||
return parseTopLevelFlow(part).map(node => parseNode(node, iterationId))
|
|
||||||
}
|
|
||||||
else if (part.startsWith('(')) {
|
|
||||||
// Parse as a nested complex node
|
|
||||||
return parseNode(part, iterationId)
|
|
||||||
}
|
|
||||||
else if (!Number.isNaN(Number(part.trim()))) {
|
|
||||||
// Parse as a numeric parameter
|
|
||||||
return Number(part.trim())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Parse as a plain node
|
|
||||||
return parseNode(part, iterationId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeData = {
|
|
||||||
id: string;
|
|
||||||
node_id: string;
|
|
||||||
title: string;
|
|
||||||
node_type?: string;
|
|
||||||
execution_metadata: Record<string, any>;
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a plain node to node data.
|
|
||||||
*/
|
|
||||||
function convertPlainNode(node: Node): NodeData[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: node.nodeId,
|
|
||||||
node_id: node.nodeId,
|
|
||||||
title: node.nodeId,
|
|
||||||
execution_metadata: {},
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a retry node to node data.
|
|
||||||
*/
|
|
||||||
function convertRetryNode(node: Node): NodeData[] {
|
|
||||||
const { nodeId, iterationId, iterationIndex, params } = node as NodeComplex
|
|
||||||
const retryCount = params ? Number.parseInt(params[0] as unknown as string, 10) : 0
|
|
||||||
const result: NodeData[] = [
|
|
||||||
{
|
|
||||||
id: nodeId,
|
|
||||||
node_id: nodeId,
|
|
||||||
title: nodeId,
|
|
||||||
execution_metadata: {},
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for (let i = 0; i < retryCount; i++) {
|
|
||||||
result.push({
|
|
||||||
id: nodeId,
|
|
||||||
node_id: nodeId,
|
|
||||||
title: nodeId,
|
|
||||||
execution_metadata: iterationId ? {
|
|
||||||
iteration_id: iterationId,
|
|
||||||
iteration_index: iterationIndex || 0,
|
|
||||||
} : {},
|
|
||||||
status: 'retry',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an iteration node to node data.
|
|
||||||
*/
|
|
||||||
function convertIterationNode(node: Node): NodeData[] {
|
|
||||||
const { nodeId, params } = node as NodeComplex
|
|
||||||
const result: NodeData[] = [
|
|
||||||
{
|
|
||||||
id: nodeId,
|
|
||||||
node_id: nodeId,
|
|
||||||
title: nodeId,
|
|
||||||
node_type: 'iteration',
|
|
||||||
status: 'succeeded',
|
|
||||||
execution_metadata: {},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
params?.forEach((param: any) => {
|
|
||||||
if (Array.isArray(param)) {
|
|
||||||
param.forEach((childNode: Node) => {
|
|
||||||
const childData = convertToNodeData([childNode])
|
|
||||||
childData.forEach((data) => {
|
|
||||||
data.execution_metadata = {
|
|
||||||
...data.execution_metadata,
|
|
||||||
iteration_id: nodeId,
|
|
||||||
iteration_index: 0,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
result.push(...childData)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a parallel node to node data.
|
|
||||||
*/
|
|
||||||
function convertParallelNode(node: Node, parentParallelId?: string, parentStartNodeId?: string): NodeData[] {
|
|
||||||
const { nodeId, params } = node as NodeComplex
|
|
||||||
const result: NodeData[] = [
|
|
||||||
{
|
|
||||||
id: nodeId,
|
|
||||||
node_id: nodeId,
|
|
||||||
title: nodeId,
|
|
||||||
execution_metadata: {
|
|
||||||
parallel_id: nodeId,
|
|
||||||
},
|
|
||||||
status: 'succeeded',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
params?.forEach((param) => {
|
|
||||||
if (Array.isArray(param)) {
|
|
||||||
const startNodeId = param[0]?.nodeId
|
|
||||||
param.forEach((childNode: Node) => {
|
|
||||||
const childData = convertToNodeData([childNode])
|
|
||||||
childData.forEach((data) => {
|
|
||||||
data.execution_metadata = {
|
|
||||||
...data.execution_metadata,
|
|
||||||
parallel_id: nodeId,
|
|
||||||
parallel_start_node_id: startNodeId,
|
|
||||||
...(parentParallelId && {
|
|
||||||
parent_parallel_id: parentParallelId,
|
|
||||||
parent_parallel_start_node_id: parentStartNodeId,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
result.push(...childData)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else if (param && typeof param === 'object') {
|
|
||||||
const startNodeId = param.nodeId
|
|
||||||
const childData = convertToNodeData([param])
|
|
||||||
childData.forEach((data) => {
|
|
||||||
data.execution_metadata = {
|
|
||||||
...data.execution_metadata,
|
|
||||||
parallel_id: nodeId,
|
|
||||||
parallel_start_node_id: startNodeId,
|
|
||||||
...(parentParallelId && {
|
|
||||||
parent_parallel_id: parentParallelId,
|
|
||||||
parent_parallel_start_node_id: parentStartNodeId,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
result.push(...childData)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main function to convert nodes to node data.
|
|
||||||
*/
|
|
||||||
function convertToNodeData(nodes: Node[], parentParallelId?: string, parentStartNodeId?: string): NodeData[] {
|
|
||||||
const result: NodeData[] = []
|
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
switch (node.nodeType) {
|
|
||||||
case 'plain':
|
|
||||||
result.push(...convertPlainNode(node))
|
|
||||||
break
|
|
||||||
case 'retry':
|
|
||||||
result.push(...convertRetryNode(node))
|
|
||||||
break
|
|
||||||
case 'iteration':
|
|
||||||
result.push(...convertIterationNode(node))
|
|
||||||
break
|
|
||||||
case 'parallel':
|
|
||||||
result.push(...convertParallelNode(node, parentParallelId, parentStartNodeId))
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown nodeType: ${node.nodeType}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export { parseDSL }
|
|
||||||
@ -1,97 +1,128 @@
|
|||||||
import graphToLogStruct, { parseNodeString } from './graph-to-log-struct'
|
import parseDSL from './graph-to-log-struct'
|
||||||
|
|
||||||
describe('graphToLogStruct', () => {
|
describe('parseDSL', () => {
|
||||||
test('parseNodeString', () => {
|
it('should parse plain nodes correctly', () => {
|
||||||
expect(parseNodeString('(node1, param1, (node2, param2, (node3, param1)), param4)')).toEqual({
|
const dsl = 'plainNode1 -> plainNode2'
|
||||||
node: 'node1',
|
const result = parseDSL(dsl)
|
||||||
params: [
|
expect(result).toEqual([
|
||||||
'param1',
|
{ id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: {}, status: 'succeeded' },
|
||||||
{
|
{ id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: {}, status: 'succeeded' },
|
||||||
node: 'node2',
|
])
|
||||||
params: [
|
|
||||||
'param2',
|
|
||||||
{
|
|
||||||
node: 'node3',
|
|
||||||
params: [
|
|
||||||
'param1',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'param4',
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should parse retry nodes correctly', () => {
|
||||||
|
const dsl = '(retry, retryNode, 3)'
|
||||||
|
const result = parseDSL(dsl)
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'succeeded' },
|
||||||
|
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'retry' },
|
||||||
|
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'retry' },
|
||||||
|
{ id: 'retryNode', node_id: 'retryNode', title: 'retryNode', execution_metadata: {}, status: 'retry' },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
test('iteration nodes', () => {
|
|
||||||
expect(graphToLogStruct('start -> (iteration, 1, [2, 3])')).toEqual([
|
it('should parse iteration nodes correctly', () => {
|
||||||
|
const dsl = '(iteration, iterationNode, plainNode1 -> plainNode2)'
|
||||||
|
const result = parseDSL(dsl)
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 'iterationNode', node_id: 'iterationNode', title: 'iterationNode', node_type: 'iteration', execution_metadata: {}, status: 'succeeded' },
|
||||||
|
{ id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0 }, status: 'succeeded' },
|
||||||
|
{ id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0 }, status: 'succeeded' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse parallel nodes correctly', () => {
|
||||||
|
const dsl = '(parallel, parallelNode, nodeA, nodeB -> nodeC)'
|
||||||
|
const result = parseDSL(dsl)
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 'parallelNode', node_id: 'parallelNode', title: 'parallelNode', execution_metadata: { parallel_id: 'parallelNode' }, status: 'succeeded' },
|
||||||
|
{ id: 'nodeA', node_id: 'nodeA', title: 'nodeA', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'nodeA' }, status: 'succeeded' },
|
||||||
|
{ id: 'nodeB', node_id: 'nodeB', title: 'nodeB', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'nodeB' }, status: 'succeeded' },
|
||||||
|
{ id: 'nodeC', node_id: 'nodeC', title: 'nodeC', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'nodeB' }, status: 'succeeded' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
it('should handle nested parallel nodes', () => {
|
||||||
|
const dsl = '(parallel, outerParallel, (parallel, innerParallel, plainNode1 -> plainNode2) -> plainNode3)'
|
||||||
|
const result = parseDSL(dsl)
|
||||||
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
id: 'start',
|
id: 'outerParallel',
|
||||||
node_id: 'start',
|
node_id: 'outerParallel',
|
||||||
title: 'start',
|
title: 'outerParallel',
|
||||||
execution_metadata: {},
|
execution_metadata: { parallel_id: 'outerParallel' },
|
||||||
status: 'succeeded',
|
status: 'succeeded',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1',
|
id: 'innerParallel',
|
||||||
node_id: '1',
|
node_id: 'innerParallel',
|
||||||
title: '1',
|
title: 'innerParallel',
|
||||||
execution_metadata: {},
|
execution_metadata: { parallel_id: 'outerParallel', parallel_start_node_id: 'innerParallel' },
|
||||||
status: 'succeeded',
|
status: 'succeeded',
|
||||||
node_type: 'iteration',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: 'plainNode1',
|
||||||
node_id: '2',
|
node_id: 'plainNode1',
|
||||||
title: '2',
|
title: 'plainNode1',
|
||||||
execution_metadata: { iteration_id: '1', iteration_index: 0 },
|
execution_metadata: {
|
||||||
status: 'succeeded',
|
parallel_id: 'innerParallel',
|
||||||
|
parallel_start_node_id: 'plainNode1',
|
||||||
|
parent_parallel_id: 'outerParallel',
|
||||||
|
parent_parallel_start_node_id: 'innerParallel',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
node_id: '3',
|
|
||||||
title: '3',
|
|
||||||
execution_metadata: { iteration_id: '1', iteration_index: 1 },
|
|
||||||
status: 'succeeded',
|
status: 'succeeded',
|
||||||
},
|
},
|
||||||
])
|
|
||||||
})
|
|
||||||
test('retry nodes', () => {
|
|
||||||
expect(graphToLogStruct('start -> (retry, 1, 3)')).toEqual([
|
|
||||||
{
|
{
|
||||||
id: 'start',
|
id: 'plainNode2',
|
||||||
node_id: 'start',
|
node_id: 'plainNode2',
|
||||||
title: 'start',
|
title: 'plainNode2',
|
||||||
execution_metadata: {},
|
execution_metadata: {
|
||||||
status: 'succeeded',
|
parallel_id: 'innerParallel',
|
||||||
|
parallel_start_node_id: 'plainNode1',
|
||||||
|
parent_parallel_id: 'outerParallel',
|
||||||
|
parent_parallel_start_node_id: 'innerParallel',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
node_id: '1',
|
|
||||||
title: '1',
|
|
||||||
execution_metadata: {},
|
|
||||||
status: 'succeeded',
|
status: 'succeeded',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1',
|
id: 'plainNode3',
|
||||||
node_id: '1',
|
node_id: 'plainNode3',
|
||||||
title: '1',
|
title: 'plainNode3',
|
||||||
execution_metadata: {},
|
execution_metadata: {
|
||||||
status: 'retry',
|
parallel_id: 'outerParallel',
|
||||||
},
|
parallel_start_node_id: 'plainNode3',
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
node_id: '1',
|
|
||||||
title: '1',
|
|
||||||
execution_metadata: {},
|
|
||||||
status: 'retry',
|
|
||||||
},
|
},
|
||||||
{
|
status: 'succeeded',
|
||||||
id: '1',
|
|
||||||
node_id: '1',
|
|
||||||
title: '1',
|
|
||||||
execution_metadata: {},
|
|
||||||
status: 'retry',
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// iterations not support nested iterations
|
||||||
|
// it('should handle nested iterations', () => {
|
||||||
|
// const dsl = '(iteration, outerIteration, (iteration, innerIteration -> plainNode1 -> plainNode2))'
|
||||||
|
// const result = parseDSL(dsl)
|
||||||
|
// expect(result).toEqual([
|
||||||
|
// { id: 'outerIteration', node_id: 'outerIteration', title: 'outerIteration', node_type: 'iteration', execution_metadata: {}, status: 'succeeded' },
|
||||||
|
// { id: 'innerIteration', node_id: 'innerIteration', title: 'innerIteration', node_type: 'iteration', execution_metadata: { iteration_id: 'outerIteration', iteration_index: 0 }, status: 'succeeded' },
|
||||||
|
// { id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: { iteration_id: 'innerIteration', iteration_index: 0 }, status: 'succeeded' },
|
||||||
|
// { id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: { iteration_id: 'innerIteration', iteration_index: 0 }, status: 'succeeded' },
|
||||||
|
// ])
|
||||||
|
// })
|
||||||
|
|
||||||
|
it('should handle nested iterations within parallel nodes', () => {
|
||||||
|
const dsl = '(parallel, parallelNode, (iteration, iterationNode, plainNode1, plainNode2))'
|
||||||
|
const result = parseDSL(dsl)
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ id: 'parallelNode', node_id: 'parallelNode', title: 'parallelNode', execution_metadata: { parallel_id: 'parallelNode' }, status: 'succeeded' },
|
||||||
|
{ id: 'iterationNode', node_id: 'iterationNode', title: 'iterationNode', node_type: 'iteration', execution_metadata: { parallel_id: 'parallelNode', parallel_start_node_id: 'iterationNode' }, status: 'succeeded' },
|
||||||
|
{ id: 'plainNode1', node_id: 'plainNode1', title: 'plainNode1', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0, parallel_id: 'parallelNode', parallel_start_node_id: 'iterationNode' }, status: 'succeeded' },
|
||||||
|
{ id: 'plainNode2', node_id: 'plainNode2', title: 'plainNode2', execution_metadata: { iteration_id: 'iterationNode', iteration_index: 0, parallel_id: 'parallelNode', parallel_start_node_id: 'iterationNode' }, status: 'succeeded' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error for unknown node types', () => {
|
||||||
|
const dsl = '(unknown, nodeId)'
|
||||||
|
expect(() => parseDSL(dsl)).toThrowError('Unknown nodeType: unknown')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,174 +1,304 @@
|
|||||||
const STEP_SPLIT = '->'
|
type IterationInfo = { iterationId: string; iterationIndex: number }
|
||||||
|
type NodePlain = { nodeType: 'plain'; nodeId: string; } & Partial<IterationInfo>
|
||||||
|
type NodeComplex = { nodeType: string; nodeId: string; params: (NodePlain | (NodeComplex & Partial<IterationInfo>) | Node[] | number)[] } & Partial<IterationInfo>
|
||||||
|
type Node = NodePlain | NodeComplex
|
||||||
|
|
||||||
const toNodeData = (step: string, info: Record<string, any> = {}): any => {
|
/**
|
||||||
const [nodeId, title] = step.split('@')
|
* Parses a DSL string into an array of node objects.
|
||||||
|
* @param dsl - The input DSL string.
|
||||||
const data: Record<string, any> = {
|
* @returns An array of parsed nodes.
|
||||||
id: nodeId,
|
*/
|
||||||
node_id: nodeId,
|
function parseDSL(dsl: string): NodeData[] {
|
||||||
title: title || nodeId,
|
return convertToNodeData(parseTopLevelFlow(dsl).map(nodeStr => parseNode(nodeStr)))
|
||||||
execution_metadata: {},
|
|
||||||
status: 'succeeded',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionMetadata = data.execution_metadata
|
/**
|
||||||
const { isRetry, isIteration, inIterationInfo } = info
|
* Splits a top-level flow string by "->", respecting nested structures.
|
||||||
if (isRetry)
|
* @param dsl - The DSL string to split.
|
||||||
data.status = 'retry'
|
* @returns An array of top-level segments.
|
||||||
|
*/
|
||||||
if (isIteration)
|
function parseTopLevelFlow(dsl: string): string[] {
|
||||||
data.node_type = 'iteration'
|
const segments: string[] = []
|
||||||
|
let buffer = ''
|
||||||
|
let nested = 0
|
||||||
|
|
||||||
if (inIterationInfo) {
|
for (let i = 0; i < dsl.length; i++) {
|
||||||
executionMetadata.iteration_id = inIterationInfo.iterationId
|
const char = dsl[i]
|
||||||
executionMetadata.iteration_index = inIterationInfo.iterationIndex
|
if (char === '(') nested++
|
||||||
|
if (char === ')') nested--
|
||||||
|
if (char === '-' && dsl[i + 1] === '>' && nested === 0) {
|
||||||
|
segments.push(buffer.trim())
|
||||||
|
buffer = ''
|
||||||
|
i++ // Skip the ">" character
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer += char
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (buffer.trim())
|
||||||
|
segments.push(buffer.trim())
|
||||||
|
|
||||||
return data
|
return segments
|
||||||
}
|
}
|
||||||
|
|
||||||
const toRetryNodeData = ({
|
/**
|
||||||
nodeId,
|
* Parses a single node string.
|
||||||
repeatTimes,
|
* If the node is complex (e.g., has parentheses), it extracts the node type, node ID, and parameters.
|
||||||
}: {
|
* @param nodeStr - The node string to parse.
|
||||||
nodeId: string,
|
* @param parentIterationId - The ID of the parent iteration node (if applicable).
|
||||||
repeatTimes: number,
|
* @returns A parsed node object.
|
||||||
}): any => {
|
*/
|
||||||
const res = [toNodeData(nodeId)]
|
function parseNode(nodeStr: string, parentIterationId?: string): Node {
|
||||||
for (let i = 0; i < repeatTimes; i++)
|
// Check if the node is a complex node
|
||||||
res.push(toNodeData(nodeId, { isRetry: true }))
|
if (nodeStr.startsWith('(') && nodeStr.endsWith(')')) {
|
||||||
return res
|
const innerContent = nodeStr.slice(1, -1).trim() // Remove outer parentheses
|
||||||
|
let nested = 0
|
||||||
|
let buffer = ''
|
||||||
|
const parts: string[] = []
|
||||||
|
|
||||||
|
// Split the inner content by commas, respecting nested parentheses
|
||||||
|
for (let i = 0; i < innerContent.length; i++) {
|
||||||
|
const char = innerContent[i]
|
||||||
|
if (char === '(') nested++
|
||||||
|
if (char === ')') nested--
|
||||||
|
|
||||||
|
if (char === ',' && nested === 0) {
|
||||||
|
parts.push(buffer.trim())
|
||||||
|
buffer = ''
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer += char
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
parts.push(buffer.trim())
|
||||||
|
|
||||||
const toIterationNodeData = ({
|
// Extract nodeType, nodeId, and params
|
||||||
nodeId,
|
const [nodeType, nodeId, ...paramsRaw] = parts
|
||||||
children,
|
const params = parseParams(paramsRaw, nodeType === 'iteration' ? nodeId.trim() : parentIterationId)
|
||||||
}: {
|
const complexNode = {
|
||||||
nodeId: string,
|
nodeType: nodeType.trim(),
|
||||||
children: number[],
|
nodeId: nodeId.trim(),
|
||||||
}) => {
|
params,
|
||||||
const res = [toNodeData(nodeId, { isIteration: true })]
|
}
|
||||||
// TODO: handle inner node structure
|
if (parentIterationId) {
|
||||||
for (let i = 0; i < children.length; i++) {
|
(complexNode as any).iterationId = parentIterationId;
|
||||||
const step = `${children[i]}`
|
(complexNode as any).iterationIndex = 0 // Fixed as 0
|
||||||
res.push(toNodeData(step, { inIterationInfo: { iterationId: nodeId, iterationIndex: i } }))
|
}
|
||||||
|
return complexNode
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
// If it's not a complex node, treat it as a plain node
|
||||||
|
const plainNode: NodePlain = { nodeType: 'plain', nodeId: nodeStr.trim() }
|
||||||
|
if (parentIterationId) {
|
||||||
|
plainNode.iterationId = parentIterationId
|
||||||
|
plainNode.iterationIndex = 0 // Fixed as 0
|
||||||
|
}
|
||||||
|
return plainNode
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeStructure = {
|
/**
|
||||||
node: string;
|
* Parses parameters of a complex node.
|
||||||
params: Array<string | NodeStructure>;
|
* Supports nested flows and complex sub-nodes.
|
||||||
|
* Adds iteration-specific metadata recursively.
|
||||||
|
* @param paramParts - The parameters string split by commas.
|
||||||
|
* @param iterationId - The ID of the iteration node, if applicable.
|
||||||
|
* @returns An array of parsed parameters (plain nodes, nested nodes, or flows).
|
||||||
|
*/
|
||||||
|
function parseParams(paramParts: string[], iterationId?: string): (Node | Node[] | number)[] {
|
||||||
|
return paramParts.map((part) => {
|
||||||
|
if (part.includes('->')) {
|
||||||
|
// Parse as a flow and return an array of nodes
|
||||||
|
return parseTopLevelFlow(part).map(node => parseNode(node, iterationId))
|
||||||
|
}
|
||||||
|
else if (part.startsWith('(')) {
|
||||||
|
// Parse as a nested complex node
|
||||||
|
return parseNode(part, iterationId)
|
||||||
|
}
|
||||||
|
else if (!Number.isNaN(Number(part.trim()))) {
|
||||||
|
// Parse as a numeric parameter
|
||||||
|
return Number(part.trim())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Parse as a plain node
|
||||||
|
return parseNode(part, iterationId)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseNodeString(input: string): NodeStructure {
|
type NodeData = {
|
||||||
input = input.trim()
|
id: string;
|
||||||
if (input.startsWith('(') && input.endsWith(')'))
|
node_id: string;
|
||||||
input = input.slice(1, -1)
|
title: string;
|
||||||
|
node_type?: string;
|
||||||
|
execution_metadata: Record<string, any>;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
const parts: Array<string | NodeStructure> = []
|
/**
|
||||||
let current = ''
|
* Converts a plain node to node data.
|
||||||
let depth = 0
|
*/
|
||||||
let inArrayDepth = 0
|
function convertPlainNode(node: Node): NodeData[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: node.nodeId,
|
||||||
|
node_id: node.nodeId,
|
||||||
|
title: node.nodeId,
|
||||||
|
execution_metadata: {},
|
||||||
|
status: 'succeeded',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
/**
|
||||||
const char = input[i]
|
* Converts a retry node to node data.
|
||||||
|
*/
|
||||||
|
function convertRetryNode(node: Node): NodeData[] {
|
||||||
|
const { nodeId, iterationId, iterationIndex, params } = node as NodeComplex
|
||||||
|
const retryCount = params ? Number.parseInt(params[0] as unknown as string, 10) : 0
|
||||||
|
const result: NodeData[] = [
|
||||||
|
{
|
||||||
|
id: nodeId,
|
||||||
|
node_id: nodeId,
|
||||||
|
title: nodeId,
|
||||||
|
execution_metadata: {},
|
||||||
|
status: 'succeeded',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
if (char === '(')
|
for (let i = 0; i < retryCount; i++) {
|
||||||
depth++
|
result.push({
|
||||||
else if (char === ')')
|
id: nodeId,
|
||||||
depth--
|
node_id: nodeId,
|
||||||
|
title: nodeId,
|
||||||
|
execution_metadata: iterationId ? {
|
||||||
|
iteration_id: iterationId,
|
||||||
|
iteration_index: iterationIndex || 0,
|
||||||
|
} : {},
|
||||||
|
status: 'retry',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (char === '[')
|
return result
|
||||||
inArrayDepth++
|
}
|
||||||
else if (char === ']')
|
|
||||||
inArrayDepth--
|
|
||||||
|
|
||||||
const isInArray = inArrayDepth > 0
|
/**
|
||||||
|
* Converts an iteration node to node data.
|
||||||
|
*/
|
||||||
|
function convertIterationNode(node: Node): NodeData[] {
|
||||||
|
const { nodeId, params } = node as NodeComplex
|
||||||
|
const result: NodeData[] = [
|
||||||
|
{
|
||||||
|
id: nodeId,
|
||||||
|
node_id: nodeId,
|
||||||
|
title: nodeId,
|
||||||
|
node_type: 'iteration',
|
||||||
|
status: 'succeeded',
|
||||||
|
execution_metadata: {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
if (char === ',' && depth === 0 && !isInArray) {
|
params?.forEach((param: any) => {
|
||||||
parts.push(current.trim())
|
if (Array.isArray(param)) {
|
||||||
current = ''
|
param.forEach((childNode: Node) => {
|
||||||
}
|
const childData = convertToNodeData([childNode])
|
||||||
else {
|
childData.forEach((data) => {
|
||||||
current += char
|
data.execution_metadata = {
|
||||||
|
...data.execution_metadata,
|
||||||
|
iteration_id: nodeId,
|
||||||
|
iteration_index: 0,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
result.push(...childData)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (current)
|
return result
|
||||||
parts.push(current.trim())
|
|
||||||
|
|
||||||
const result: NodeStructure = {
|
|
||||||
node: '',
|
|
||||||
params: [],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
/**
|
||||||
const part = parts[i]
|
* Converts a parallel node to node data.
|
||||||
|
*/
|
||||||
if (typeof part === 'string') {
|
function convertParallelNode(node: Node, parentParallelId?: string, parentStartNodeId?: string): NodeData[] {
|
||||||
if (part.startsWith('('))
|
const { nodeId, params } = node as NodeComplex
|
||||||
result.params.push(parseNodeString(part))
|
const result: NodeData[] = [
|
||||||
|
{
|
||||||
|
id: nodeId,
|
||||||
|
node_id: nodeId,
|
||||||
|
title: nodeId,
|
||||||
|
execution_metadata: {
|
||||||
|
parallel_id: nodeId,
|
||||||
|
},
|
||||||
|
status: 'succeeded',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
if (part.startsWith('[')) {
|
params?.forEach((param) => {
|
||||||
const content = part.slice(1, -1)
|
if (Array.isArray(param)) {
|
||||||
result.params.push(parseNodeString(content))
|
const startNodeId = param[0]?.nodeId
|
||||||
}
|
param.forEach((childNode: Node) => {
|
||||||
|
const childData = convertToNodeData([childNode])
|
||||||
|
childData.forEach((data) => {
|
||||||
|
data.execution_metadata = {
|
||||||
|
...data.execution_metadata,
|
||||||
|
parallel_id: nodeId,
|
||||||
|
parallel_start_node_id: startNodeId,
|
||||||
|
...(parentParallelId && {
|
||||||
|
parent_parallel_id: parentParallelId,
|
||||||
|
parent_parallel_start_node_id: parentStartNodeId,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
else if (i === 0) {
|
})
|
||||||
result.node = part as unknown as string
|
result.push(...childData)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
else {
|
else if (param && typeof param === 'object') {
|
||||||
result.params.push(part as unknown as string)
|
const startNodeId = param.nodeId
|
||||||
|
const childData = convertToNodeData([param])
|
||||||
|
childData.forEach((data) => {
|
||||||
|
data.execution_metadata = {
|
||||||
|
...data.execution_metadata,
|
||||||
|
parallel_id: nodeId,
|
||||||
|
parallel_start_node_id: startNodeId,
|
||||||
|
...(parentParallelId && {
|
||||||
|
parent_parallel_id: parentParallelId,
|
||||||
|
parent_parallel_start_node_id: parentStartNodeId,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
result.push(...childData)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const toNodes = (input: string): any[] => {
|
/**
|
||||||
const list = input.split(STEP_SPLIT)
|
* Main function to convert nodes to node data.
|
||||||
.map(step => step.trim())
|
*/
|
||||||
|
function convertToNodeData(nodes: Node[], parentParallelId?: string, parentStartNodeId?: string): NodeData[] {
|
||||||
const res: any[] = []
|
const result: NodeData[] = []
|
||||||
list.forEach((step) => {
|
|
||||||
const isPlainStep = !step.includes('(')
|
|
||||||
if (isPlainStep) {
|
|
||||||
res.push(toNodeData(step))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { node, params } = parseNodeString(step)
|
nodes.forEach((node) => {
|
||||||
switch (node) {
|
switch (node.nodeType) {
|
||||||
case 'iteration':
|
case 'plain':
|
||||||
console.log(params)
|
result.push(...convertPlainNode(node))
|
||||||
break
|
|
||||||
res.push(...toIterationNodeData({
|
|
||||||
nodeId: params[0] as string,
|
|
||||||
children: JSON.parse(params[1] as string) as number[],
|
|
||||||
}))
|
|
||||||
break
|
break
|
||||||
case 'retry':
|
case 'retry':
|
||||||
res.push(...toRetryNodeData({
|
result.push(...convertRetryNode(node))
|
||||||
nodeId: params[0] as string,
|
break
|
||||||
repeatTimes: Number.parseInt(params[1] as string),
|
case 'iteration':
|
||||||
}))
|
result.push(...convertIterationNode(node))
|
||||||
break
|
break
|
||||||
|
case 'parallel':
|
||||||
|
result.push(...convertParallelNode(node, parentParallelId, parentStartNodeId))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown nodeType: ${node.nodeType}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
return result
|
||||||
* : 1 -> 2 -> 3
|
|
||||||
* iteration: (iteration, 1, [2, 3]) -> 4. (1, [2, 3]) means 1 is parent, [2, 3] is children
|
|
||||||
* parallel: 1 -> (parallel, [1,2,3], [4, (parallel: (6,7))]).
|
|
||||||
* retry: (retry, 1, 3). 1 is parent, 3 is retry times
|
|
||||||
*/
|
|
||||||
const graphToLogStruct = (input: string): any[] => {
|
|
||||||
const list = toNodes(input)
|
|
||||||
return list
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default graphToLogStruct
|
export default parseDSL
|
||||||
|
|||||||
Loading…
Reference in New Issue