Skip to content

Commit 69d8c6f

Browse files
authored
Merge pull request #58 from FlowTestAI/env-ui-integrate
Integrate env add/delete variables action in ui
2 parents cb3b581 + 75f239b commit 69d8c6f

File tree

9 files changed

+208
-83
lines changed

9 files changed

+208
-83
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,11 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
185185

186186
ipcMain.handle('renderer:update-environment', async (event, collectionPath, name, variables) => {
187187
try {
188+
const env = Object.entries(variables)
189+
.map(([key, value]) => `${key}: "${value}"`)
190+
.join('\n');
188191
const envDir = path.join(collectionPath, 'environments');
189-
updateFile(path.join(envDir, name), variables);
192+
updateFile(path.join(envDir, name), env);
190193
} catch (error) {
191194
return Promise.reject(error);
192195
}

src/components/atoms/Tabs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const Tabs = () => {
4848
<div role='tablist' className='overflow-scroll tabs tabs-lg'>
4949
{tabs
5050
// tabs belonging to one collection will be shown at a time
51-
.reverse()
51+
//.reverse()
5252
.filter((t) => t.collectionId === focusTab.collectionId)
5353
.map((tab, index) => {
5454
return (

src/components/molecules/environment/index.js

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import useEnvStore from 'stores/EnvStore';
3-
import { EditText } from 'react-edit-text';
43
import 'react-edit-text/dist/index.css';
5-
import { toast } from 'react-toastify';
64
import { TrashIcon } from '@heroicons/react/24/outline';
5+
import AddEnvVariableModal from '../modals/AddEnvVariableModal';
6+
import ConfirmActionModal from '../modals/ConfirmActionModal';
77

88
const Env = () => {
99
const variables = useEnvStore((state) => state.variables);
10-
const handleAddRow = useEnvStore((state) => state.handleAddRow);
11-
const handleKeyChange = useEnvStore((state) => state.handleKeyChange);
12-
const handleValueChange = useEnvStore((state) => state.handleValueChange);
10+
const handleAddVariable = useEnvStore((state) => state.handleAddVariable);
11+
const handleDeleteVariable = useEnvStore((state) => state.handleDeleteVariable);
1312

14-
const handleSave = ({ name, value, previousValue }) => {
15-
console.log('handle save');
16-
if (name === 'editVariableKey') {
17-
const existingVar = Object.keys(variables).find((key) => key === value);
18-
if (existingVar) {
19-
toast.error('A variable with the same name already exists');
20-
} else {
21-
handleKeyChange(value, previousValue);
22-
}
23-
} else {
24-
handleValueChange(name, value);
25-
}
26-
};
13+
const [addVariableModalOpen, setAddVariableModalOpen] = useState(false);
14+
const [confirmActionModalOpen, setConfirmActionModalOpen] = useState(false);
15+
16+
const [deleteKey, setDeleteKey] = useState('');
2717

2818
return (
2919
<div className='p-4'>
@@ -43,17 +33,14 @@ const Env = () => {
4333
{Object.entries(variables).map(([key, value], index) => (
4434
<tr key={index}>
4535
<th>{index}</th>
46-
<td>
47-
<EditText name='editVariableKey' className='text-xl' value={key} onSave={handleSave} />
48-
</td>
49-
<td>
50-
<EditText name={key} className='text-xl' value={value} onSave={handleSave} />
51-
</td>
36+
<td>{key}</td>
37+
<td>{value}</td>
5238
<td>
5339
<div
5440
className='relative inline-block p-2 text-left transition duration-200 ease-out rounded rounded-l-none hover:bg-slate-200'
5541
onClick={() => {
56-
console.log(`Delete ${key}`);
42+
setDeleteKey(key);
43+
setConfirmActionModalOpen(true);
5744
}}
5845
>
5946
<TrashIcon className='w-4 h-4' aria-hidden='true' />
@@ -63,8 +50,22 @@ const Env = () => {
6350
))}
6451
</tbody>
6552
</table>
66-
<button onClick={handleAddRow}>+ Add Variable</button>
53+
<button onClick={() => setAddVariableModalOpen(true)}>+ Add Variable</button>
6754
</div>
55+
<AddEnvVariableModal
56+
closeFn={() => setAddVariableModalOpen(false)}
57+
open={addVariableModalOpen}
58+
handleAddVariable={(key, value) => handleAddVariable(key, value)}
59+
/>
60+
<ConfirmActionModal
61+
closeFn={() => setConfirmActionModalOpen(false)}
62+
open={confirmActionModalOpen}
63+
message='Do you want to delete this variable?'
64+
actionFn={() => {
65+
handleDeleteVariable(deleteKey);
66+
setConfirmActionModalOpen(false);
67+
}}
68+
/>
6869
</div>
6970
);
7071
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React, { Fragment, useState } from 'react';
2+
import { PropTypes } from 'prop-types';
3+
import { Dialog, Transition } from '@headlessui/react';
4+
import Button from 'components/atoms/common/Button';
5+
import { BUTTON_TYPES } from 'constants/Common';
6+
import useEnvStore from 'stores/EnvStore';
7+
import { toast } from 'react-toastify';
8+
9+
const AddEnvVariableModal = ({ closeFn = () => null, open = false, handleAddVariable }) => {
10+
const [key, setKey] = useState('');
11+
const [value, setValue] = useState('');
12+
13+
return (
14+
<Transition appear show={open} as={Fragment}>
15+
<Dialog as='div' className='relative z-10' onClose={closeFn}>
16+
<Transition.Child
17+
as={Fragment}
18+
enter='ease-out duration-300'
19+
enterFrom='opacity-0'
20+
enterTo='opacity-100'
21+
leave='ease-in duration-200'
22+
leaveFrom='opacity-100'
23+
leaveTo='opacity-0'
24+
>
25+
<div className='fixed inset-0 bg-black/25' />
26+
</Transition.Child>
27+
28+
<div className='fixed inset-0 overflow-y-auto'>
29+
<div className='flex items-center justify-center min-h-full p-4 text-center'>
30+
<Transition.Child
31+
as={Fragment}
32+
enter='ease-out duration-300'
33+
enterFrom='opacity-0 scale-95'
34+
enterTo='opacity-100 scale-100'
35+
leave='ease-in duration-200'
36+
leaveFrom='opacity-100 scale-100'
37+
leaveTo='opacity-0 scale-95'
38+
>
39+
<Dialog.Panel className='w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl'>
40+
<Dialog.Title
41+
as='h3'
42+
className='pb-4 text-lg font-semibold text-center text-gray-900 border-b border-neutral-300'
43+
>
44+
Add Variable
45+
</Dialog.Title>
46+
<div className='mt-6'>
47+
<div className='mt-4'>
48+
<input
49+
id='key'
50+
className='block w-full rounded border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 outline-blue-300 focus:border-blue-100 focus:ring-blue-100'
51+
placeholder='Key'
52+
value={key}
53+
onChange={(event) => setKey(event.target.value)}
54+
/>
55+
</div>
56+
<div className='mt-4'>
57+
<input
58+
id='value'
59+
className='block w-full rounded border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 outline-blue-300 focus:border-blue-100 focus:ring-blue-100'
60+
placeholder='Value'
61+
value={value}
62+
onChange={(event) => setValue(event.target.value)}
63+
/>
64+
</div>
65+
</div>
66+
<div className='flex items-center gap-2 mt-6'>
67+
<Button btnType={BUTTON_TYPES.error} isDisabled={false} onClickHandle={closeFn} fullWidth={true}>
68+
Cancel
69+
</Button>
70+
<Button
71+
btnType={BUTTON_TYPES.info}
72+
isDisabled={false}
73+
fullWidth={true}
74+
onClickHandle={() => {
75+
const variables = useEnvStore.getState().variables;
76+
if (key.trim() === '') {
77+
toast.error('Variable name cannot be empty.');
78+
} else if (variables[key] != undefined) {
79+
toast.error('A variable with the same name already exists.');
80+
} else {
81+
handleAddVariable(key, value);
82+
}
83+
closeFn();
84+
}}
85+
>
86+
Add
87+
</Button>
88+
</div>
89+
</Dialog.Panel>
90+
</Transition.Child>
91+
</div>
92+
</div>
93+
</Dialog>
94+
</Transition>
95+
);
96+
};
97+
98+
AddEnvVariableModal.propTypes = {
99+
closeFn: PropTypes.func.isRequired,
100+
open: PropTypes.boolean.isRequired,
101+
};
102+
103+
export default AddEnvVariableModal;

src/components/molecules/modals/SaveFlowModal.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { Dialog, Transition } from '@headlessui/react';
44
import { InboxArrowDownIcon } from '@heroicons/react/24/outline';
55
import Tippy from '@tippyjs/react';
66
import 'tippy.js/dist/tippy.css';
7-
import { updateFlowTest } from 'service/collection';
7+
import { updateEnvironmentFile, updateFlowTest } from 'service/collection';
88
import { toast } from 'react-toastify';
9+
import { OBJ_TYPES } from 'constants/Common';
910

1011
const SaveFlowModal = ({ tab }) => {
1112
let [isOpen, setIsOpen] = useState(false);
@@ -15,15 +16,29 @@ const SaveFlowModal = ({ tab }) => {
1516
}
1617

1718
function saveHandle() {
18-
updateFlowTest(tab.pathname, tab.flowDataDraft, tab.collectionId)
19-
.then((result) => {
20-
console.log(`Updated flowtest: path = ${tab.pathname}, collectionId = ${tab.collectionId}, result: ${result}`);
21-
toast.success(`Updated the flowtest: ${tab.pathname}`);
22-
})
23-
.catch((error) => {
24-
console.log(`Error updating flowtest = ${tab.pathname}: ${error}`);
25-
toast.error(`Error while updating flowtest: ${tab.pathname}`);
26-
});
19+
if (tab.type == OBJ_TYPES.flowtest && tab.flowDataDraft) {
20+
updateFlowTest(tab.pathname, tab.flowDataDraft, tab.collectionId)
21+
.then((result) => {
22+
console.log(
23+
`Updated flowtest: path = ${tab.pathname}, collectionId = ${tab.collectionId}, result: ${result}`,
24+
);
25+
toast.success(`Updated the flowtest: ${tab.pathname}`);
26+
})
27+
.catch((error) => {
28+
console.log(`Error updating flowtest = ${tab.pathname}: ${error}`);
29+
toast.error(`Error while updating flowtest: ${tab.pathname}`);
30+
});
31+
} else if (tab.type == OBJ_TYPES.environment && tab.variablesDraft) {
32+
updateEnvironmentFile(tab.name, tab.collectionId, tab.variablesDraft)
33+
.then((result) => {
34+
console.log(`Updated environment: name = ${tab.name}, collectionId = ${tab.collectionId}, result: ${result}`);
35+
toast.success(`Updated environment: ${tab.name}`);
36+
})
37+
.catch((error) => {
38+
console.log(`Error updating environment = ${tab.name}: ${error}`);
39+
toast.error(`Error while updating environment: ${tab.name}`);
40+
});
41+
}
2742
}
2843

2944
// ToDo: Save the file with the given file name

src/service/collection.js

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export const openCollection = (openAPISpecFilePath, collectionFolderPath) => {
3434
}
3535
} catch (error) {
3636
console.log(`Error opening collection: ${error}`);
37-
toast.error('Error opening collection');
37+
//toast.error('Error opening collection');
38+
return Promise.reject(new Error('Error opening collection'));
3839
}
3940
};
4041

@@ -53,7 +54,8 @@ export const deleteCollection = (collectionId) => {
5354
}
5455
} catch (error) {
5556
console.log(`Error deleting collection: ${error}`);
56-
toast.error('Error deleting collection');
57+
//toast.error('Error deleting collection');
58+
return Promise.reject(new Error('Error deleting collection'));
5759
}
5860
};
5961

@@ -77,7 +79,8 @@ export const createFolder = (folderName, folderPath, collectionId) => {
7779
}
7880
} catch (error) {
7981
console.log(`Error creating new folder: ${error}`);
80-
toast.error('Error creating new folder');
82+
//toast.error('Error creating new folder');
83+
return Promise.reject(new Error('Error creating new folder'));
8184
}
8285
};
8386

@@ -100,7 +103,8 @@ export const deleteFolder = (folderPath, collectionId) => {
100103
}
101104
} catch (error) {
102105
console.log(`Error deleting folder: ${error}`);
103-
toast.error('Error deleting folder');
106+
//toast.error('Error deleting folder');
107+
return Promise.reject(new Error('Error deleting folder'));
104108
}
105109
};
106110

@@ -129,7 +133,8 @@ export const createEnvironmentFile = (name, collectionId) => {
129133
return Promise.reject(new Error('Collection not found'));
130134
}
131135
} catch (error) {
132-
toast.error('Error creating new environment');
136+
//toast.error('Error creating new environment');
137+
return Promise.reject(new Error('Error creating new environment'));
133138
}
134139
};
135140

@@ -149,7 +154,8 @@ export const readEnvironmentFile = (name, collectionId) => {
149154
throw new Error('Collection not found');
150155
}
151156
} catch (error) {
152-
toast.error(`Error reading environment: ${name}`);
157+
//toast.error(`Error reading environment: ${name}`);
158+
return Promise.reject(new Error('Error reading environment'));
153159
}
154160
};
155161

