Skip to content

Commit b131e46

Browse files
authored
Merge pull request #67 from FlowTestAI/refer-flows
Ability to define preflow & postflow and run composite graph end to end
2 parents 82ede92 + 893f8db commit b131e46

File tree

10 files changed

+257
-71
lines changed

10 files changed

+257
-71
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
246246
}
247247
});
248248

249+
ipcMain.handle('renderer:read-flowtest-sync', (event, pathname) => {
250+
const content = readFile(pathname);
251+
const flowData = content === '{}' ? undefined : readableDataToFlowData(JSON.parse(content));
252+
return flowData;
253+
});
254+
249255
ipcMain.handle('renderer:update-flowtest', async (event, pathname, flowData) => {
250256
try {
251257
const readableData = flowDataToReadableData(flowData);

src/components/molecules/flow/flowtestai.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ const translateGeneratedNodesToOpenApiNodes = (generatedNodes, openApiNodes) =>
1414
outputNode['requestBody']['body'] = JSON.stringify(node_arguments.requestBody);
1515
}
1616
if (node_arguments.parameters) {
17-
outputNode.variables = {};
17+
outputNode.preReqVars = {};
1818
Object.entries(node_arguments.parameters).forEach(([paramName, paramValue], _) => {
19-
outputNode.variables[paramName] = {
19+
outputNode.preReqVars[paramName] = {
2020
type: typeof paramValue,
2121
value: paramValue,
2222
};

src/components/molecules/flow/graph/Graph.js

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
11
// assumption is that apis are giving json as output
22

3-
import { cloneDeep } from 'lodash';
43
import useCanvasStore from 'stores/CanvasStore';
5-
import useCollectionStore from 'stores/CollectionStore';
6-
import { useTabStore } from 'stores/TabStore';
74
import { computeAuthNode } from './compute/authnode';
85
import { computeEvaluateNode } from './compute/evaluatenode';
96
import { computeRequestNode } from './compute/requestnode';
107

118
class Graph {
12-
constructor(nodes, edges, collectionId, onGraphComplete) {
13-
const activeEnv = useCollectionStore
14-
.getState()
15-
.collections.find((c) => c.id === collectionId)
16-
?.environments.find((e) => e.name === useTabStore.getState().selectedEnv);
9+
constructor(nodes, edges, startTime, initialEnvVars, initialLogs) {
1710
this.nodes = nodes;
1811
this.edges = edges;
19-
this.onGraphComplete = onGraphComplete;
20-
this.logs = [];
21-
this.timeout = 60000; // 1m timeout
22-
this.startTime = Date.now();
12+
this.logs = initialLogs;
13+
this.timeout = 60000; //ms
14+
this.startTime = startTime;
2315
this.graphRunNodeOutput = {};
2416
this.auth = undefined;
25-
this.envVariables = activeEnv ? cloneDeep(activeEnv.variables) : undefined;
17+
this.envVariables = initialEnvVars;
2618
}
2719

2820
#checkTimeout() {
@@ -33,13 +25,13 @@ class Graph {
3325
let connectingEdge = undefined;
3426

3527
if (node.type === 'evaluateNode') {
36-
if (result[3] === true) {
28+
if (result.output === true) {
3729
connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'true' && edge.source === node.id);
3830
} else {
3931
connectingEdge = this.edges.find((edge) => edge.sourceHandle == 'false' && edge.source === node.id);
4032
}
4133
} else {
42-
if (result[0] === 'Success') {
34+
if (result.status === 'Success') {
4335
connectingEdge = this.edges.find((edge) => edge.source === node.id);
4436
}
4537
}
@@ -63,7 +55,7 @@ class Graph {
6355
return prevNodesData;
6456
}
6557

66-
async #computeNode(node, prevNodeOutput) {
58+
async #computeNode(node) {
6759
let result = undefined;
6860
const prevNodeOutputData = this.#computeDataFromPreviousNodes(node);
6961

@@ -73,16 +65,29 @@ class Graph {
7365
if (node.type === 'outputNode') {
7466
this.logs.push(`Output: ${JSON.stringify(prevNodeOutputData)}`);
7567
useCanvasStore.getState().setOutputNode(node.id, prevNodeOutputData);
76-
result = ['Success', node, prevNodeOutput];
68+
result = {
69+
status: 'Success',
70+
node,
71+
};
7772
}
7873

7974
if (node.type === 'evaluateNode') {
8075
if (computeEvaluateNode(node, prevNodeOutputData, this.logs)) {
8176
this.logs.push('Result: true');
82-
result = ['Success', node, prevNodeOutput, true];
77+
result = {
78+
status: 'Success',
79+
node,
80+
output: true,
81+
};
82+
//result = ['Success', node, prevNodeOutput, true];
8383
} else {
8484
this.logs.push('Result: false');
85-
result = ['Success', node, prevNodeOutput, false];
85+
result = {
86+
status: 'Success',
87+
node,
88+
output: true,
89+
};
90+
//result = ['Success', node, prevNodeOutput, false];
8691
}
8792
}
8893

@@ -93,21 +98,27 @@ class Graph {
9398
};
9499
await wait(delay);
95100
this.logs.push(`Wait for: ${delay} ms`);
96-
result = ['Success', node, prevNodeOutput];
101+
result = {
102+
status: 'Success',
103+
node,
104+
};
97105
}
98106

99107
if (node.type === 'authNode') {
100108
this.auth = node.data.type ? computeAuthNode(node.data, this.envVariables) : undefined;
101-
result = ['Success', node, prevNodeOutput];
109+
result = {
110+
status: 'Success',
111+
node,
112+
};
102113
}
103114

104115
if (node.type === 'requestNode') {
105116
result = await computeRequestNode(node, prevNodeOutputData, this.envVariables, this.auth, this.logs);
106117
// add post response variables if any
107-
if (result[3]) {
118+
if (result.postRespVars) {
108119
this.envVariables = {
109120
...this.envVariables,
110-
...result[3],
121+
...result.postRespVars,
111122
};
112123
}
113124
}
@@ -117,12 +128,18 @@ class Graph {
117128
}
118129
} catch (err) {
119130
this.logs.push(`Flow failed at: ${JSON.stringify(node)} due to ${err}`);
120-
return ['Failed', node];
131+
return {
132+
status: 'Failed',
133+
node,
134+
};
121135
}
122136

123137
if (result === undefined) {
124138
this.logs.push(`Flow failed at: ${JSON.stringify(node)}`);
125-
return ['Failed', node];
139+
return {
140+
status: 'Failed',
141+
node,
142+
};
126143
} else {
127144
const connectingEdge = this.#computeConnectingEdge(node, result);
128145

@@ -132,15 +149,15 @@ class Graph {
132149
['requestNode', 'outputNode', 'evaluateNode', 'delayNode', 'authNode'].includes(node.type) &&
133150
node.id === connectingEdge.target,
134151
);
135-
this.graphRunNodeOutput[node.id] = result[2] && result[2].data ? result[2].data : {};
136-
return this.#computeNode(nextNode, result[2]);
152+
this.graphRunNodeOutput[node.id] = result.data ? result.data : {};
153+
return this.#computeNode(nextNode);
137154
} else {
138155
return result;
139156
}
140157
}
141158
}
142159

143-
run() {
160+
async run() {
144161
// reset every output node for a fresh run
145162
this.nodes.forEach((node) => {
146163
if (node.type === 'outputNode') {
@@ -154,26 +171,34 @@ class Graph {
154171
if (startNode == undefined) {
155172
this.logs.push('No start node found');
156173
this.logs.push('End Flowtest');
157-
return;
174+
return {
175+
status: 'Success',
176+
logs: this.logs,
177+
envVars: this.envVariables,
178+
};
158179
}
159180
const connectingEdge = this.edges.find((edge) => edge.source === startNode.id);
160181

161182
// only start computing graph if initial node has the connecting edge
162183
if (connectingEdge != undefined) {
163184
const firstNode = this.nodes.find((node) => node.id === connectingEdge.target);
164-
this.#computeNode(firstNode, undefined).then((result) => {
165-
if (result[0] == 'Failed') {
166-
console.debug('Flow failed at: ', result[1]);
167-
}
168-
this.logs.push('End Flowtest');
169-
this.logs.push(`Total time: ${Date.now() - this.startTime} ms`);
170-
console.log(this.logs);
171-
this.onGraphComplete(result, this.logs);
172-
});
185+
const result = await this.#computeNode(firstNode);
186+
if (result.status == 'Failed') {
187+
console.debug('Flow failed at: ', result.node);
188+
}
189+
this.logs.push('End Flowtest');
190+
return {
191+
status: result.status,
192+
logs: this.logs,
193+
envVars: this.envVariables,
194+
};
173195
} else {
174-
this.logs.push('No connected request node to start node');
175196
this.logs.push('End Flowtest');
176-
this.onGraphComplete(['Success'], this.logs);
197+
return {
198+
status: 'Success',
199+
logs: this.logs,
200+
envVars: this.envVariables,
201+
};
177202
}
178203
}
179204
}

src/components/molecules/flow/graph/compute/requestnode.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const formulateRequest = (node, finalUrl, variablesDict, auth, logs) => {
4949
};
5050

5151
export const computeRequestNode = async (node, prevNodeOutputData, envVariables, auth, logs) => {
52-
// step1 evaluate variables of this node
52+
// step1 evaluate pre request variables of this node
5353
const evalVariables = computeNodeVariables(node.data.preReqVars, prevNodeOutputData);
5454

5555
const variablesDict = {
@@ -72,14 +72,26 @@ export const computeRequestNode = async (node, prevNodeOutputData, envVariables,
7272
console.debug('Failure at node: ', node);
7373
console.debug('Error encountered: ', JSON.stringify(res.error));
7474
logs.push(`Request failed: ${JSON.stringify(res.error)}`);
75-
return ['Failed', node, res.error];
75+
return {
76+
status: 'Failed',
77+
node,
78+
};
7679
} else {
7780
logs.push(`Request successful: ${JSON.stringify(res)}`);
7881
console.debug('Response: ', JSON.stringify(res));
7982
if (node.data.postRespVars) {
8083
const evalPostRespVars = computeNodeVariables(node.data.postRespVars, res.data);
81-
return ['Success', node, res, evalPostRespVars];
84+
return {
85+
status: 'Success',
86+
node,
87+
data: res.data,
88+
postRespVars: evalPostRespVars,
89+
};
8290
}
83-
return ['Success', node, res];
91+
return {
92+
status: 'Success',
93+
node,
94+
data: res.data,
95+
};
8496
}
8597
};

src/components/molecules/flow/index.js

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import DelayNode from './nodes/DelayNode';
2020
import AuthNode from './nodes/AuthNode';
2121
import FlowNode from 'components/atoms/flow/FlowNode';
2222
import useCanvasStore from 'stores/CanvasStore';
23+
import { readFlowTestSync } from 'service/collection';
24+
import useCollectionStore from 'stores/CollectionStore';
25+
import { useTabStore } from 'stores/TabStore';
2326

2427
const StartNode = () => (
2528
<FlowNode title='Start' handleLeft={false} handleRight={true} handleRightData={{ type: 'source' }}></FlowNode>
@@ -207,12 +210,11 @@ const Flow = ({ collectionId }) => {
207210
return true;
208211
};
209212

210-
const onGraphComplete = (result, logs) => {
211-
console.debug('Graph complete callback: ', result);
213+
const onGraphComplete = (status, logs) => {
212214
setLogs(logs);
213-
if (result[0] == 'Success') {
215+
if (status == 'Success') {
214216
toast.success('FlowTest Run Success! View Logs');
215-
} else if (result[0] == 'Failed') {
217+
} else if (status == 'Failed') {
216218
toast.error('FlowTest Run Failed! View Logs');
217219
}
218220
runnableEdges(false);
@@ -237,15 +239,84 @@ const Flow = ({ collectionId }) => {
237239
>
238240
<Controls>
239241
<ControlButton
240-
onClick={() => {
242+
onClick={async () => {
241243
runnableEdges(true);
242-
const g = new Graph(
243-
cloneDeep(reactFlowInstance.getNodes()),
244-
cloneDeep(reactFlowInstance.getEdges()),
245-
collectionId,
246-
onGraphComplete,
247-
);
248-
g.run();
244+
const startTime = Date.now();
245+
try {
246+
let result = undefined;
247+
let g = undefined;
248+
let envVariables = {};
249+
250+
const preFlow = useCanvasStore.getState().preFlow;
251+
const postFlow = useCanvasStore.getState().postFlow;
252+
253+
const activeEnv = useCollectionStore
254+
.getState()
255+
.collections.find((c) => c.id === collectionId)
256+
?.environments.find((e) => e.name === useTabStore.getState().selectedEnv);
257+
if (activeEnv) {
258+
envVariables = cloneDeep(activeEnv.variables);
259+
}
260+
261+
// ============= pre flow ================
262+
const preFlowData = await readFlowTestSync(preFlow);
263+
if (preFlowData) {
264+
g = new Graph(
265+
cloneDeep(preFlowData.nodes),
266+
cloneDeep(preFlowData.edges),
267+
startTime,
268+
envVariables,
269+
[],
270+
);
271+
result = await g.run();
272+
}
273+
274+
//console.log('pre flow: ', result);
275+
276+
if (result?.status === 'Failed') {
277+
onGraphComplete(result.status, result.logs);
278+
return;
279+
}
280+
281+
// ============= flow =====================
282+
g = new Graph(
283+
cloneDeep(reactFlowInstance.getNodes()),
284+
cloneDeep(reactFlowInstance.getEdges()),
285+
startTime,
286+
result ? result.envVars : envVariables,
287+
result ? result.logs : [],
288+
);
289+
result = await g.run();
290+
291+
//console.log('flow: ', result);
292+
//console.log('after flow vars: ', result.envVars);
293+
294+
if (result.status === 'Failed') {
295+
onGraphComplete(result.status, result.logs);
296+
return;
297+
}
298+
299+
// ============= post flow ================
300+
const postFlowData = await readFlowTestSync(postFlow);
301+
if (postFlowData) {
302+
g = new Graph(
303+
cloneDeep(postFlowData.nodes),
304+
cloneDeep(postFlowData.edges),
305+
startTime,
306+
result.envVars,
307+
result.logs,
308+
);
309+
result = await g.run();
310+
}
311+
312+
//console.log('post flow: ', result);
313+
314+
result.logs.push(`Total time: ${Date.now() - startTime} ms`);
315+
onGraphComplete(result.status, result.logs);
316+
} catch (error) {
317+
toast.error(`Error running graph: ${error}`);
318+
runnableEdges(false);
319+
}
249320
}}
250321
title='run'
251322
>

0 commit comments

Comments
 (0)