feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`encapsulate-nodes-service > should decapsulateLayout 1`] = `
{
"edges": [
{
"sourceNodeID": "100001",
"targetNodeID": "154702",
},
{
"sourceNodeID": "102906",
"targetNodeID": "900001",
},
{
"sourceNodeID": "177547",
"targetNodeID": "154702",
},
{
"sourceNodeID": "154702",
"targetNodeID": "102906",
},
{
"sourceNodeID": "177547",
"targetNodeID": "109408",
},
],
"nodes": [
{
"data": undefined,
"id": "100001",
"meta": {
"position": {
"x": 180,
"y": 26.700000000000017,
},
},
"type": "1",
},
{
"data": undefined,
"id": "900001",
"meta": {
"position": {
"x": 1743.609729023942,
"y": 176.16991217034956,
},
},
"type": "2",
},
{
"data": undefined,
"id": "154702",
"meta": {
"position": {
"x": 987.7405729256998,
"y": 245.06502111375198,
},
},
"type": "3",
},
{
"data": {
"inputs": {
"spaceId": "test_space_id",
"workflowId": "test_workflow_id",
},
},
"id": "102906",
"meta": {
"position": {
"x": 848.7417419605051,
"y": -297.0809682834506,
},
},
"type": "9",
},
{
"data": undefined,
"id": "177547",
"meta": {
"position": {
"x": -125.93331336641754,
"y": 357.10168132837816,
},
},
"type": "3",
},
{
"data": undefined,
"id": "109408",
"meta": {
"position": {
"x": 1141.6692827086551,
"y": 537.1820787709282,
},
},
"type": "3",
},
{
"data": undefined,
"id": "156471",
"meta": {
"position": {
"x": 984.2265002815602,
"y": 712.1169030826604,
},
},
"type": "3",
},
],
}
`;

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { cloneDeep } from 'lodash-es';
import {
WorkflowDocument,
type WorkflowNodeEntity,
} from '@flowgram-adapter/free-layout-editor';
import { complexMock } from '../workflow.mock';
import { createContainer } from '../create-container';
import {
EncapsulateLinesService,
EncapsulateNodesService,
} from '../../src/encapsulate';
describe('encapsulate-lines-service', () => {
let workflowDocument: WorkflowDocument;
let encapsulateNodesService: EncapsulateNodesService;
let encapsulateLinesService: EncapsulateLinesService;
beforeEach(() => {
const container = createContainer();
workflowDocument = container.get<WorkflowDocument>(WorkflowDocument);
encapsulateLinesService = container.get<EncapsulateLinesService>(
EncapsulateLinesService,
);
encapsulateNodesService = container.get<EncapsulateNodesService>(
EncapsulateNodesService,
);
});
it('should create empty decapsulate lines', async () => {
await workflowDocument.fromJSON(complexMock);
const { inputLines, outputLines } =
encapsulateLinesService.createDecapsulateLines({
node: workflowDocument.getNode('102906') as WorkflowNodeEntity,
workflowJSON: {
edges: [],
nodes: [],
},
startNodeId: '',
endNodeId: '',
idsMap: new Map(),
});
expect(inputLines).toEqual([]);
expect(outputLines).toEqual([]);
});
// 解封节点有多个输入且解封流程start节点有多个输出不能创建输入连线
it('should not create decapsulate input lines', async () => {
const json = {
...cloneDeep(complexMock),
edges: [
...complexMock.edges,
{
sourceNodeID: '100001',
targetNodeID: '177547',
},
],
};
await workflowDocument.fromJSON(json);
const sourceNode = workflowDocument.getNode('154702') as WorkflowNodeEntity;
const { idsMap, startNode, endNode } =
await encapsulateNodesService.createDecapsulateNodes(
sourceNode,
json.nodes,
);
const { inputLines } = encapsulateLinesService.createDecapsulateLines({
node: sourceNode,
workflowJSON: {
nodes: [],
edges: json.edges,
},
startNodeId: startNode.id,
endNodeId: endNode.id,
idsMap,
});
expect(inputLines).toEqual([]);
});
// 解封节点有多个输出且解封流程end节点有多个输入不能创建输出连线
it('should not create decapsulate output lines', async () => {
const json = {
...cloneDeep(complexMock),
edges: [
...complexMock.edges,
{
sourceNodeID: '109408',
targetNodeID: '900001',
},
],
};
await workflowDocument.fromJSON(json);
const sourceNode = workflowDocument.getNode('177547') as WorkflowNodeEntity;
const { idsMap, startNode, endNode } =
await encapsulateNodesService.createDecapsulateNodes(
sourceNode,
json.nodes,
);
const { outputLines } = encapsulateLinesService.createDecapsulateLines({
node: sourceNode,
workflowJSON: {
nodes: [],
edges: json.edges,
},
startNodeId: startNode.id,
endNodeId: endNode.id,
idsMap,
});
expect(outputLines).toEqual([]);
});
it('should create decapsulate lines', async () => {
await workflowDocument.fromJSON(cloneDeep(complexMock));
const sourceNode = workflowDocument.getNode('102906') as WorkflowNodeEntity;
const { idsMap, startNode, endNode } =
await encapsulateNodesService.createDecapsulateNodes(
sourceNode,
complexMock.nodes,
);
const { inputLines, outputLines, internalLines } =
encapsulateLinesService.createDecapsulateLines({
node: sourceNode,
workflowJSON: {
nodes: [],
edges: complexMock.edges,
},
startNodeId: startNode.id,
endNodeId: endNode.id,
idsMap,
});
expect(internalLines.length).toEqual(3);
expect(inputLines.length).toEqual(1);
expect(outputLines.length).toEqual(1);
});
});

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, beforeEach, it, expect } from 'vitest';
import { FlowNodeTransformData } from '@flowgram-adapter/free-layout-editor';
import {
WorkflowDocument,
type WorkflowNodeEntity,
} from '@flowgram-adapter/free-layout-editor';
import { complexMock } from '../workflow.mock';
import { createContainer } from '../create-container';
import { EncapsulateNodesService } from '../../src/encapsulate';
describe('encapsulate-nodes-service', () => {
let workflowDocument: WorkflowDocument;
let encapsulateNodesService: EncapsulateNodesService;
beforeEach(() => {
const container = createContainer();
workflowDocument = container.get<WorkflowDocument>(WorkflowDocument);
encapsulateNodesService = container.get<EncapsulateNodesService>(
EncapsulateNodesService,
);
});
it('should getNodesMiddlePoint', async () => {
await workflowDocument.fromJSON(complexMock);
const nodes = ['109408', '156471'].map(id =>
workflowDocument.getNode(id),
) as WorkflowNodeEntity[];
const point = encapsulateNodesService.getNodesMiddlePoint(nodes);
expect(point).toEqual({
x: 993.4484760125105,
y: 489.11299357749374,
});
});
it('should getNodesMiddlePoint by json', () => {
const point = encapsulateNodesService.getNodesMiddlePoint(
complexMock.nodes,
);
expect(point).toEqual({
x: 808.8382078287623,
y: 207.51796739960494,
});
});
it('should createDecapsulateNode', async () => {
await workflowDocument.fromJSON(complexMock);
const json = {
id: '1',
type: 'test',
meta: {
position: {
x: 0,
y: 0,
},
},
blocks: [
{
id: '2',
type: 'test',
meta: {
position: {
x: 5,
y: 6,
},
},
},
],
};
const idsMap = new Map<string, string>();
const node = await encapsulateNodesService.createDecapsulateNode(
json,
{
x: 170,
y: 16.700000000000017,
},
idsMap,
);
expect(node.id).not.toBe('1');
expect(node.flowNodeType).toBe(json.type);
const transformData = node.getData(FlowNodeTransformData);
expect(transformData.position).toEqual({ x: 170, y: 16.700000000000017 });
const child = node.collapsedChildren[0];
expect(child.id).not.toBe('2');
expect(child.flowNodeType).toBe(json.blocks[0].type);
const childTransformData = child.getData(FlowNodeTransformData);
expect(childTransformData.position).toEqual({
x: 175,
y: 22.700000000000017,
});
expect(idsMap.get('1')).toBe(node.id);
expect(idsMap.get('2')).toBe(child.id);
});
it('should decapsulateLayout', async () => {
await workflowDocument.fromJSON(complexMock);
await encapsulateNodesService.decapsulateLayout(
workflowDocument.getNode('100001') as WorkflowNodeEntity,
[complexMock.nodes[1], complexMock.nodes[2], complexMock.nodes[3]],
);
const json = await workflowDocument.toJSON();
expect(json).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, beforeEach } from 'vitest';
import {
WorkflowDocument,
WorkflowSelectService,
type WorkflowNodeEntity,
} from '@flowgram-adapter/free-layout-editor';
import { complexMock } from '../workflow.mock';
import { createContainer } from '../create-container';
import {
EncapsulateService,
type EncapsulateSuccessResult,
} from '../../src/encapsulate';
describe('encapsulate-service', () => {
let workflowDocument: WorkflowDocument;
let encapsulateService: EncapsulateService;
let workflowSelectService: WorkflowSelectService;
beforeEach(async () => {
const container = createContainer();
workflowDocument = container.get<WorkflowDocument>(WorkflowDocument);
workflowSelectService = container.get<WorkflowSelectService>(
WorkflowSelectService,
);
encapsulateService = container.get<EncapsulateService>(EncapsulateService);
await workflowDocument.fromJSON(complexMock);
});
it('should can encapsulate', () => {
expect(encapsulateService.canEncapsulate()).toBeTruthy();
});
it('should can decapsulate', () => {
const node = workflowDocument.getNode('102906') as WorkflowNodeEntity;
expect(encapsulateService.canDecapsulate(node)).toBeTruthy();
});
it('should encapsulate nodes', async () => {
['102906', '154702'].forEach(id =>
workflowSelectService.toggleSelect(
workflowDocument.getNode(id) as WorkflowNodeEntity,
),
);
const res =
(await encapsulateService.encapsulate()) as EncapsulateSuccessResult;
if (!res.success) {
console.log(res);
}
expect(res.success).toBeTruthy();
expect(res.subFlowNode).toBeDefined();
expect(res.inputLines.length).toEqual(2);
res.inputLines.forEach(line => {
expect(line.to).toBe(res.subFlowNode);
});
expect(res.outputLines.length).toEqual(1);
res.outputLines.forEach(line => {
expect(line.from).toBe(res.subFlowNode);
});
expect(workflowSelectService.selectedNodes.length).toEqual(1);
expect(workflowSelectService.selectedNodes[0]).toBe(res.subFlowNode);
});
});