Skip to content

Commit 8150834

Browse files
committed
multipart form-data should allow multiple parms of type text and file
1 parent 0ce64f7 commit 8150834

5 files changed

Lines changed: 240 additions & 63 deletions

File tree

src/components/molecules/flow/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ const Flow = ({ tab, collectionId }) => {
196196

197197
const onGraphComplete = async (status, time, logs) => {
198198
const response = await uploadGraphRunLogs(tab.name, status, time, logs);
199-
console.log(response);
200-
setLogs(tab.id, logs, response);
199+
//console.log(response);
200+
setLogs(tab.id, status, logs, response);
201201
if (status == 'Success') {
202202
toast.success(`FlowTest Run Success!`);
203203
} else if (status == 'Failed') {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { useState, Fragment } from 'react';
2+
import { ClockIcon } from '@heroicons/react/24/outline';
3+
import { Listbox, Transition } from '@headlessui/react';
4+
import { CheckIcon, ChevronUpDownIcon, PlusIcon } from '@heroicons/react/20/solid';
5+
6+
export const optionsData = [
7+
{ value: 'text', label: 'Text' },
8+
{ value: 'file', label: 'File' },
9+
];
10+
11+
const FormDataSelector = ({ onSelectHandler = () => null }) => {
12+
const [selected, setSelected] = useState(null);
13+
14+
const getSelectedLabel = (selectedValue) => {
15+
let labelToShow = '';
16+
optionsData.forEach((element) => {
17+
if (element.value === selectedValue) {
18+
labelToShow = element.label;
19+
}
20+
});
21+
return labelToShow;
22+
};
23+
24+
return (
25+
<>
26+
<Listbox
27+
name='param-selector'
28+
value={selected}
29+
onChange={(selectedValue) => {
30+
setSelected(getSelectedLabel(selectedValue));
31+
onSelectHandler(selectedValue);
32+
}}
33+
>
34+
<div className='relative flex h-full'>
35+
<Listbox.Button
36+
className={`flex items-center justify-between gap-1 sm:text-sm ${optionsData.length ? 'cursor-default' : 'cursor-not-allowed'}`}
37+
>
38+
<PlusIcon className='w-4 h-4' />
39+
</Listbox.Button>
40+
{optionsData.length ? (
41+
<Transition
42+
as={Fragment}
43+
leave='transition ease-in duration-100'
44+
leaveFrom='opacity-100'
45+
leaveTo='opacity-0'
46+
>
47+
<Listbox.Options className='absolute right-0 z-10 w-full overflow-auto text-base bg-white rounded shadow-lg top-11 max-h-60 min-w-24 ring-1 ring-black/5 sm:text-sm'>
48+
{optionsData.map((optionData, optionDataIndex) => (
49+
<Listbox.Option
50+
key={optionDataIndex}
51+
className={({ active }) =>
52+
`relative cursor-default select-none py-2 pl-10 pr-4 hover:font-semibold ${
53+
active ? 'bg-background-light text-slate-900' : ''
54+
}`
55+
}
56+
value={optionData.value}
57+
>
58+
{({ selected }) => (
59+
<>
60+
<span className={`block truncate`}>{optionData.label}</span>
61+
{selected ? (
62+
<span className='absolute inset-y-0 left-0 flex items-center pl-3 font-semibold'>
63+
<CheckIcon className='w-5 h-5' aria-hidden='true' />
64+
</span>
65+
) : null}
66+
</>
67+
)}
68+
</Listbox.Option>
69+
))}
70+
</Listbox.Options>
71+
</Transition>
72+
) : (
73+
''
74+
)}
75+
</div>
76+
</Listbox>
77+
</>
78+
);
79+
};
80+
81+
export default FormDataSelector;

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

Lines changed: 138 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import React, { Fragment, useState, useRef } from 'react';
22
import { PropTypes } from 'prop-types';
3-
import { EllipsisVerticalIcon } from '@heroicons/react/24/solid';
4-
import { DocumentArrowUpIcon, ClipboardDocumentCheckIcon, ClipboardDocumentIcon } from '@heroicons/react/24/outline';
3+
import { EllipsisVerticalIcon, PlusIcon } from '@heroicons/react/24/solid';
4+
import {
5+
DocumentArrowUpIcon,
6+
ClipboardDocumentCheckIcon,
7+
ClipboardDocumentIcon,
8+
TrashIcon,
9+
} from '@heroicons/react/24/outline';
510
import { Menu, Transition } from '@headlessui/react';
611
import useCanvasStore from 'stores/CanvasStore';
712
import { toast } from 'react-toastify';
@@ -15,6 +20,7 @@ import { useTabStore } from 'stores/TabStore';
1520
import { cloneDeep } from 'lodash';
1621
import { CopyToClipboard } from 'react-copy-to-clipboard';
1722
import Tippy from '@tippyjs/react';
23+
import FormDataSelector from './FormDataSelector';
1824

1925
const requestBodyTypeOptions = ['None', 'form-data', 'raw-json'];
2026

@@ -24,7 +30,8 @@ const RequestBody = ({ nodeId, nodeData }) => {
2430

2531
const [copyStatus, setCopyStatus] = useState(false); // To indicate if the text was copied
2632

27-
const uploadFileForRequestNode = useRef(null);
33+
// Refs to hold the input elements
34+
const inputRefs = useRef([]);
2835

2936
const updateCachedValues = () => {
3037
if (nodeData.requestBody) {
@@ -37,12 +44,12 @@ const RequestBody = ({ nodeId, nodeData }) => {
3744
}
3845
};
3946

40-
const handleFileUpload = async (e) => {
47+
const handleFileUpload = async (e, index) => {
4148
if (!e.target.files) return;
4249

4350
if (e.target.files.length === 1) {
4451
const file = e.target.files[0];
45-
const { name } = file;
52+
const { name, path } = file;
4653

4754
const reader = new FileReader();
4855
reader.onload = (evt) => {
@@ -53,24 +60,15 @@ const RequestBody = ({ nodeId, nodeData }) => {
5360

5461
const value = result;
5562

56-
setRequestNodeBody(nodeId, 'form-data', {
57-
key: nodeData.requestBody.body.key,
58-
value: value,
59-
name: name,
60-
});
63+
const updatedParams = [...nodeData.requestBody.body];
64+
updatedParams[index].value = path;
65+
updatedParams[index].name = name;
66+
setRequestNodeBody(nodeId, 'form-data', updatedParams);
6167
};
6268
reader.readAsDataURL(file);
6369
}
6470
};
6571

66-
const handleFormDataKey = (e) => {
67-
setRequestNodeBody(nodeId, 'form-data', {
68-
key: e.target.value,
69-
value: nodeData.requestBody.body.value,
70-
name: nodeData.requestBody.body.name,
71-
});
72-
};
73-
7472
const handleRawJson = (value) => {
7573
setRequestNodeBody(nodeId, 'raw-json', value);
7674
};
@@ -89,11 +87,7 @@ const RequestBody = ({ nodeId, nodeData }) => {
8987
if (cachedValues['form-data']) {
9088
setRequestNodeBody(nodeId, option, cachedValues['form-data']);
9189
} else {
92-
setRequestNodeBody(nodeId, option, {
93-
key: '',
94-
value: '',
95-
name: '',
96-
});
90+
setRequestNodeBody(nodeId, option, []);
9791
}
9892
}
9993
};
@@ -112,13 +106,113 @@ const RequestBody = ({ nodeId, nodeData }) => {
112106
return [];
113107
};
114108

109+
console.log(nodeData.requestBody);
110+
111+
const renderFormData = (params) => {
112+
return (
113+
<div>
114+
{params && params.length > 0 ? (
115+
<table className='leading-normal border-2 border-collapse border-background-dark'>
116+
<thead>
117+
<tr className='text-xs font-bold tracking-wider text-left bg-ghost-50 text-ghost-600'>
118+
<th className='p-2 border-2 border-background-dark'>Key</th>
119+
<th className='p-2 border-2 border-background-dark'>Value</th>
120+
<th className='p-2 border-2 border-background-dark'></th>
121+
</tr>
122+
</thead>
123+
<tbody>
124+
{params.map((param, index) => (
125+
<tr key={index} className='text-sm border-b border-gray-200 text-ghost-700 hover:bg-ghost-50'>
126+
<td className='whitespace-no-wrap border-2 border-background-dark'>
127+
<input
128+
type='text'
129+
className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'
130+
name='variable-name'
131+
value={param.key}
132+
onChange={(event) => {
133+
const updatedParams = [...nodeData.requestBody.body];
134+
updatedParams[index].key = event.target.value;
135+
setRequestNodeBody(nodeId, 'form-data', updatedParams);
136+
}}
137+
/>
138+
</td>
139+
<td className='whitespace-no-wrap border-2 border-background-dark'>
140+
{param.type === 'text' ? (
141+
<input
142+
type='text'
143+
className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'
144+
name='variable-name'
145+
value={param.value}
146+
onChange={(event) => {
147+
const updatedParams = [...nodeData.requestBody.body];
148+
updatedParams[index].value = event.target.value;
149+
setRequestNodeBody(nodeId, 'form-data', updatedParams);
150+
}}
151+
/>
152+
) : (
153+
<div className='w-full nodrag nowheel'>
154+
<Button
155+
btnType={BUTTON_TYPES.secondary}
156+
isDisabled={false}
157+
onClickHandle={() => {
158+
//uploadFileForRequestNode.current.click();
159+
if (inputRefs.current[index]) {
160+
inputRefs.current[index].click(); // Trigger the file input click
161+
}
162+
}}
163+
fullWidth={true}
164+
>
165+
<DocumentArrowUpIcon className='w-4 h-4 text-center' />
166+
<div
167+
className='max-w-xs overflow-hidden whitespace-nowrap'
168+
style={{ textOverflow: 'ellipsis' }}
169+
>
170+
{param.name && param.name.trim() !== '' ? param.name : 'Upload File'}
171+
</div>
172+
{/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}
173+
<div className='hidden'>
174+
<input
175+
type='file'
176+
id='file'
177+
ref={(el) => (inputRefs.current[index] = el)}
178+
onChange={(e) => handleFileUpload(e, index)}
179+
/>
180+
</div>
181+
</Button>
182+
</div>
183+
)}
184+
</td>
185+
<td className='p-2 border-2 border-background-dark'>
186+
<div className='flex items-center gap-4'>
187+
<div
188+
onClick={() => {
189+
const updatedParams = nodeData.requestBody.body.filter((_, i) => i !== index);
190+
setRequestNodeBody(nodeId, 'form-data', updatedParams);
191+
}}
192+
className='cursor-pointer'
193+
>
194+
<TrashIcon className='w-4 h-4' />
195+
</div>
196+
</div>
197+
</td>
198+
</tr>
199+
))}
200+
</tbody>
201+
</table>
202+
) : (
203+
''
204+
)}
205+
</div>
206+
);
207+
};
208+
115209
return (
116210
<>
117-
<div className='flex items-center justify-between bg-background p-4'>
211+
<div className='flex items-center justify-between p-4 bg-background'>
118212
<h3>Body</h3>
119213
<Menu as='div' className='relative inline-block text-left'>
120214
<Menu.Button data-click-from='body-type-menu'>
121-
<EllipsisVerticalIcon className='h-4 w-4' aria-hidden='true' data-click-from='body-type-menu' />
215+
<EllipsisVerticalIcon className='w-4 h-4' aria-hidden='true' data-click-from='body-type-menu' />
122216
</Menu.Button>
123217
<Transition
124218
as={Fragment}
@@ -130,13 +224,13 @@ const RequestBody = ({ nodeId, nodeData }) => {
130224
leaveTo='transform opacity-0 scale-95'
131225
>
132226
<Menu.Items
133-
className='absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white px-1 py-1 shadow-lg ring-1 ring-black/5 focus:outline-none'
227+
className='absolute right-0 z-10 w-56 px-1 py-1 mt-2 origin-top-right bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black/5 focus:outline-none'
134228
data-click-from='body-type-menu'
135229
>
136230
{requestBodyTypeOptions.map((bodyTypeOption, index) => (
137231
<Menu.Item key={index} data-click-from='body-type-menu' onClick={() => handleClose(bodyTypeOption)}>
138232
<button
139-
className='group flex w-full items-center rounded-md px-2 py-2 text-sm text-gray-900 hover:bg-background-light'
233+
className='flex items-center w-full px-2 py-2 text-sm text-gray-900 rounded-md group hover:bg-background-light'
140234
data-click-from='body-type-menu'
141235
>
142236
{bodyTypeOption}
@@ -150,8 +244,8 @@ const RequestBody = ({ nodeId, nodeData }) => {
150244
{nodeData.requestBody && nodeData.requestBody.type === 'raw-json' && (
151245
<>
152246
<NodeHorizontalDivider />
153-
<div className='bg-background p-4'>
154-
<div className='nodrag nowheel w-full min-w-72'>
247+
<div className='p-4 bg-background'>
248+
<div className='w-full nodrag nowheel min-w-72'>
155249
<div className='relative bg-background-lighter'>
156250
<Editor
157251
name='request-body-json'
@@ -161,7 +255,7 @@ const RequestBody = ({ nodeId, nodeData }) => {
161255
completionOptions={getActiveVariables()}
162256
/>
163257

164-
<div className='absolute right-5 top-0 cursor-pointer text-slate-400 hover:text-cyan-900'>
258+
<div className='absolute top-0 cursor-pointer right-5 text-slate-400 hover:text-cyan-900'>
165259
<CopyToClipboard
166260
text={nodeData.requestBody.body}
167261
onCopy={() => {
@@ -172,11 +266,11 @@ const RequestBody = ({ nodeId, nodeData }) => {
172266
<button>
173267
{copyStatus ? (
174268
<Tippy content='Copied to Clipboard' placement='top'>
175-
<ClipboardDocumentCheckIcon className='h-6 w-6' />
269+
<ClipboardDocumentCheckIcon className='w-6 h-6' />
176270
</Tippy>
177271
) : (
178272
<Tippy content='Copy to Clipboard' placement='top'>
179-
<ClipboardDocumentIcon className='h-6 w-6' />
273+
<ClipboardDocumentIcon className='w-6 h-6' />
180274
</Tippy>
181275
)}
182276
</button>
@@ -207,33 +301,19 @@ const RequestBody = ({ nodeId, nodeData }) => {
207301
{nodeData.requestBody && nodeData.requestBody.type === 'form-data' && (
208302
<>
209303
<NodeHorizontalDivider />
210-
<div className='bg-background p-4'>
211-
<TextInputWithLabel
212-
placeHolder='key'
213-
onChangeHandler={(e) => handleFormDataKey(e)}
214-
name={'variable-value'}
215-
value={nodeData.requestBody.body.key}
216-
label={'File'}
217-
/>
218-
<div className='pt-4'>
219-
<Button
220-
btnType={BUTTON_TYPES.secondary}
221-
isDisabled={false}
222-
onClickHandle={() => {
223-
uploadFileForRequestNode.current.click();
224-
}}
225-
fullWidth={true}
226-
>
227-
<DocumentArrowUpIcon className='h-4 w-4 text-center' />
228-
Upload File
229-
{/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}
230-
<div className='hidden'>
231-
<input type='file' id='file' ref={uploadFileForRequestNode} onChange={handleFileUpload} />
232-
</div>
233-
</Button>
234-
<div className='pt-1 text-center'>
235-
{nodeData.requestBody.body.name != '' ? nodeData.requestBody.body.name : 'Choose a file to upload'}
304+
<div className='pb-2 bg-background'>
305+
<div>
306+
<div className='flex items-center justify-between'>
307+
<div className='p-2'>Add Param</div>
308+
<FormDataSelector
309+
onSelectHandler={(type) => {
310+
const currentParams = nodeData.requestBody.body;
311+
const updatedParams = currentParams.concat([{ key: '', value: '', type }]);
312+
setRequestNodeBody(nodeId, 'form-data', updatedParams);
313+
}}
314+
/>
236315
</div>
316+
{renderFormData(nodeData.requestBody.body)}
237317
</div>
238318
</div>
239319
</>

0 commit comments

Comments
 (0)