mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
feat(workflow): add selection context menu helpers and integrate with context menu component (#34013)
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: lif <1835304752@qq.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: Stephen Zhou <hi@hyoban.cc> Co-authored-by: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Co-authored-by: Desel72 <pedroluiscolmenares722@gmail.com> Co-authored-by: Renzo <170978465+RenzoMXD@users.noreply.github.com> Co-authored-by: Krishna Chaitanya <krishnabkc15@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@ -0,0 +1,199 @@
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
import formatToTracingNodeList from '../index'
|
||||
|
||||
const mockFormatAgentNode = vi.hoisted(() => vi.fn())
|
||||
const mockFormatHumanInputNode = vi.hoisted(() => vi.fn())
|
||||
const mockFormatRetryNode = vi.hoisted(() => vi.fn())
|
||||
const mockAddChildrenToLoopNode = vi.hoisted(() => vi.fn())
|
||||
const mockAddChildrenToIterationNode = vi.hoisted(() => vi.fn())
|
||||
const mockFormatParallelNode = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('../agent', () => ({
|
||||
__esModule: true,
|
||||
default: (...args: unknown[]) => mockFormatAgentNode(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../human-input', () => ({
|
||||
__esModule: true,
|
||||
default: (...args: unknown[]) => mockFormatHumanInputNode(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../retry', () => ({
|
||||
__esModule: true,
|
||||
default: (...args: unknown[]) => mockFormatRetryNode(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../loop', () => ({
|
||||
addChildrenToLoopNode: (...args: unknown[]) => mockAddChildrenToLoopNode(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../iteration', () => ({
|
||||
addChildrenToIterationNode: (...args: unknown[]) => mockAddChildrenToIterationNode(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../parallel', () => ({
|
||||
__esModule: true,
|
||||
default: (...args: unknown[]) => mockFormatParallelNode(...args),
|
||||
}))
|
||||
|
||||
const createTrace = (overrides: Partial<NodeTracing> = {}): NodeTracing => ({
|
||||
id: overrides.id ?? overrides.node_id ?? 'node-1',
|
||||
index: overrides.index ?? 0,
|
||||
predecessor_node_id: '',
|
||||
node_id: overrides.node_id ?? 'node-1',
|
||||
node_type: overrides.node_type ?? BlockEnum.Tool,
|
||||
title: overrides.title ?? 'Node',
|
||||
inputs: {},
|
||||
inputs_truncated: false,
|
||||
process_data: {},
|
||||
process_data_truncated: false,
|
||||
outputs_truncated: false,
|
||||
status: overrides.status ?? 'succeeded',
|
||||
error: overrides.error,
|
||||
elapsed_time: 1,
|
||||
execution_metadata: overrides.execution_metadata ?? {
|
||||
total_tokens: 0,
|
||||
total_price: 0,
|
||||
currency: 'USD',
|
||||
},
|
||||
metadata: {
|
||||
iterator_length: 0,
|
||||
iterator_index: 0,
|
||||
loop_length: 0,
|
||||
loop_index: 0,
|
||||
},
|
||||
created_at: 0,
|
||||
created_by: {
|
||||
id: 'user-1',
|
||||
name: 'User',
|
||||
email: 'user@example.com',
|
||||
},
|
||||
finished_at: 1,
|
||||
})
|
||||
|
||||
const createExecutionMetadata = (overrides: Partial<NonNullable<NodeTracing['execution_metadata']>> = {}): NonNullable<NodeTracing['execution_metadata']> => ({
|
||||
total_tokens: 0,
|
||||
total_price: 0,
|
||||
currency: 'USD',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('formatToTracingNodeList', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockFormatAgentNode.mockImplementation((list: NodeTracing[]) => list)
|
||||
mockFormatHumanInputNode.mockImplementation((list: NodeTracing[]) => list)
|
||||
mockFormatRetryNode.mockImplementation((list: NodeTracing[]) => list)
|
||||
mockAddChildrenToLoopNode.mockImplementation((item: NodeTracing, children: NodeTracing[]) => ({
|
||||
...item,
|
||||
loopChildren: children.map(child => child.node_id),
|
||||
details: [[{ id: 'loop-detail-row' }]],
|
||||
}))
|
||||
mockAddChildrenToIterationNode.mockImplementation((item: NodeTracing, children: NodeTracing[]) => ({
|
||||
...item,
|
||||
iterationChildren: children.map(child => child.node_id),
|
||||
details: [[{ id: 'iteration-detail-row' }]],
|
||||
}))
|
||||
mockFormatParallelNode.mockImplementation((list: unknown[]) =>
|
||||
list.map(item => ({
|
||||
...(item as Record<string, unknown>),
|
||||
parallelFormatted: true,
|
||||
})))
|
||||
})
|
||||
|
||||
it('should sort the input by index and run the formatter pipeline in order', () => {
|
||||
const t = vi.fn((key: string) => key)
|
||||
const traces = [
|
||||
createTrace({ id: 'b', node_id: 'b', title: 'B', index: 2 }),
|
||||
createTrace({ id: 'a', node_id: 'a', title: 'A', index: 0 }),
|
||||
createTrace({ id: 'c', node_id: 'c', title: 'C', index: 1 }),
|
||||
]
|
||||
|
||||
const result = formatToTracingNodeList(traces, t)
|
||||
|
||||
expect(mockFormatAgentNode).toHaveBeenCalledWith([
|
||||
expect.objectContaining({ node_id: 'a' }),
|
||||
expect.objectContaining({ node_id: 'c' }),
|
||||
expect.objectContaining({ node_id: 'b' }),
|
||||
])
|
||||
expect(mockFormatHumanInputNode).toHaveBeenCalledWith(mockFormatAgentNode.mock.results[0].value)
|
||||
expect(mockFormatRetryNode).toHaveBeenCalledWith(mockFormatHumanInputNode.mock.results[0].value)
|
||||
expect(mockFormatParallelNode).toHaveBeenLastCalledWith(expect.any(Array), t)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({ node_id: 'a', parallelFormatted: true }),
|
||||
expect.objectContaining({ node_id: 'c', parallelFormatted: true }),
|
||||
expect.objectContaining({ node_id: 'b', parallelFormatted: true }),
|
||||
])
|
||||
})
|
||||
|
||||
it('should collapse loop and iteration children into parent nodes and propagate child failures', () => {
|
||||
const t = vi.fn((key: string) => key)
|
||||
const loopParent = createTrace({
|
||||
id: 'loop-parent',
|
||||
node_id: 'loop-parent',
|
||||
node_type: BlockEnum.Loop,
|
||||
index: 0,
|
||||
})
|
||||
const loopChild = createTrace({
|
||||
id: 'loop-child',
|
||||
node_id: 'loop-child',
|
||||
index: 1,
|
||||
status: 'failed',
|
||||
error: 'loop child failed',
|
||||
execution_metadata: createExecutionMetadata({ loop_id: 'loop-parent' }),
|
||||
})
|
||||
const iterationParent = createTrace({
|
||||
id: 'iteration-parent',
|
||||
node_id: 'iteration-parent',
|
||||
node_type: BlockEnum.Iteration,
|
||||
index: 2,
|
||||
})
|
||||
const iterationChild = createTrace({
|
||||
id: 'iteration-child',
|
||||
node_id: 'iteration-child',
|
||||
index: 3,
|
||||
status: 'failed',
|
||||
error: 'iteration child failed',
|
||||
execution_metadata: createExecutionMetadata({ iteration_id: 'iteration-parent' }),
|
||||
})
|
||||
|
||||
const result = formatToTracingNodeList([
|
||||
loopParent,
|
||||
loopChild,
|
||||
iterationParent,
|
||||
iterationChild,
|
||||
], t)
|
||||
|
||||
expect(mockAddChildrenToLoopNode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
node_id: 'loop-parent',
|
||||
status: 'failed',
|
||||
error: 'loop child failed',
|
||||
}),
|
||||
[expect.objectContaining({ node_id: 'loop-child' })],
|
||||
)
|
||||
expect(mockAddChildrenToIterationNode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
node_id: 'iteration-parent',
|
||||
status: 'failed',
|
||||
error: 'iteration child failed',
|
||||
}),
|
||||
[expect.objectContaining({ node_id: 'iteration-child' })],
|
||||
)
|
||||
expect(mockFormatParallelNode).toHaveBeenCalledTimes(3)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
node_id: 'loop-parent',
|
||||
loopChildren: ['loop-child'],
|
||||
parallelFormatted: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
node_id: 'iteration-parent',
|
||||
iterationChildren: ['iteration-child'],
|
||||
parallelFormatted: true,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user