Skip to content

Commit a4c6906

Browse files
authored
Merge pull request #48 from FlowTestAI/integrate-actions-e2e
Integrate actions e2e
2 parents 982c6e0 + 93ef115 commit a4c6906

File tree

20 files changed

+609
-475
lines changed

20 files changed

+609
-475
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"axios": "^1.5.1",
1818
"eslint-import-resolver-alias": "^1.1.2",
1919
"eslint-plugin-import": "^2.29.1",
20+
"immer": "^10.0.4",
21+
"lodash": "^4.17.21",
2022
"notistack": "^3.0.1",
2123
"postcss": "^8.4.35",
2224
"react": "^18.2.0",
@@ -32,7 +34,7 @@
3234
"socket.io-client": "^4.7.4",
3335
"tailwindcss": "^3.4.1",
3436
"web-vitals": "^2.1.4",
35-
"zustand": "^4.5.0"
37+
"zustand": "^4.5.2"
3638
},
3739
"scripts": {
3840
"start": "npm run build && cd packages/flowtest-electron && npm start",

packages/flowtest-electron/src/ipc/collection.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,8 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
104104

105105
ipcMain.handle('renderer:delete-collection', async (event, collection) => {
106106
try {
107-
const fullPath = path.join(collection.pathname, collection.name);
108-
deleteDirectory(fullPath);
109-
console.log(`Deleted directory: ${fullPath}`);
107+
deleteDirectory(collection.pathname);
108+
console.log(`Deleted directory: ${collection.pathname}`);
110109

111110
mainWindow.webContents.send('main:collection-deleted', collection.id);
112111

@@ -117,7 +116,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
117116
}
118117
});
119118

120-
ipcMain.handle('renderer:new-folder', async (event, name, path) => {
119+
ipcMain.handle('renderer:create-folder', async (event, name, path) => {
121120
try {
122121
const result = createDirectory(name, path);
123122
console.log(`Created directory: ${result}`);
@@ -181,7 +180,8 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
181180

182181
ipcMain.handle('renderer:read-flowtest', async (event, pathname, collectionId) => {
183182
try {
184-
const flowData = readableDataToFlowData(readFile(pathname));
183+
const content = readFile(pathname);
184+
const flowData = content === '{}' ? undefined : readableDataToFlowData(JSON.parse(content));
185185
mainWindow.webContents.send('main:read-flowtest', pathname, collectionId, flowData);
186186
} catch (error) {
187187
return Promise.reject(error);

src/components/atoms/SelectEnvironment.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ import { Listbox, Transition } from '@headlessui/react';
33
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';
44
import { Square3Stack3DIcon } from '@heroicons/react/24/outline';
55

6-
const environments = [
7-
{
8-
id: 'feebd601-63c5-4a4f-8def-1dde4f317567',
9-
createdAt: 1710096201431,
10-
modifiedAt: 1710096201431,
11-
name: 'test.env',
12-
pathname:
13-
'/Users/sirachit/Desktop/Personal Devo/FlowTest/test/folder-test/Simple API overview/environments/test.env',
14-
variables: { k1: 'v1', k2: 'v2', k3: 'v3' },
15-
},
16-
{
17-
id: 'feebd601-63c5-4a4f-8def-1dde4f317567',
18-
createdAt: 1710096202431,
19-
modifiedAt: 1710096202431,
20-
name: 'test2.env',
21-
pathname:
22-
'/Users/sirachit/Desktop/Personal Devo/FlowTest/test/folder-test/Simple API overview/environments/test2.env',
23-
variables: { k1: 'v1', k2: 'v2', k3: 'v3' },
24-
},
25-
];
6+
// const environments = [
7+
// {
8+
// id: 'feebd601-63c5-4a4f-8def-1dde4f317567',
9+
// createdAt: 1710096201431,
10+
// modifiedAt: 1710096201431,
11+
// name: 'test.env',
12+
// pathname:
13+
// '/Users/sirachit/Desktop/Personal Devo/FlowTest/test/folder-test/Simple API overview/environments/test.env',
14+
// variables: { k1: 'v1', k2: 'v2', k3: 'v3' },
15+
// },
16+
// {
17+
// id: 'feebd601-63c5-4a4f-8def-1dde4f317567',
18+
// createdAt: 1710096202431,
19+
// modifiedAt: 1710096202431,
20+
// name: 'test2.env',
21+
// pathname:
22+
// '/Users/sirachit/Desktop/Personal Devo/FlowTest/test/folder-test/Simple API overview/environments/test2.env',
23+
// variables: { k1: 'v1', k2: 'v2', k3: 'v3' },
24+
// },
25+
// ];
2626

27-
const SelectEnvironment = () => {
27+
const SelectEnvironment = ({ environments }) => {
2828
const [selected, setSelected] = useState(null);
2929
return (
3030
<Listbox value={selected} onChange={setSelected}>
Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { XMarkIcon } from '@heroicons/react/24/outline';
33
import { useTabStore } from 'stores/TabStore';
44

5-
const FlowTab = () => {
5+
const Tabs = () => {
66
const tabs = useTabStore((state) => state.tabs);
77
const setFocusTab = useTabStore((state) => state.setFocusTab);
88
const focusTabId = useTabStore((state) => state.focusTabId);
@@ -15,18 +15,21 @@ const FlowTab = () => {
1515
<div role='tablist' className='tabs tabs-lg'>
1616
{tabs.map((tab, index) => {
1717
return (
18-
<a
19-
role='tab'
20-
className={`${tabCommonStyles} ${focusTabId === tab.id ? activeTabStyles : ''}`}
21-
key={index}
22-
data-id={tab.id}
23-
data-collection-id={tab.collectionId}
24-
onClick={() => {
25-
setFocusTab(tab.id);
26-
console.log(`CLICKED THE ${tab.id}`);
27-
}}
28-
>
29-
{tab.name}
18+
<>
19+
<a
20+
role='tab'
21+
className={`${tabCommonStyles} ${focusTabId === tab.id ? activeTabStyles : ''}`}
22+
key={index}
23+
data-id={tab.id}
24+
data-collection-id={tab.collectionId}
25+
onClick={() => {
26+
setFocusTab(tab.id);
27+
console.log(`CLICKED THE ${tab.id}`);
28+
}}
29+
>
30+
{tab.name}
31+
</a>
32+
{/* close needs to be a separate clickable component other wise it gets confused with above */}
3033
<div
3134
className='flex h-full items-center px-2 hover:rounded hover:rounded-l-none hover:bg-slate-200'
3235
onClick={() => {
@@ -35,11 +38,11 @@ const FlowTab = () => {
3538
>
3639
<XMarkIcon className='h-4 w-4' />
3740
</div>
38-
</a>
41+
</>
3942
);
4043
})}
4144
</div>
4245
);
4346
};
4447

45-
export default FlowTab;
48+
export default Tabs;

src/components/molecules/flow/AddNodes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const AddNodes = ({ collectionId }) => {
128128
</Disclosure.Button>
129129
<Disclosure.Panel className='px-4 pt-4 pb-2 text-sm border-l border-r'>
130130
<div>
131-
{JSON.parse(collection.nodes).map((node, index1) => (
131+
{collection.nodes.map((node, index1) => (
132132
<div
133133
key={`${node.requestType} - ${node.operationId}`}
134134
onDragStart={(event) => {

src/components/molecules/flow/index.js

Lines changed: 100 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useCallback, useMemo, useState, useEffect } from 'react';
22
import ReactFlow, { useNodesState, useEdgesState, addEdge, Controls, Background, ControlButton } from 'reactflow';
33
import 'reactflow/dist/style.css';
4+
import { cloneDeep, isEqual } from 'lodash';
45

56
// css
67
import './index.css';
@@ -27,12 +28,97 @@ const StartNode = () => (
2728
<FlowNode title='Start' handleLeft={false} handleRight={true} handleRightData={{ type: 'source' }}></FlowNode>
2829
);
2930

31+
const init = (flowData) => {
32+
// Initialization
33+
if (flowData && flowData.nodes && flowData.edges) {
34+
return {
35+
nodes: flowData.nodes,
36+
edges: flowData.edges,
37+
};
38+
} else if (flowData && flowData.nodes && !flowData.edges) {
39+
// AI prompt generated
40+
const nodes = [
41+
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
42+
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
43+
];
44+
const edges = [
45+
{
46+
id: `reactflow__edge-0-1`,
47+
source: `0`,
48+
sourceHandle: null,
49+
target: `1`,
50+
targetHandle: null,
51+
type: 'buttonedge',
52+
},
53+
];
54+
for (let i = 2; i <= flowData.nodes.length; i++) {
55+
nodes.push({
56+
id: `${i}`,
57+
type: flowData.nodes[i - 1].type,
58+
position: { x: 150 + i * 500, y: 50 },
59+
data: flowData.nodes[i - 1],
60+
});
61+
edges.push({
62+
id: `reactflow__edge-${i - 1}-${i}`,
63+
source: `${i - 1}`,
64+
sourceHandle: null,
65+
target: `${i}`,
66+
targetHandle: null,
67+
type: 'buttonedge',
68+
});
69+
}
70+
return {
71+
nodes: flowData.nodes,
72+
edges: flowData.edges,
73+
};
74+
} else {
75+
return {
76+
nodes: [
77+
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
78+
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
79+
],
80+
edges: [
81+
{
82+
id: `reactflow__edge-0-1`,
83+
source: `0`,
84+
sourceHandle: null,
85+
target: `1`,
86+
targetHandle: null,
87+
type: 'buttonedge',
88+
},
89+
],
90+
};
91+
}
92+
};
93+
3094
const Flow = ({ tabId, collectionId, flowData }) => {
95+
useEffect(() => {
96+
// Action to perform on tab change
97+
console.log(`Tab changed to: ${tabId}`);
98+
console.log(flowData);
99+
// perform actions based on the new tabId
100+
const result = init(cloneDeep(flowData));
101+
setNodes(result.nodes);
102+
setEdges(result.edges);
103+
}, [tabId]);
104+
31105
const setCanvasDirty = () => {
106+
console.debug('set canvas dirty');
32107
const tab = useTabStore.getState().tabs.find((t) => t.id === tabId);
33108
if (tab) {
34109
tab.isDirty = true;
35-
tab.flowData = getFlowData();
110+
tab.flowData = {
111+
nodes: nodes.map((node) => {
112+
const _node = JSON.parse(JSON.stringify(node));
113+
return { ..._node };
114+
}),
115+
edges: edges.map((edge) => {
116+
return {
117+
...edge,
118+
animated: false,
119+
};
120+
}),
121+
};
36122
}
37123
};
38124

@@ -41,21 +127,6 @@ const Flow = ({ tabId, collectionId, flowData }) => {
41127

42128
const [reactFlowInstance, setReactFlowInstance] = useState(null);
43129

44-
const getFlowData = () => {
45-
if (reactFlowInstance) {
46-
// save might get triggered when the flow is running, don't store animated edges
47-
const updatedEdges = reactFlowInstance.getEdges().map((edge) => {
48-
return {
49-
...edge,
50-
animated: false,
51-
};
52-
});
53-
const rfInstanceObject = reactFlowInstance.toObject();
54-
rfInstanceObject.edges = updatedEdges;
55-
return rfInstanceObject;
56-
}
57-
};
58-
59130
const nodeTypes = useMemo(
60131
() => ({
61132
startNode: StartNode,
@@ -75,9 +146,21 @@ const Flow = ({ tabId, collectionId, flowData }) => {
75146
[],
76147
);
77148

78-
const [nodes, setNodes, onNodesChange] = useNodesState();
149+
const [nodes, setNodes, onNodesChange] = useNodesState([]);
79150
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
80151

152+
useEffect(() => {
153+
// skip inital render
154+
if (isEqual(nodes, []) && isEqual(edges, [])) {
155+
return;
156+
}
157+
if (flowData && isEqual(JSON.parse(JSON.stringify(nodes)), flowData.nodes) && isEqual(edges, flowData.edges)) {
158+
console.debug('canvas is unchanged');
159+
return;
160+
}
161+
setCanvasDirty();
162+
}, [nodes, edges]);
163+
81164
const onConnect = (params) => {
82165
const newEdge = {
83166
...params,
@@ -133,72 +216,10 @@ const Flow = ({ tabId, collectionId, flowData }) => {
133216
console.debug('Dropped node: ', newNode);
134217

135218
setNodes((nds) => nds.concat(newNode));
136-
setCanvasDirty();
137219
},
138220
[reactFlowInstance],
139221
);
140222

141-
// Initialization
142-
useEffect(() => {
143-
if (flowData && flowData.nodes && flowData.edges) {
144-
setNodes(flowData.nodes);
145-
setEdges(flowData.edges);
146-
} else if (flowData && flowData.nodes) {
147-
// AI prompt generated
148-
const nodes = [
149-
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
150-
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
151-
];
152-
const edges = [
153-
{
154-
id: `reactflow__edge-0-1`,
155-
source: `0`,
156-
sourceHandle: null,
157-
target: `1`,
158-
targetHandle: null,
159-
type: 'buttonedge',
160-
},
161-
];
162-
163-
for (let i = 2; i <= flowData.nodes.length; i++) {
164-
nodes.push({
165-
id: `${i}`,
166-
type: flowData.nodes[i - 1].type,
167-
position: { x: 150 + i * 500, y: 50 },
168-
data: flowData.nodes[i - 1],
169-
});
170-
171-
edges.push({
172-
id: `reactflow__edge-${i - 1}-${i}`,
173-
source: `${i - 1}`,
174-
sourceHandle: null,
175-
target: `${i}`,
176-
targetHandle: null,
177-
type: 'buttonedge',
178-
});
179-
}
180-
setNodes(nodes);
181-
setEdges(edges);
182-
setCanvasDirty();
183-
} else {
184-
setNodes([
185-
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
186-
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
187-
]);
188-
setEdges([
189-
{
190-
id: `reactflow__edge-0-1`,
191-
source: `0`,
192-
sourceHandle: null,
193-
target: `1`,
194-
targetHandle: null,
195-
type: 'buttonedge',
196-
},
197-
]);
198-
setCanvasDirty();
199-
}
200-
}, []);
201-
202223
const isValidConnection = (connection) => {
203224
let canConnect = true;
204225
// Only 1 outgoing edge from each (source, sourceHandle) is allowed

src/components/molecules/flow/nodes/EvaluateNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const operatorMenu = (data) => {
2323
<select
2424
onChange={handleOperatorSelection}
2525
name='auth-type'
26-
default={selectedOperatorValue}
26+
value={selectedOperatorValue}
2727
className='w-full h-12 px-1 py-2 border rounded-md border-neutral-500 text-neutral-500 outline-0 focus:ring-0'
2828
>
2929
<option value={CHOOSE_OPERATOR_DEFAULT_VALUE_OBJ.value}>

0 commit comments

Comments
 (0)