Skip to content

Commit 3144747

Browse files
authored
Merge pull request #128 from FlowTestAI/file-location
feat: multipart form-data should allow multiple parms of type text and file
2 parents 0ce64f7 + 563e011 commit 3144747

16 files changed

Lines changed: 335 additions & 103 deletions

File tree

.changeset/odd-clouds-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'flowtestai': minor
3+
---
4+
5+
Allow multiple kv params in multipart form data request type

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"date-fns": "^3.6.0",
5757
"eslint-import-resolver-alias": "^1.1.2",
5858
"eslint-plugin-import": "^2.29.1",
59+
"form-data": "^4.0.0",
5960
"immer": "^10.0.4",
6061
"lodash": "^4.17.21",
6162
"mousetrap": "^1.6.5",

packages/flowtest-cli/bin/index.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const argv = yargs(hideBin(process.argv))
6868
try {
6969
const flowData = serialize(JSON.parse(content));
7070
// output json output to a file
71-
//console.log(chalk.green(JSON.stringify(flowData)));
71+
7272
const logger = new GraphLogger();
7373
const startTime = Date.now();
7474
const g = new Graph(
@@ -80,13 +80,11 @@ const argv = yargs(hideBin(process.argv))
8080
logger,
8181
);
8282
console.log(chalk.yellow('Running Flow \n'));
83-
if (flowData.nodes.find((n) => n.type === 'flowNode')) {
84-
console.log(
85-
chalk.blue(
86-
'[Note] This flow contains nested flows so run it from parent directory of collection. Ignore if already doing that. \n',
87-
),
88-
);
89-
}
83+
console.log(
84+
chalk.blue(
85+
'Right now CLI commands must be run from root directory of collection. We will gradually add support to run commands from anywhere inside the collection. \n',
86+
),
87+
);
9088
const result = await g.run();
9189
console.log('\n');
9290
if (result.status === 'Success') {

packages/flowtest-cli/graph/compute/requestnode.js

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ const Node = require('./node');
33
const axios = require('axios');
44
const chalk = require('chalk');
55
const { LogLevel } = require('../GraphLogger');
6+
const FormData = require('form-data');
7+
const { extend, cloneDeep } = require('lodash');
8+
const fs = require('fs');
9+
const path = require('path');
610

711
const newAbortSignal = () => {
812
const abortController = new AbortController();
@@ -113,19 +117,16 @@ class requestNode extends Node {
113117
: JSON.parse('{}');
114118
} else if (this.nodeData.requestBody.type === 'form-data') {
115119
contentType = 'multipart/form-data';
116-
requestData = {
117-
key: computeVariables(this.nodeData.requestBody.body.key, variablesDict),
118-
value: this.nodeData.requestBody.body.value,
119-
name: this.nodeData.requestBody.body.name,
120-
};
120+
const params = cloneDeep(this.nodeData.requestBody.body);
121+
requestData = params;
121122
}
122123
}
123124

124125
const options = {
125126
method: restMethod,
126127
url: finalUrl,
127128
headers: {
128-
'Content-type': contentType,
129+
'content-type': contentType,
129130
},
130131
data: requestData,
131132
};
@@ -141,12 +142,23 @@ class requestNode extends Node {
141142

142143
async runHttpRequest(request) {
143144
try {
144-
if (request.headers['Content-type'] === 'multipart/form-data') {
145-
const requestData = new FormData();
146-
const file = await convertBase64ToBlob(request.data.value);
147-
requestData.append(request.data.key, file, request.data.name);
145+
if (request.headers['content-type'] === 'multipart/form-data') {
146+
const formData = new FormData();
147+
const params = request.data;
148+
await params.map(async (param, index) => {
149+
if (param.type === 'text') {
150+
formData.append(param.key, param.value);
151+
}
152+
153+
if (param.type === 'file') {
154+
let trimmedFilePath = param.value.trim();
155+
156+
formData.append(param.key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
157+
}
158+
});
148159

149-
request.data = requestData;
160+
request.data = formData;
161+
extend(request.headers, formData.getHeaders());
150162
}
151163

152164
// assuming 'application/json' type

packages/flowtest-cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"boxen": "^7.1.1",
1818
"chalk": "^3.0.0",
1919
"dotenv": "^16.4.5",
20+
"form-data": "^4.0.0",
2021
"fs": "^0.0.1-security",
2122
"lodash": "^4.17.21",
2223
"omelette": "^0.4.17",

packages/flowtest-electron/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"dotenv": "^16.4.5",
4444
"electron-store": "^8.1.0",
4545
"flatted": "^3.3.1",
46+
"form-data": "^4.0.0",
4647
"fs": "^0.0.1-security",
4748
"json-refs": "^3.0.15",
4849
"langchain": "^0.1.28",

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const FlowtestAI = require('../ai/flowtestai');
1818
const { stringify, parse } = require('flatted');
1919
const { deserialize, serialize } = require('../utils/flowparser/parser');
2020
const { axiosClient } = require('./axiosClient');
21+
const FormData = require('form-data');
22+
const { extend } = require('lodash');
2123

2224
const collectionStore = new Collections();
2325
const flowTestAI = new FlowtestAI();
@@ -283,23 +285,38 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
283285
}
284286
});
285287

286-
ipcMain.handle('renderer:run-http-request', async (event, request) => {
288+
ipcMain.handle('renderer:run-http-request', async (event, request, collectionPath) => {
287289
try {
288-
if (request.headers['Content-type'] === 'multipart/form-data') {
289-
const requestData = new FormData();
290-
const file = await convertBase64ToBlob(request.data.value);
291-
requestData.append(request.data.key, file, request.data.name);
290+
if (request.headers['content-type'] === 'multipart/form-data') {
291+
const formData = new FormData();
292+
const params = request.data;
293+
await params.map(async (param, index) => {
294+
if (param.type === 'text') {
295+
formData.append(param.key, param.value);
296+
}
297+
298+
if (param.type === 'file') {
299+
let trimmedFilePath = param.value.trim();
300+
301+
if (!path.isAbsolute(trimmedFilePath)) {
302+
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
303+
}
292304

293-
request.data = requestData;
305+
formData.append(param.key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
306+
}
307+
});
308+
309+
request.data = formData;
310+
extend(request.headers, formData.getHeaders());
294311
}
295312

296-
// assuming 'application/json' type
297313
const options = {
298314
...request,
299315
signal: newAbortSignal(),
300316
};
301317

302318
const result = await axios(options);
319+
303320
return {
304321
status: result.status,
305322
statusText: result.statusText,

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import setVarNode from './compute/setvarnode';
1111
import { LogLevel } from './GraphLogger';
1212

1313
class Graph {
14-
constructor(nodes, edges, startTime, initialEnvVars, logger, caller) {
14+
constructor(nodes, edges, startTime, initialEnvVars, logger, caller, collectionPath) {
1515
this.nodes = nodes;
1616
this.edges = edges;
1717
this.logger = logger;
@@ -21,6 +21,7 @@ class Graph {
2121
this.auth = undefined;
2222
this.envVariables = initialEnvVars;
2323
this.caller = caller;
24+
this.collectionPath = collectionPath;
2425
}
2526

2627
#checkTimeout() {
@@ -125,7 +126,14 @@ class Graph {
125126
}
126127

127128
if (node.type === 'requestNode') {
128-
const rNode = new requestNode(node.data, prevNodeOutputData, this.envVariables, this.auth, this.logger);
129+
const rNode = new requestNode(
130+
node.data,
131+
prevNodeOutputData,
132+
this.envVariables,
133+
this.auth,
134+
this.logger,
135+
this.collectionPath,
136+
);
129137
result = await rNode.evaluate();
130138
// add post response variables if any
131139
if (result.postRespVars) {
@@ -146,6 +154,7 @@ class Graph {
146154
this.envVariables,
147155
this.logger,
148156
node.type,
157+
this.collectionPath,
149158
);
150159
result = await cNode.evaluate();
151160
this.envVariables = result.envVars;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import Graph1 from '../Graph';
22
import Node from './node';
33

44
class nestedFlowNode extends Node {
5-
constructor(nodes, edges, startTime, initialEnvVars, logger, caller) {
5+
constructor(nodes, edges, startTime, initialEnvVars, logger, caller, collectionPath) {
66
super('flowNode');
7-
this.internalGraph = new Graph1(nodes, edges, startTime, initialEnvVars, logger, caller);
7+
this.internalGraph = new Graph1(nodes, edges, startTime, initialEnvVars, logger, caller, collectionPath);
88
}
99

1010
async evaluate() {

0 commit comments

Comments
 (0)