mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-04-23 22:50:17 +08:00
feat: test buildNodesAndEdgesFromDSLComponents (#940)
### What problem does this PR solve? feat: test buildNodesAndEdgesFromDSLComponents #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
571aaaff22
commit
d9bc093df1
2
web/jest-setup.ts
Normal file
2
web/jest-setup.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import 'umi/test-setup';
|
33
web/jest.config.ts
Normal file
33
web/jest.config.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Config, configUmiAlias, createConfig } from 'umi/test';
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
return (await configUmiAlias({
|
||||||
|
...createConfig({
|
||||||
|
target: 'browser',
|
||||||
|
jsTransformer: 'esbuild',
|
||||||
|
// config opts for esbuild , it will pass to esbuild directly
|
||||||
|
jsTransformerOpts: { jsx: 'automatic' },
|
||||||
|
}),
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'**/*.{ts,tsx,js,jsx}',
|
||||||
|
'!.umi/**',
|
||||||
|
'!.umi-test/**',
|
||||||
|
'!.umi-production/**',
|
||||||
|
'!.umirc.{js,ts}',
|
||||||
|
'!.umirc.*.{js,ts}',
|
||||||
|
'!jest.config.{js,ts}',
|
||||||
|
'!coverage/**',
|
||||||
|
'!dist/**',
|
||||||
|
'!config/**',
|
||||||
|
'!mock/**',
|
||||||
|
],
|
||||||
|
// if you require some es-module npm package, please uncomment below line and insert your package name
|
||||||
|
// transformIgnorePatterns: ['node_modules/(?!.*(lodash-es|your-es-pkg-name)/)']
|
||||||
|
coverageThreshold: {
|
||||||
|
global: {
|
||||||
|
lines: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})) as Config.InitialOptions;
|
||||||
|
};
|
4784
web/package-lock.json
generated
4784
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,8 @@
|
|||||||
"postinstall": "umi setup",
|
"postinstall": "umi setup",
|
||||||
"lint": "umi lint --eslint-only",
|
"lint": "umi lint --eslint-only",
|
||||||
"setup": "umi setup",
|
"setup": "umi setup",
|
||||||
"start": "npm run dev"
|
"start": "npm run dev",
|
||||||
|
"test": "jest --no-cache --coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"antd": "^5.12.7",
|
"antd": "^5.12.7",
|
||||||
"axios": "^1.6.3",
|
"axios": "^1.6.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
|
"dagre": "^0.8.5",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"eventsource-parser": "^1.1.2",
|
"eventsource-parser": "^1.1.2",
|
||||||
"i18next": "^23.7.16",
|
"i18next": "^23.7.16",
|
||||||
@ -45,20 +47,28 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-dev-inspector/umi4-plugin": "^2.0.1",
|
"@react-dev-inspector/umi4-plugin": "^2.0.1",
|
||||||
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
|
"@testing-library/react": "^15.0.7",
|
||||||
|
"@types/dagre": "^0.7.52",
|
||||||
|
"@types/jest": "^29.5.12",
|
||||||
"@types/lodash": "^4.14.202",
|
"@types/lodash": "^4.14.202",
|
||||||
"@types/react": "^18.0.33",
|
"@types/react": "^18.0.33",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.7",
|
"@types/react-copy-to-clipboard": "^5.0.7",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@types/react-syntax-highlighter": "^15.5.11",
|
"@types/react-syntax-highlighter": "^15.5.11",
|
||||||
|
"@types/testing-library__jest-dom": "^6.0.0",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@types/webpack-env": "^1.18.4",
|
"@types/webpack-env": "^1.18.4",
|
||||||
"@umijs/lint": "^4.1.1",
|
"@umijs/lint": "^4.1.1",
|
||||||
"@umijs/plugins": "^4.1.0",
|
"@umijs/plugins": "^4.1.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.4",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"prettier-plugin-packagejson": "^2.4.9",
|
"prettier-plugin-packagejson": "^2.4.9",
|
||||||
"react-dev-inspector": "^2.0.1",
|
"react-dev-inspector": "^2.0.1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.0.3",
|
"typescript": "^5.0.3",
|
||||||
"umi-plugin-icons": "^0.1.1"
|
"umi-plugin-icons": "^0.1.1"
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import ReactFlow, {
|
|||||||
OnConnect,
|
OnConnect,
|
||||||
OnEdgesChange,
|
OnEdgesChange,
|
||||||
OnNodesChange,
|
OnNodesChange,
|
||||||
Position,
|
|
||||||
addEdge,
|
addEdge,
|
||||||
applyEdgeChanges,
|
applyEdgeChanges,
|
||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
@ -19,47 +18,24 @@ import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu';
|
|||||||
|
|
||||||
import FlowDrawer from '../flow-drawer';
|
import FlowDrawer from '../flow-drawer';
|
||||||
import { useHandleDrop, useShowDrawer } from '../hooks';
|
import { useHandleDrop, useShowDrawer } from '../hooks';
|
||||||
|
import { initialEdges, initialNodes } from '../mock';
|
||||||
|
import { getLayoutedElements } from '../utils';
|
||||||
import { TextUpdaterNode } from './node';
|
import { TextUpdaterNode } from './node';
|
||||||
|
|
||||||
const nodeTypes = { textUpdater: TextUpdaterNode };
|
const nodeTypes = { textUpdater: TextUpdaterNode };
|
||||||
|
|
||||||
const initialNodes = [
|
|
||||||
{
|
|
||||||
sourcePosition: Position.Left,
|
|
||||||
targetPosition: Position.Right,
|
|
||||||
id: 'node-1',
|
|
||||||
type: 'textUpdater',
|
|
||||||
position: { x: 400, y: 100 },
|
|
||||||
data: { label: 123 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePosition: Position.Right,
|
|
||||||
targetPosition: Position.Left,
|
|
||||||
id: '1',
|
|
||||||
data: { label: 'Hello' },
|
|
||||||
position: { x: 0, y: 50 },
|
|
||||||
type: 'input',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourcePosition: Position.Right,
|
|
||||||
targetPosition: Position.Left,
|
|
||||||
id: '2',
|
|
||||||
data: { label: 'World' },
|
|
||||||
position: { x: 200, y: 50 },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const initialEdges = [
|
|
||||||
{ id: '1-2', source: '1', target: '2', label: 'to the', type: 'step' },
|
|
||||||
];
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
sideWidth: number;
|
sideWidth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FlowCanvas({ sideWidth }: IProps) {
|
function FlowCanvas({ sideWidth }: IProps) {
|
||||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
|
||||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
initialNodes,
|
||||||
|
initialEdges,
|
||||||
|
'LR',
|
||||||
|
);
|
||||||
|
const [nodes, setNodes] = useState<Node[]>(layoutedNodes);
|
||||||
|
const [edges, setEdges] = useState<Edge[]>(layoutedEdges);
|
||||||
const { ref, menu, onNodeContextMenu, onPaneClick } =
|
const { ref, menu, onNodeContextMenu, onPaneClick } =
|
||||||
useHandleNodeContextMenu(sideWidth);
|
useHandleNodeContextMenu(sideWidth);
|
||||||
const { drawerVisible, hideDrawer, showDrawer } = useShowDrawer();
|
const { drawerVisible, hideDrawer, showDrawer } = useShowDrawer();
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
RocketOutlined,
|
RocketOutlined,
|
||||||
SendOutlined,
|
SendOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
import { Position } from 'reactflow';
|
||||||
|
|
||||||
export const componentList = [
|
export const componentList = [
|
||||||
{ name: 'Begin', icon: <SendOutlined />, description: '' },
|
{ name: 'Begin', icon: <SendOutlined />, description: '' },
|
||||||
@ -10,6 +11,39 @@ export const componentList = [
|
|||||||
{ name: 'Generate', icon: <MergeCellsOutlined />, description: '' },
|
{ name: 'Generate', icon: <MergeCellsOutlined />, description: '' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const initialNodes = [
|
||||||
|
{
|
||||||
|
sourcePosition: Position.Left,
|
||||||
|
targetPosition: Position.Right,
|
||||||
|
id: 'node-1',
|
||||||
|
type: 'textUpdater',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
// position: { x: 400, y: 100 },
|
||||||
|
data: { label: 123 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
id: '1',
|
||||||
|
data: { label: 'Hello' },
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
// position: { x: 0, y: 50 },
|
||||||
|
type: 'input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
id: '2',
|
||||||
|
data: { label: 'World' },
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
// position: { x: 200, y: 50 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const initialEdges = [
|
||||||
|
{ id: '1-2', source: '1', target: '2', label: 'to the', type: 'step' },
|
||||||
|
];
|
||||||
|
|
||||||
export const dsl = {
|
export const dsl = {
|
||||||
components: {
|
components: {
|
||||||
begin: {
|
begin: {
|
||||||
@ -17,8 +51,8 @@ export const dsl = {
|
|||||||
component_name: 'Begin',
|
component_name: 'Begin',
|
||||||
params: {},
|
params: {},
|
||||||
},
|
},
|
||||||
downstream: ['Answer:China'],
|
downstream: ['Answer:China'], // other edge target is downstream, edge source is current node id
|
||||||
upstream: [],
|
upstream: [], // edge source is upstream, edge target is current node id
|
||||||
},
|
},
|
||||||
'Answer:China': {
|
'Answer:China': {
|
||||||
obj: {
|
obj: {
|
||||||
|
30
web/src/pages/flow/utils.test.ts
Normal file
30
web/src/pages/flow/utils.test.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { dsl } from './mock';
|
||||||
|
import { buildNodesAndEdgesFromDSLComponents } from './utils';
|
||||||
|
|
||||||
|
test('buildNodesAndEdgesFromDSLComponents', () => {
|
||||||
|
const { edges, nodes } = buildNodesAndEdgesFromDSLComponents(dsl.components);
|
||||||
|
|
||||||
|
expect(nodes.length).toEqual(4);
|
||||||
|
expect(edges.length).toEqual(4);
|
||||||
|
|
||||||
|
expect(edges).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
source: 'begin',
|
||||||
|
target: 'Answer:China',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
source: 'Answer:China',
|
||||||
|
target: 'Retrieval:China',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
source: 'Retrieval:China',
|
||||||
|
target: 'Generate:China',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
source: 'Generate:China',
|
||||||
|
target: 'Answer:China',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
@ -1,10 +1,32 @@
|
|||||||
import { DSLComponents } from '@/interfaces/database/flow';
|
import { DSLComponents } from '@/interfaces/database/flow';
|
||||||
|
import dagre from 'dagre';
|
||||||
import { Edge, Node, Position } from 'reactflow';
|
import { Edge, Node, Position } from 'reactflow';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
export const buildNodesFromDSLComponents = (data: DSLComponents) => {
|
const buildEdges = (
|
||||||
|
operatorIds: string[],
|
||||||
|
currentId: string,
|
||||||
|
allEdges: Edge[],
|
||||||
|
isUpstream = false,
|
||||||
|
) => {
|
||||||
|
operatorIds.forEach((cur) => {
|
||||||
|
const source = isUpstream ? cur : currentId;
|
||||||
|
const target = isUpstream ? currentId : cur;
|
||||||
|
if (!allEdges.some((e) => e.source === source && e.target === target)) {
|
||||||
|
allEdges.push({
|
||||||
|
id: uuidv4(),
|
||||||
|
label: '',
|
||||||
|
type: 'step',
|
||||||
|
source: source,
|
||||||
|
target: target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildNodesAndEdgesFromDSLComponents = (data: DSLComponents) => {
|
||||||
const nodes: Node[] = [];
|
const nodes: Node[] = [];
|
||||||
const edges: Edge[] = [];
|
let edges: Edge[] = [];
|
||||||
|
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
const downstream = [...value.downstream];
|
const downstream = [...value.downstream];
|
||||||
@ -23,22 +45,51 @@ export const buildNodesFromDSLComponents = (data: DSLComponents) => {
|
|||||||
targetPosition: Position.Right,
|
targetPosition: Position.Right,
|
||||||
});
|
});
|
||||||
|
|
||||||
// intermediate node
|
buildEdges(upstream, key, edges, true);
|
||||||
// The first and last nodes do not need to be considered
|
buildEdges(downstream, key, edges, false);
|
||||||
if (upstream.length > 0 && downstream.length > 0) {
|
|
||||||
for (let i = 0; i < upstream.length; i++) {
|
|
||||||
const up = upstream[i];
|
|
||||||
for (let j = 0; j < downstream.length; j++) {
|
|
||||||
const down = downstream[j];
|
|
||||||
edges.push({
|
|
||||||
id: uuidv4(),
|
|
||||||
label: '',
|
|
||||||
type: 'step',
|
|
||||||
source: up,
|
|
||||||
target: down,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { nodes, edges };
|
||||||
|
};
|
||||||
|
|
||||||
|
const dagreGraph = new dagre.graphlib.Graph();
|
||||||
|
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||||
|
|
||||||
|
const nodeWidth = 172;
|
||||||
|
const nodeHeight = 36;
|
||||||
|
|
||||||
|
export const getLayoutedElements = (
|
||||||
|
nodes: Node[],
|
||||||
|
edges: Edge[],
|
||||||
|
direction = 'TB',
|
||||||
|
) => {
|
||||||
|
const isHorizontal = direction === 'LR';
|
||||||
|
dagreGraph.setGraph({ rankdir: direction });
|
||||||
|
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
||||||
|
});
|
||||||
|
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
dagreGraph.setEdge(edge.source, edge.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
dagre.layout(dagreGraph);
|
||||||
|
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const nodeWithPosition = dagreGraph.node(node.id);
|
||||||
|
node.targetPosition = isHorizontal ? Position.Left : Position.Top;
|
||||||
|
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
||||||
|
|
||||||
|
// We are shifting the dagre node position (anchor=center center) to the top left
|
||||||
|
// so it matches the React Flow node anchor point (top left).
|
||||||
|
node.position = {
|
||||||
|
x: nodeWithPosition.x - nodeWidth / 2,
|
||||||
|
y: nodeWithPosition.y - nodeHeight / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { nodes, edges };
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "./src/.umi/tsconfig.json",
|
"extends": "./src/.umi/tsconfig.json",
|
||||||
"@@/*": [
|
"@@/*": [
|
||||||
"src/.umi/*"
|
"src/.umi/*",
|
||||||
],
|
],
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user