Skip to content

Commit 5f7e245

Browse files
authored
Merge pull request #46 from FlowTestAI/send-http-request
Ability to send http requests for graph run
2 parents 3b91b7c + 4aa0e7e commit 5f7e245

File tree

9 files changed

+244
-189
lines changed

9 files changed

+244
-189
lines changed

packages/flowtest-electron/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
},
1717
"dependencies": {
1818
"@apidevtools/swagger-parser": "^10.1.0",
19+
"axios": "^1.6.7",
1920
"chokidar": "^3.6.0",
2021
"dotenv": "^16.4.5",
2122
"electron-store": "^8.1.0",

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs');
22
const path = require('path');
3+
const axios = require('axios');
34
const { ipcMain, shell, dialog, app } = require('electron');
45
const SwaggerParser = require('@apidevtools/swagger-parser');
56
const JsonRefs = require('json-refs');
@@ -17,6 +18,22 @@ const readFile = require('../utils/filemanager/readfile');
1718

1819
const collectionStore = new Collections();
1920

21+
const timeout = 60000;
22+
23+
const newAbortSignal = () => {
24+
const abortController = new AbortController();
25+
setTimeout(() => abortController.abort(), timeout || 0);
26+
27+
return abortController.signal;
28+
};
29+
30+
/** web platform: blob. */
31+
const convertBase64ToBlob = async (base64) => {
32+
const response = await fetch(base64);
33+
const blob = await response.blob();
34+
return blob;
35+
};
36+
2037
const registerRendererEventHandlers = (mainWindow, watcher) => {
2138
ipcMain.handle('renderer:open-directory-selection-dialog', async (event, arg) => {
2239
try {
@@ -189,6 +206,47 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
189206
return Promise.reject(error);
190207
}
191208
});
209+
210+
ipcMain.handle('renderer:run-http-request', async (event, request) => {
211+
try {
212+
if (request.headers['Content-type'] === 'multipart/form-data') {
213+
const requestData = new FormData();
214+
const file = await convertBase64ToBlob(request.data.value);
215+
requestData.append(request.data.key, file, request.data.name);
216+
217+
request.data = requestData;
218+
}
219+
220+
// assuming 'application/json' type
221+
const options = {
222+
...request,
223+
signal: newAbortSignal(),
224+
};
225+
226+
const result = await axios(options);
227+
return {
228+
status: result.status,
229+
statusText: result.statusText,
230+
data: result.data,
231+
};
232+
} catch (error) {
233+
if (error?.response) {
234+
return {
235+
error: {
236+
status: error.response.status,
237+
statusText: error.response.statusText,
238+
data: error.response.data,
239+
},
240+
};
241+
} else {
242+
return {
243+
error: {
244+
message: 'An unknown error occurred while running the request',
245+
},
246+
};
247+
}
248+
}
249+
});
192250
};
193251

194252
module.exports = registerRendererEventHandlers;

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

Lines changed: 14 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,195 +1,24 @@
1-
import Operators from '../constants/operators.js';
2-
31
// assumption is that apis are giving json as output
42

3+
import { computeEvaluateNode } from './compute/evaluatenode';
4+
import { computeRequestNode } from './compute/requestnode';
5+
56
class Graph {
6-
constructor(nodes, edges, onGraphComplete, authKey, runRequest) {
7+
constructor(nodes, edges, onGraphComplete) {
78
this.nodes = nodes;
89
this.edges = edges;
910
this.onGraphComplete = onGraphComplete;
10-
this.authKey = authKey;
11-
this.runRequest = runRequest;
1211
this.logs = [];
1312
this.timeout = 60000; // 1m timeout
1413
this.startTime = Date.now();
1514
this.graphRunNodeOutput = {};
15+
this.auth = undefined;
1616
}
1717

1818
#checkTimeout() {
1919
return Date.now() - this.startTime > this.timeout;
2020
}
2121

22-
#formulateRequest(node, finalUrl) {
23-
let restMethod = node.data.requestType.toLowerCase();
24-
let contentType = 'application/json';
25-
let requestData = undefined;
26-
27-
if (restMethod === 'get') {
28-
if (node.data.requestBody) {
29-
if (node.data.requestBody.type === 'raw-json') {
30-
contentType = 'application/json';
31-
requestData = node.data.requestBody.body ? JSON.parse(node.data.requestBody.body) : JSON.parse('{}');
32-
}
33-
}
34-
} else if (restMethod === 'post' || restMethod === 'put') {
35-
if (node.data.requestBody) {
36-
if (node.data.requestBody.type === 'form-data') {
37-
contentType = 'multipart/form-data';
38-
requestData = {
39-
key: node.data.requestBody.body.key,
40-
value: node.data.requestBody.body.value,
41-
name: node.data.requestBody.body.name,
42-
};
43-
} else if (node.data.requestBody.type === 'raw-json') {
44-
contentType = 'application/json';
45-
requestData = node.data.requestBody.body ? JSON.parse(node.data.requestBody.body) : JSON.parse('{}');
46-
}
47-
}
48-
}
49-
50-
const options = {
51-
method: restMethod,
52-
url: finalUrl,
53-
headers: {
54-
'Content-type': contentType,
55-
},
56-
data: requestData,
57-
auth: {
58-
username: this.authKey ? this.authKey.accessId : '',
59-
password: this.authKey ? this.authKey.accessKey : '',
60-
},
61-
};
62-
63-
this.logs.push(`${restMethod} ${finalUrl}`);
64-
return options;
65-
}
66-
67-
#computeNodeVariable(variable, prevNodeOutputData) {
68-
if (variable.type.toLowerCase() === 'string') {
69-
return variable.value;
70-
}
71-
72-
if (variable.type.toLowerCase() === 'number') {
73-
return variable.value;
74-
}
75-
76-
if (variable.type.toLowerCase() === 'bool') {
77-
return Boolean(variable.value);
78-
}
79-
80-
if (variable.type.toLowerCase() === 'select') {
81-
if (prevNodeOutputData == undefined || Object.keys(prevNodeOutputData).length === 0) {
82-
this.logs.push(
83-
`Cannot evaluate variable ${variable} as previous node output data ${JSON.stringify(prevNodeOutputData)} is empty`,
84-
);
85-
throw 'Error evaluating node variables';
86-
}
87-
const jsonTree = variable.value.split('.');
88-
const getVal = (parent, pos) => {
89-
if (pos == jsonTree.length) {
90-
return parent;
91-
}
92-
const key = jsonTree[pos];
93-
if (key == '') {
94-
return parent;
95-
}
96-
97-
return getVal(parent[key], pos + 1);
98-
};
99-
const result = getVal(prevNodeOutputData, 0);
100-
if (result == undefined) {
101-
this.logs.push(
102-
`Cannot evaluate variable ${JSON.stringify(variable)} as previous node output data ${JSON.stringify(prevNodeOutputData)} did not contain the variable`,
103-
);
104-
throw 'Error evaluating node variables';
105-
}
106-
return result;
107-
}
108-
}
109-
110-
#computeNodeVariables(variables, prevNodeOutputData) {
111-
let evalVariables = {};
112-
Object.entries(variables).map(([vname, variable], index) => {
113-
evalVariables[vname] = this.#computeNodeVariable(variable, prevNodeOutputData);
114-
});
115-
return evalVariables;
116-
}
117-
118-
async #computeRequestNode(node, prevNodeOutputData) {
119-
try {
120-
// step1 evaluate variables of this node
121-
const evalVariables = this.#computeNodeVariables(node.data.variables, prevNodeOutputData);
122-
123-
// step2 replace variables in url with value
124-
let finalUrl = node.data.url;
125-
Object.entries(evalVariables).map(([vname, vvalue], index) => {
126-
finalUrl = finalUrl.replace(`{${vname}}`, vvalue);
127-
});
128-
129-
// step 3
130-
const options = this.#formulateRequest(node, finalUrl);
131-
132-
console.debug('Evaluated variables: ', evalVariables);
133-
console.debug('Evaluated Url: ', finalUrl);
134-
//const res = await axios(options);
135-
const res = await this.runRequest(JSON.stringify(options));
136-
this.logs.push(`Request successful: ${JSON.stringify(res.data)}`);
137-
console.debug('Response: ', res);
138-
return ['Success', node, res];
139-
} catch (error) {
140-
console.debug('Failure at node: ', node);
141-
console.debug('Error encountered: ', error);
142-
if (error.response) {
143-
// The request was made and the server responded with a status code
144-
// that falls out of the range of 2xx
145-
console.debug(error.response.data);
146-
console.debug(error.response.status);
147-
console.debug(error.response.headers);
148-
this.logs.push(
149-
`Request failed. Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`,
150-
);
151-
} else if (error.request) {
152-
// The request was made but no response was received
153-
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
154-
// http.ClientRequest in node.js
155-
console.debug('Response: ', error.request);
156-
this.logs.push(`Request failed: ${error.request}`);
157-
} else if (error.message) {
158-
// Something happened in setting up the request that triggered an Error
159-
console.debug('Response: ', error.message);
160-
this.logs.push(`Request failed: ${error.message}`);
161-
} else {
162-
// Something not related to axios request
163-
console.debug('Failure: ', error);
164-
this.logs.push(`Request failed: ${error}`);
165-
}
166-
return ['Failed', node, error];
167-
}
168-
}
169-
170-
#computeEvaluateNode(node, prevNodeOutputData) {
171-
const evalVariables = this.#computeNodeVariables(node.data.variables, prevNodeOutputData);
172-
const var1 = evalVariables.var1;
173-
const var2 = evalVariables.var2;
174-
175-
const operator = node.data.operator;
176-
if (operator == undefined) {
177-
throw 'Operator undefined';
178-
}
179-
this.logs.push(
180-
`Evaluate var1: ${JSON.stringify(var1)} of type: ${typeof var1}, var2: ${JSON.stringify(var2)} of type: ${typeof var2} with operator: ${operator}`,
181-
);
182-
if (operator == Operators.isEqualTo) {
183-
return var1 === var2;
184-
} else if (operator == Operators.isNotEqualTo) {
185-
return var1 != var2;
186-
} else if (operator == Operators.isGreaterThan) {
187-
return var1 > var2;
188-
} else if (operator == Operators.isLessThan) {
189-
return var1 < var2;
190-
}
191-
}
192-
19322
#computeConnectingEdge(node, result) {
19423
let connectingEdge = undefined;
19524