@@ -160,7 +166,7 @@ export const updateEnvironmentFile = (name, collectionId, variables) => {
160166
const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);
161167

162168
if (collection) {
163-
const existingEnv = collection.enviroments.find((e) => e.name === name);
169+
const existingEnv = collection.environments.find((e) => e.name === name);
164170
if (existingEnv) {
165171
return new Promise((resolve, reject) => {
166172
ipcRenderer
@@ -175,7 +181,9 @@ export const updateEnvironmentFile = (name, collectionId, variables) => {
175181
return Promise.reject(new Error('Collection not found'));
176182
}
177183
} catch (error) {
178-
toast.error('Error updating environment');
184+
console.log(`Error updating environment: ${error}`);
185+
//toast.error('Error updating environment');
186+
return Promise.reject(new Error('Error updating environment'));
179187
}
180188
};
181189

@@ -198,7 +206,8 @@ export const deleteEnvironmentFile = (name, collectionId) => {
198206
return Promise.reject(new Error('Collection not found'));
199207
}
200208
} catch (error) {
201-
toast.error('Error deleting environment');
209+
//toast.error('Error deleting environment');
210+
return Promise.reject(new Error('Error deleting environment'));
202211
}
203212
};
204213

@@ -248,7 +257,8 @@ export const createFlowTest = (name, folderPath, collectionId) => {
248257
}
249258
} catch (error) {
250259
console.log(`Error creating new flowtest: ${error}`);
251-
toast.error('Error creating new flowtest');
260+
//toast.error('Error creating new flowtest');
261+
return Promise.reject(new Error('Error creating new flowtest'));
252262
}
253263
};
254264

@@ -278,7 +288,8 @@ export const readFlowTest = (pathname, collectionId) => {
278288
}
279289
} catch (error) {
280290
console.log(`Error reading flowtest: ${error}`);
281-
toast.error('Error reading flowtest');
291+
//toast.error('Error reading flowtest');
292+
return Promise.reject(new Error('Error reading flowtest'));
282293
}
283294
};
284295

@@ -303,7 +314,8 @@ export const updateFlowTest = (pathname, flowData, collectionId) => {
303314
}
304315
} catch (error) {
305316
console.log(`Error updating flowtest: ${error}`);
306-
toast.error('Error updating flowtest');
317+
//toast.error('Error updating flowtest');
318+
return Promise.reject(new Error('Error updating flowtest'));
307319
}
308320
};
309321

@@ -326,6 +338,7 @@ export const deleteFlowTest = (pathname, collectionId) => {
326338
}
327339
} catch (error) {
328340
console.log(`Error deleting flowtest: ${error}`);
329-
toast.error('Error deleting flowtest');
341+
//toast.error('Error deleting flowtest');
342+
return Promise.reject(new Error('Error deleting flowtest'));
330343
}
331344
};

0 commit comments

Comments
 (0)