Skip to content

Commit 45a6242

Browse files
authored
Merge pull request #49 from FlowTestAI/add-obj-types-constants
Add obj types constants and compute variables in graph flow
2 parents a4c6906 + 0d1f975 commit 45a6242

File tree

20 files changed

+292
-214
lines changed

20 files changed

+292
-214
lines changed

.eslintrc.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ module.exports = {
2121
sourceType: 'module',
2222
},
2323
plugins: ['react', 'react-hooks', 'prettier'],
24-
rules: {},
24+
rules: {
25+
'react/prop-types': 'off', // keeping off for first phase
26+
'no-unused-vars': 'off', // Getting a lot of Error for: '_' is assigned a value but never used no-unused-vars. For now disabling this because need to understand more about the use '_'.
27+
},
2528
settings: {
2629
'import/resolver': {
2730
node: {

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ const computeUrl = (baseUrl, path) => {
88
}
99
};
1010

11+
const replaceSingleToDoubleCurlyBraces = (str) => {
12+
// Replace opening curly braces
13+
str = str.replace(/{/g, '{{');
14+
// Replace closing curly braces
15+
str = str.replace(/}/g, '}}');
16+
return str;
17+
};
18+
1119
const parseOpenAPISpec = (collection) => {
1220
let parsedNodes = [];
1321
try {
@@ -17,7 +25,7 @@ const parseOpenAPISpec = (collection) => {
1725
Object.entries(operation).map(([requestType, request], _) => {
1826
const summary = request['summary'];
1927
const operationId = request['operationId'];
20-
var url = computeUrl(baseUrl, path);
28+
var url = replaceSingleToDoubleCurlyBraces(computeUrl(baseUrl, path));
2129
var variables = {};
2230

2331
// console.log(operationId)
@@ -30,10 +38,10 @@ const parseOpenAPISpec = (collection) => {
3038
// allow different type of variables in request node like string, int, array etc...
3139
if (value['in'] === 'query') {
3240
if (firstQueryParam) {
33-
url = url.concat(`?${value['name']}={${value['name']}}`);
41+
url = url.concat(`?${value['name']}={{${value['name']}}}`);
3442
firstQueryParam = false;
3543
} else {
36-
url = url.concat(`&${value['name']}={${value['name']}}`);
44+
url = url.concat(`&${value['name']}={{${value['name']}}}`);
3745
}
3846
}
3947
});

packages/flowtest-electron/tests/utils/collection-parser.test.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,37 @@ const expected = [
2626
requestType: 'POST',
2727
},
2828
{
29-
url: 'https://petstore3.swagger.io/api/v3/pet/findByStatus?status={status}',
29+
url: 'https://petstore3.swagger.io/api/v3/pet/findByStatus?status={{status}}',
3030
description: 'Finds Pets by status',
3131
operationId: 'findPetsByStatus',
3232
requestType: 'GET',
3333
},
3434
{
35-
url: 'https://petstore3.swagger.io/api/v3/pet/findByTags?tags={tags}',
35+
url: 'https://petstore3.swagger.io/api/v3/pet/findByTags?tags={{tags}}',
3636
description: 'Finds Pets by tags',
3737
operationId: 'findPetsByTags',
3838
requestType: 'GET',
3939
},
4040
{
41-
url: 'https://petstore3.swagger.io/api/v3/pet/{petId}',
41+
url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',
4242
description: 'Find pet by ID',
4343
operationId: 'getPetById',
4444
requestType: 'GET',
4545
},
4646
{
47-
url: 'https://petstore3.swagger.io/api/v3/pet/{petId}',
47+
url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',
4848
description: 'Updates a pet in the store with form data',
4949
operationId: 'updatePetWithForm',
5050
requestType: 'POST',
5151
},
5252
{
53-
url: 'https://petstore3.swagger.io/api/v3/pet/{petId}',
53+
url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}',
5454
description: 'Deletes a pet',
5555
operationId: 'deletePet',
5656
requestType: 'DELETE',
5757
},
5858
{
59-
url: 'https://petstore3.swagger.io/api/v3/pet/{petId}/uploadImage',
59+
url: 'https://petstore3.swagger.io/api/v3/pet/{{petId}}/uploadImage',
6060
description: 'uploads an image',
6161
operationId: 'uploadFile',
6262
requestType: 'POST',
@@ -74,13 +74,13 @@ const expected = [
7474
requestType: 'POST',
7575
},
7676
{
77-
url: 'https://petstore3.swagger.io/api/v3/store/order/{orderId}',
77+
url: 'https://petstore3.swagger.io/api/v3/store/order/{{orderId}}',
7878
description: 'Find purchase order by ID',
7979
operationId: 'getOrderById',
8080
requestType: 'GET',
8181
},
8282
{
83-
url: 'https://petstore3.swagger.io/api/v3/store/order/{orderId}',
83+
url: 'https://petstore3.swagger.io/api/v3/store/order/{{orderId}}',
8484
description: 'Delete purchase order by ID',
8585
operationId: 'deleteOrder',
8686
requestType: 'DELETE',
@@ -98,7 +98,7 @@ const expected = [
9898
requestType: 'POST',
9999
},
100100
{
101-
url: 'https://petstore3.swagger.io/api/v3/user/login?username={username}?password={password}',
101+
url: 'https://petstore3.swagger.io/api/v3/user/login?username={{username}}&password={{password}}',
102102
description: 'Logs user into the system',
103103
operationId: 'loginUser',
104104
requestType: 'GET',
@@ -110,19 +110,19 @@ const expected = [
110110
requestType: 'GET',
111111
},
112112
{
113-
url: 'https://petstore3.swagger.io/api/v3/user/{username}',
113+
url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',
114114
description: 'Get user by user name',
115115
operationId: 'getUserByName',
116116
requestType: 'GET',
117117
},
118118
{
119-
url: 'https://petstore3.swagger.io/api/v3/user/{username}',
119+
url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',
120120
description: 'Update user',
121121
operationId: 'updateUser',
122122
requestType: 'PUT',
123123
},
124124
{
125-
url: 'https://petstore3.swagger.io/api/v3/user/{username}',
125+
url: 'https://petstore3.swagger.io/api/v3/user/{{username}}',
126126
description: 'Delete user',
127127
operationId: 'deleteUser',
128128
requestType: 'DELETE',

src/components/atoms/SelectEnvironment.js

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,16 @@ import React, { Fragment, useState } from 'react';
22
import { Listbox, Transition } from '@headlessui/react';
33
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';
44
import { Square3Stack3DIcon } from '@heroicons/react/24/outline';
5-
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-
// ];
5+
import { useTabStore } from 'stores/TabStore';
266

277
const SelectEnvironment = ({ environments }) => {
8+
const setEnv = useTabStore((state) => state.setSelectedEnv);
9+
2810
const [selected, setSelected] = useState(null);
11+
if (selected) {
12+
setEnv(selected);
13+
}
14+
2915
return (
3016
<Listbox value={selected} onChange={setSelected}>
3117
<div className='relative flex h-full pl-4 border-l border-neutral-300'>

src/components/atoms/Tabs.js

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,45 @@ const Tabs = () => {
66
const tabs = useTabStore((state) => state.tabs);
77
const setFocusTab = useTabStore((state) => state.setFocusTab);
88
const focusTabId = useTabStore((state) => state.focusTabId);
9+
const focusTab = tabs.find((t) => t.id === focusTabId);
910
const closeTab = useTabStore((state) => state.closeTab);
1011
const activeTabStyles =
1112
'before:absolute before:h-[0.25rem] before:w-full before:bg-slate-300 before:content-[""] before:bottom-0 before:left-0';
1213
const tabCommonStyles =
1314
'tab flex items-center gap-x-2 border-r border-neutral-300 bg-transparent pr-0 tracking-[0.15em] transition duration-500 ease-in text-sm';
1415
return (
1516
<div role='tablist' className='tabs tabs-lg'>
16-
{tabs.map((tab, index) => {
17-
return (
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 */}
33-
<div
34-
className='flex h-full items-center px-2 hover:rounded hover:rounded-l-none hover:bg-slate-200'
35-
onClick={() => {
36-
closeTab(tab.id, tab.collectionId);
37-
}}
38-
>
39-
<XMarkIcon className='h-4 w-4' />
40-
</div>
41-
</>
42-
);
43-
})}
17+
{tabs
18+
// tabs belonging to one collection will be shown at a time
19+
.filter((t) => t.collectionId === focusTab.collectionId)
20+
.map((tab, index) => {
21+
return (
22+
<>
23+
<a
24+
role='tab'
25+
className={`${tabCommonStyles} ${focusTabId === tab.id ? activeTabStyles : ''}`}
26+
key={index}
27+
data-id={tab.id}
28+
data-collection-id={tab.collectionId}
29+
onClick={() => {
30+
setFocusTab(tab.id);
31+
console.log(`CLICKED THE ${tab.id}`);
32+
}}
33+
>
34+
{tab.name}
35+
</a>
36+
{/* close needs to be a separate clickable component other wise it gets confused with above */}
37+
<div
38+
className='flex h-full items-center px-2 hover:rounded hover:rounded-l-none hover:bg-slate-200'
39+
onClick={() => {
40+
closeTab(tab.id, tab.collectionId);
41+
}}
42+
>
43+
<XMarkIcon className='h-4 w-4' />
44+
</div>
45+
</>
46+
);
47+
})}
4448
</div>
4549
);
4650
};

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

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

3+
import useCollectionStore from 'stores/CollectionStore';
4+
import { useTabStore } from 'stores/TabStore';
5+
import { computeAuthNode } from './compute/authnode';
36
import { computeEvaluateNode } from './compute/evaluatenode';
47
import { computeRequestNode } from './compute/requestnode';
58

69
class Graph {
7-
constructor(nodes, edges, onGraphComplete) {
10+
constructor(nodes, edges, collectionId, onGraphComplete) {
811
this.nodes = nodes;
912
this.edges = edges;
1013
this.onGraphComplete = onGraphComplete;
@@ -13,6 +16,10 @@ class Graph {
1316
this.startTime = Date.now();
1417
this.graphRunNodeOutput = {};
1518
this.auth = undefined;
19+
this.env = useCollectionStore
20+
.getState()
21+
.collections.find((c) => c.id === collectionId)
22+
?.environments.find((e) => e.name === useTabStore.getState().selectedEnv);
1623
}
1724

1825
#checkTimeout() {
@@ -87,12 +94,12 @@ class Graph {
8794
}
8895

8996
if (node.type === 'authNode') {
90-
this.auth = node.data.auth;
97+
this.auth = computeAuthNode(node.data.auth, this.env);
9198
result = ['Success', node, prevNodeOutput];
9299
}
93100

94101
if (node.type === 'requestNode') {
95-
result = await computeRequestNode(node, prevNodeOutputData, this.auth, this.logs);
102+
result = await computeRequestNode(node, prevNodeOutputData, this.env, this.auth, this.logs);
96103
}
97104

98105
if (this.#checkTimeout()) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { computeVariables } from './utils';
2+
3+
export const computeAuthNode = (auth, env) => {
4+
if (auth.type === 'basic-auth') {
5+
auth.username = computeVariables(auth.username, env?.variables);
6+
auth.password = computeVariables(auth.password, env?.variables);
7+
8+
return auth;
9+
} else if (auth.type === 'no-auth') {
10+
return auth;
11+
} else {
12+
throw Error(`auth type: ${auth.type} is not valid`);
13+
}
14+
};

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

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { computeNodeVariables } from './utils';
1+
import { computeNodeVariables, computeVariables } from './utils';
22

33
const runHttpRequest = (request) => {
44
const { ipcRenderer } = window;
@@ -8,31 +8,24 @@ const runHttpRequest = (request) => {
88
});
99
};
1010

11-
const formulateRequest = (node, finalUrl, auth, logs) => {
11+
const formulateRequest = (node, finalUrl, variablesDict, auth, logs) => {
1212
let restMethod = node.data.requestType.toLowerCase();
1313
let contentType = 'application/json';
1414
let requestData = undefined;
1515

16-
if (restMethod === 'get') {
17-
if (node.data.requestBody) {
18-
if (node.data.requestBody.type === 'raw-json') {
19-
contentType = 'application/json';
20-
requestData = node.data.requestBody.body ? JSON.parse(node.data.requestBody.body) : JSON.parse('{}');
21-
}
22-
}
23-
} else if (restMethod === 'post' || restMethod === 'put') {
24-
if (node.data.requestBody) {
25-
if (node.data.requestBody.type === 'form-data') {
26-
contentType = 'multipart/form-data';
27-
requestData = {
28-
key: node.data.requestBody.body.key,
29-
value: node.data.requestBody.body.value,
30-
name: node.data.requestBody.body.name,
31-
};
32-
} else if (node.data.requestBody.type === 'raw-json') {
33-
contentType = 'application/json';
34-
requestData = node.data.requestBody.body ? JSON.parse(node.data.requestBody.body) : JSON.parse('{}');
35-
}
16+
if (node.data.requestBody) {
17+
if (node.data.requestBody.type === 'raw-json') {
18+
contentType = 'application/json';
19+
requestData = node.data.requestBody.body
20+
? JSON.parse(computeVariables(node.data.requestBody.body, variablesDict))
21+
: JSON.parse('{}');
22+
} else if (node.data.requestBody.type === 'form-data') {
23+
contentType = 'multipart/form-data';
24+
requestData = {
25+
key: computeVariables(node.data.requestBody.body.key, variablesDict),
26+
value: node.data.requestBody.body.value,
27+
name: node.data.requestBody.body.name,
28+
};
3629
}
3730
}
3831

@@ -55,20 +48,22 @@ const formulateRequest = (node, finalUrl, auth, logs) => {
5548
return options;
5649
};
5750

58-
export const computeRequestNode = async (node, prevNodeOutputData, auth, logs) => {
51+
export const computeRequestNode = async (node, prevNodeOutputData, env, auth, logs) => {
5952
// step1 evaluate variables of this node
6053
const evalVariables = computeNodeVariables(node.data.variables, prevNodeOutputData);
6154

55+
const variablesDict = {
56+
...env?.variables,
57+
...evalVariables,
58+
};
59+
6260
// step2 replace variables in url with value
63-
let finalUrl = node.data.url;
64-
Object.entries(evalVariables).map(([vname, vvalue], index) => {
65-
finalUrl = finalUrl.replace(`{${vname}}`, vvalue);
66-
});
61+
const finalUrl = computeVariables(node.data.url, variablesDict);
6762

6863
// step 3
69-
const options = formulateRequest(node, finalUrl, auth, logs);
64+
const options = formulateRequest(node, finalUrl, variablesDict, auth, logs);
7065

71-
console.debug('Evaluated variables: ', evalVariables);
66+
console.debug('Avialable variables: ', variablesDict);
7267
console.debug('Evaluated Url: ', finalUrl);
7368

7469
const res = await runHttpRequest(options);

0 commit comments

Comments
 (0)