@@ -238,7 +67,7 @@ class Graph {
23867
}
23968

24069
if (node.type === 'evaluateNode') {
241-
if (this.#computeEvaluateNode(node, prevNodeOutputData)) {
70+
if (computeEvaluateNode(node, prevNodeOutputData, this.logs)) {
24271
this.logs.push('Result: true');
24372
result = ['Success', node, prevNodeOutput, true];
24473
} else {
@@ -257,8 +86,13 @@ class Graph {
25786
result = ['Success', node, prevNodeOutput];
25887
}
25988

89+
if (node.type === 'authNode') {
90+
this.auth = node.data.auth;
91+
result = ['Success', node, prevNodeOutput];
92+
}
93+
26094
if (node.type === 'requestNode') {
261-
result = await this.#computeRequestNode(node, prevNodeOutputData);
95+
result = await computeRequestNode(node, prevNodeOutputData, this.auth, this.logs);
26296
}
26397

26498
if (this.#checkTimeout()) {
@@ -278,7 +112,7 @@ class Graph {
278112
if (connectingEdge != undefined) {
279113
const nextNode = this.nodes.find(
280114
(node) =>
281-
['requestNode', 'outputNode', 'evaluateNode', 'delayNode'].includes(node.type) &&
115+
['requestNode', 'outputNode', 'evaluateNode', 'delayNode', 'authNode'].includes(node.type) &&
282116
node.id === connectingEdge.target,
283117
);
284118
this.graphRunNodeOutput[node.id] = result[2] && result[2].data ? result[2].data : {};
@@ -298,7 +132,6 @@ class Graph {
298132
});
299133
this.graphRunNodeOutput = {};
300134

301-
console.debug('Using authkey: ', this.authKey);
302135
this.logs.push('Start Flowtest');
303136
const startNode = this.nodes.find((node) => node.type === 'startNode');
304137
if (startNode == undefined) {
@@ -317,6 +150,7 @@ class Graph {
317150
}
318151
this.logs.push('End Flowtest');
319152
this.logs.push(`Total time: ${Date.now() - this.startTime} ms`);
153+
console.log(this.logs);
320154
this.onGraphComplete(result, this.logs);
321155
});
322156
} else {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Operators from '../../constants/operators';
2+
import { computeNodeVariables } from './utils';
3+
4+
export const computeEvaluateNode = (node, prevNodeOutputData, logs) => {
5+
const evalVariables = computeNodeVariables(node.data.variables, prevNodeOutputData);
6+
const var1 = evalVariables.var1;
7+
const var2 = evalVariables.var2;
8+
9+
const operator = node.data.operator;
10+
if (operator == undefined) {
11+
throw 'Operator undefined';
12+
}
13+
logs.push(
14+
`Evaluate var1: ${JSON.stringify(var1)} of type: ${typeof var1}, var2: ${JSON.stringify(var2)} of type: ${typeof var2} with operator: ${operator}`,
15+
);
16+
if (operator == Operators.isEqualTo) {
17+
return var1 === var2;
18+
} else if (operator == Operators.isNotEqualTo) {
19+
return var1 != var2;
20+
} else if (operator == Operators.isGreaterThan) {
21+
return var1 > var2;
22+
} else if (operator == Operators.isLessThan) {
23+
return var1 < var2;
24+
}
25+
};

0 commit comments

Comments
 (0)