Skip to content

Commit 3cc10a7

Browse files
authored
Merge pull request #135 from FlowTestAI/request-headers
feat: allow to define request headers
2 parents 588e446 + 76b4b2b commit 3cc10a7

6 files changed

Lines changed: 222 additions & 37 deletions

File tree

.changeset/proud-ants-flash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'flowtestai-app': minor
3+
---
4+
5+
allow request headers to be input by users

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ class requestNode extends Node {
117117
}
118118
}
119119

120+
if (this.nodeData.headers && this.nodeData.headers.length > 0) {
121+
this.nodeData.headers.map((pair, index) => {
122+
headers[pair.name] = pair.value;
123+
});
124+
}
125+
120126
const options = {
121127
method: restMethod,
122128
url: finalUrl,

packages/flowtest-electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "flowtestai",
2+
"name": "flowtestai-app",
33
"productName": "FlowTestAI",
44
"version": "1.2.0",
55
"homepage": "https://github.com/FlowTestAI/FlowTest/tree/main",

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ class requestNode extends Node {
9797
}
9898
}
9999

100+
if (this.nodeData.headers && this.nodeData.headers.length > 0) {
101+
this.nodeData.headers.map((pair, index) => {
102+
headers[pair.name] = pair.value;
103+
});
104+
}
105+
100106
const options = {
101107
method: restMethod,
102108
url: finalUrl,

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

Lines changed: 177 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import { useTabStore } from 'stores/TabStore';
1818
import { cloneDeep } from 'lodash';
1919
import Tippy from '@tippyjs/react';
2020
import 'tippy.js/dist/tippy.css';
21+
import { Tab } from '@headlessui/react';
2122

2223
const RequestNode = ({ id, data }) => {
2324
const setRequestNodeUrl = useCanvasStore((state) => state.setRequestNodeUrl);
2425
const setRequestNodeType = useCanvasStore((state) => state.setRequestNodeType);
26+
const setRequestNodeHeaders = useCanvasStore((state) => state.setRequestNodeHeaders);
2527
const requestNodeAddPreRequestVar = useCanvasStore((state) => state.requestNodeAddPreRequestVar);
2628
const requestNodeDeletePreRequestVar = useCanvasStore((state) => state.requestNodeDeletePreRequestVar);
2729
const requestNodeChangePreRequestVar = useCanvasStore((state) => state.requestNodeChangePreRequestVar);
@@ -156,6 +158,75 @@ const RequestNode = ({ id, data }) => {
156158
);
157159
};
158160

161+
const renderHeaders = () => {
162+
return (
163+
<div>
164+
{data.headers && data.headers.length > 0 ? (
165+
<table className='border-collapse border-2 border-background-dark leading-normal'>
166+
<thead>
167+
<tr className='bg-ghost-50 text-ghost-600 text-left text-xs font-bold tracking-wider'>
168+
<th className='border-2 border-background-dark p-2'>Name</th>
169+
<th className='border-2 border-background-dark p-2'>Value</th>
170+
<th className='border-2 border-background-dark p-2'></th>
171+
</tr>
172+
</thead>
173+
<tbody>
174+
{data.headers.map((pair, index) => (
175+
<tr key={index} className='text-ghost-700 hover:bg-ghost-50 border-b border-gray-200 text-sm'>
176+
<td className='whitespace-no-wrap border-2 border-background-dark'>
177+
<input
178+
type='text'
179+
className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'
180+
name='header-name'
181+
value={pair.name}
182+
onChange={(e) => {
183+
const existingHeaders = [...data.headers];
184+
existingHeaders[index].name = e.target.value;
185+
setRequestNodeHeaders(id, existingHeaders);
186+
}}
187+
/>
188+
</td>
189+
<td className='whitespace-no-wrap border-2 border-background-dark'>
190+
<input
191+
type='text'
192+
className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'
193+
name='header-value'
194+
data-type='text'
195+
onChange={(e) => {
196+
const existingHeaders = [...data.headers];
197+
existingHeaders[index].value = e.target.value;
198+
setRequestNodeHeaders(id, existingHeaders);
199+
}}
200+
value={pair.value}
201+
/>
202+
</td>
203+
<td className='border-2 border-background-dark p-2'>
204+
<div className='flex items-center gap-4'>
205+
{/* <Tooltip text={variables[id].type} /> */}
206+
<div
207+
onClick={(e) => {
208+
setRequestNodeHeaders(
209+
id,
210+
data.headers.filter((_, i) => i !== index),
211+
);
212+
}}
213+
className='cursor-pointer'
214+
>
215+
<TrashIcon className='h-4 w-4' />
216+
</div>
217+
</div>
218+
</td>
219+
</tr>
220+
))}
221+
</tbody>
222+
</table>
223+
) : (
224+
''
225+
)}
226+
</div>
227+
);
228+
};
229+
159230
const getActiveVariables = () => {
160231
const collectionId = useCanvasStore.getState().collectionId;
161232
if (collectionId) {
@@ -219,6 +290,21 @@ const RequestNode = ({ id, data }) => {
219290
);
220291
};
221292

293+
const getDefaultIndex = () => {
294+
if (data.requestBody?.type) {
295+
return 0;
296+
} else if (
297+
(data.preReqVars && Object.entries(data.preReqVars).length > 0) ||
298+
(data.postRespVars && Object.entries(data.postRespVars).length > 0)
299+
) {
300+
return 1;
301+
} else if (data.headers && data.headers.length > 0) {
302+
return 2;
303+
} else {
304+
return 0;
305+
}
306+
};
307+
222308
return (
223309
<FlowNode
224310
title={listBox()}
@@ -237,44 +323,99 @@ const RequestNode = ({ id, data }) => {
237323
styles={'w-full mb-2'}
238324
/>
239325
<NodeHorizontalDivider />
240-
<RequestBody nodeId={id} nodeData={data} />
241-
<NodeHorizontalDivider />
242-
<div className='bg-background'>
243-
<h3 className='p-2'>Variables</h3>
244-
<div className='px-2'>
245-
<NodeHorizontalDivider />
246-
<div className='pb-2'>
247-
<div className='flex items-center justify-between'>
248-
<div className='p-2'>Pre Request</div>
249-
<button
250-
onClick={() => {
251-
setModalType('pre-request');
252-
setVariableDialogOpen(true);
253-
}}
254-
>
255-
<PlusIcon className='h-4 w-4' />
256-
</button>
326+
<Tab.Group defaultIndex={getDefaultIndex()}>
327+
<Tab.List className='flex space-x-1 rounded-xl bg-blue-900/20 p-1'>
328+
<Tab
329+
className={({ selected }) =>
330+
`w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-blue-700
331+
${selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12] hover:text-white'}`
332+
}
333+
>
334+
Body
335+
</Tab>
336+
<Tab
337+
className={({ selected }) =>
338+
`w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-blue-700
339+
${selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12] hover:text-white'}`
340+
}
341+
>
342+
Variables
343+
</Tab>
344+
<Tab
345+
className={({ selected }) =>
346+
`w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-blue-700
347+
${selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12] hover:text-white'}`
348+
}
349+
>
350+
Headers
351+
</Tab>
352+
</Tab.List>
353+
<Tab.Panels className='mt-2'>
354+
<Tab.Panel>
355+
<RequestBody nodeId={id} nodeData={data} />
356+
</Tab.Panel>
357+
<Tab.Panel>
358+
<div className='bg-background'>
359+
<h3 className='p-2'>Variables</h3>
360+
<div className='px-2'>
361+
<NodeHorizontalDivider />
362+
<div className='pb-2'>
363+
<div className='flex items-center justify-between'>
364+
<div className='p-2'>Pre Request</div>
365+
<button
366+
onClick={() => {
367+
setModalType('pre-request');
368+
setVariableDialogOpen(true);
369+
}}
370+
>
371+
<PlusIcon className='h-4 w-4' />
372+
</button>
373+
</div>
374+
{renderVariables('pre-request')}
375+
</div>
376+
<NodeHorizontalDivider />
377+
<div className='pb-2'>
378+
<div className='flex items-center justify-between'>
379+
<div className='p-2'>Post Response</div>
380+
<button
381+
onClick={() => {
382+
setModalType('post-response');
383+
setVariableDialogOpen(true);
384+
}}
385+
>
386+
<PlusIcon className='h-4 w-4' />
387+
</button>
388+
</div>
389+
{renderVariables('post-response')}
390+
</div>
391+
</div>
257392
</div>
258-
{renderVariables('pre-request')}
259-
</div>
260-
<NodeHorizontalDivider />
261-
<div className='pb-2'>
262-
<div className='flex items-center justify-between'>
263-
<div className='p-2'>Post Response</div>
264-
<button
265-
onClick={() => {
266-
setModalType('post-response');
267-
setVariableDialogOpen(true);
268-
}}
269-
>
270-
<PlusIcon className='h-4 w-4' />
271-
</button>
393+
</Tab.Panel>
394+
<Tab.Panel>
395+
<div className='bg-background'>
396+
{/* <h3 className='p-2'>Headers</h3> */}
397+
<div className='px-2'>
398+
<NodeHorizontalDivider />
399+
<div className='pb-2'>
400+
<div className='flex items-center justify-between'>
401+
<div className='p-2'>Headers</div>
402+
<button
403+
onClick={() => {
404+
const existingHeaders = data.headers || [];
405+
const updatedHeaders = existingHeaders.concat([{ name: '', value: '' }]);
406+
setRequestNodeHeaders(id, updatedHeaders);
407+
}}
408+
>
409+
<PlusIcon className='h-4 w-4' />
410+
</button>
411+
</div>
412+
{renderHeaders()}
413+
</div>
414+
</div>
272415
</div>
273-
{renderVariables('post-response')}
274-
</div>
275-
</div>
276-
</div>
277-
<NodeHorizontalDivider />
416+
</Tab.Panel>
417+
</Tab.Panels>
418+
</Tab.Group>
278419
</div>
279420
<AddVariableModal
280421
closeFn={() => setVariableDialogOpen(false)}

src/stores/CanvasStore.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,33 @@ const useCanvasStore = create((set, get) => ({
331331
});
332332
useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);
333333
},
334+
setRequestNodeHeaders: (nodeId, headers) => {
335+
set({
336+
nodes: get().nodes.map((node) => {
337+
if (node.id === nodeId) {
338+
// it's important to create a new object here, to inform React Flow about the cahnges
339+
if (Object.entries(headers).length === 0) {
340+
const { ['headers']: _, ...data } = node.data;
341+
return {
342+
...node,
343+
data,
344+
};
345+
} else {
346+
return {
347+
...node,
348+
data: {
349+
...node.data,
350+
headers,
351+
},
352+
};
353+
}
354+
}
355+
356+
return node;
357+
}),
358+
});
359+
useTabStore.getState().updateFlowTestNodes(useTabStore.getState().focusTabId, get().nodes);
360+
},
334361
setAssertNodeVariable: (nodeId, name, type, value) => {
335362
set({
336363
nodes: get().nodes.map((node) => {

0 commit comments

Comments
 (0)