Skip to content

Commit f428b6f

Browse files
committed
Open collection flow and don't delete from disk when collection is deleted
1 parent e5fbf35 commit f428b6f

File tree

10 files changed

+214
-34
lines changed

10 files changed

+214
-34
lines changed

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,44 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
104104
}
105105
});
106106

107+
ipcMain.handle('renderer:open-collection', async (event, openAPISpecFilePath, collectionFolderPath) => {
108+
try {
109+
if (isDirectory(collectionFolderPath)) {
110+
// async/await syntax
111+
const api = await SwaggerParser.validate(openAPISpecFilePath);
112+
// console.log("API name: %s, Version: %s", api.info.title, api.info.version);
113+
114+
// resolve references in openapi spec
115+
const resolvedSpec = await JsonRefs.resolveRefs(api);
116+
const parsedNodes = parseOpenAPISpec(resolvedSpec.resolved);
117+
118+
const id = uuidv4();
119+
const collectionName = api.info.title;
120+
121+
const newCollection = {
122+
id: id,
123+
name: collectionName,
124+
pathname: collectionFolderPath,
125+
openapi_spec: resolvedSpec.resolved,
126+
nodes: parsedNodes,
127+
};
128+
129+
mainWindow.webContents.send('main:collection-created', id, collectionName, collectionFolderPath, parsedNodes);
130+
131+
watcher.addWatcher(mainWindow, collectionFolderPath, id);
132+
collectionStore.add(newCollection);
133+
} else {
134+
return Promise.reject(new Error(`Directory: ${collectionFolderPath} does not exist`));
135+
}
136+
} catch (error) {
137+
return Promise.reject(error);
138+
}
139+
});
140+
107141
ipcMain.handle('renderer:delete-collection', async (event, collection) => {
108142
try {
109-
deleteDirectory(collection.pathname);
110-
console.log(`Deleted directory: ${collection.pathname}`);
143+
// deleteDirectory(collection.pathname);
144+
// console.log(`Deleted directory: ${collection.pathname}`);
111145

112146
mainWindow.webContents.send('main:collection-deleted', collection.id);
113147

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Collections {
2020
remove(collection) {
2121
const collections = this.store.get('collections') || [];
2222

23-
if (!isDirectory(collection.pathname)) {
23+
if (collections.find((c) => c.id === collection.id)) {
2424
this.store.set(
2525
'collections',
2626
collections.filter((c) => c.pathname !== collection.pathname),

packages/flowtest-electron/tests/store/collection-store.test.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ describe('collection-store', () => {
1111
id: '1234',
1212
name: 'test',
1313
pathname: `${__dirname}/test`,
14-
collection: '',
14+
openapi_spec: '',
1515
nodes: '{}',
1616
};
1717

1818
const newestCollection = {
1919
id: '12345',
2020
name: 'test1',
2121
pathname: `${__dirname}/test1`,
22-
collection: '',
22+
openapi_spec: '',
2323
nodes: '{}',
2424
};
2525

@@ -38,17 +38,14 @@ describe('collection-store', () => {
3838
store.add(newestCollection);
3939
expect(store.getAll()).toEqual([newCollection, newestCollection]);
4040

41-
// removing a collection whose directory still exist
42-
store.remove(newCollection);
43-
expect(store.getAll()).toEqual([newCollection, newestCollection]);
44-
45-
deleteDirectory(`${__dirname}/test`);
4641
store.remove(newCollection);
4742
expect(store.getAll()).toEqual([newestCollection]);
4843

49-
deleteDirectory(`${__dirname}/test1`);
5044
store.remove(newestCollection);
5145
expect(store.getAll()).toEqual([]);
46+
47+
deleteDirectory(`${__dirname}/test`);
48+
deleteDirectory(`${__dirname}/test1`);
5249
});
5350

5451
it('collection set should be unique by pathname', async () => {
@@ -57,15 +54,15 @@ describe('collection-store', () => {
5754
id: '1234',
5855
name: 'test',
5956
pathname: `${__dirname}/test`,
60-
collection: '',
57+
openapi_spec: '',
6158
nodes: '{}',
6259
};
6360

6461
const newestCollection = {
6562
id: '12345',
6663
name: 'test',
6764
pathname: `${__dirname}/test`,
68-
collection: '',
65+
openapi_spec: '',
6966
nodes: '{}',
7067
};
7168

@@ -80,8 +77,9 @@ describe('collection-store', () => {
8077
store.add(newestCollection);
8178
expect(store.getAll()).toEqual([newCollection]);
8279

83-
deleteDirectory(`${__dirname}/test`);
8480
store.remove(newCollection);
8581
expect(store.getAll()).toEqual([]);
82+
83+
deleteDirectory(`${__dirname}/test`);
8684
});
8785
});

src/components/molecules/flow/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ const Flow = ({ tabId, collectionId, flowData }) => {
297297
}}
298298
title='run'
299299
>
300-
<div>Run</div>
300+
Run
301301
</ControlButton>
302302
</Controls>
303303
<Background variant='dots' gap={12} size={1} />

src/components/molecules/headers/SideBarSubHeader.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ import { FolderArrowDownIcon } from '@heroicons/react/24/outline';
33
import { PlusIcon } from '@heroicons/react/20/solid';
44
import NewCollection from 'components/molecules/modals/create/NewCollectionModal';
55
import ImportCollectionModal from 'components/molecules/modals/ImportCollectionModal';
6+
import OpenCollectionModal from '../modals/OpenCollectionModal';
67

78
const SideBarSubHeader = () => {
8-
const [newCollectionModalOpen, setNewCollectionModalOpen] = useState(false);
9+
//const [newCollectionModalOpen, setNewCollectionModalOpen] = useState(false);
10+
const [openCollectionModal, setOpenCollectionModal] = useState(false);
911
const [importCollectionModal, setImportCollectionModal] = useState(false);
1012
return (
1113
<>
1214
<div className='flex items-center border-b border-neutral-300 text-cyan-950'>
1315
<button
1416
className='inline-flex items-center justify-center w-full gap-2 p-2 px-4 transition whitespace-nowrap hover:bg-slate-100'
15-
onClick={() => setNewCollectionModalOpen(true)}
17+
onClick={() => setOpenCollectionModal(true)}
1618
>
1719
<FolderArrowDownIcon className='w-4 h-4' />
18-
<span className='font-semibold'>New</span>
20+
<span className='font-semibold'>Open</span>
1921
</button>
2022
<button
2123
className='inline-flex items-center justify-center w-full gap-2 p-2 px-4 transition border-l-2 whitespace-nowrap border-slate-100 hover:bg-slate-100'
@@ -27,7 +29,7 @@ const SideBarSubHeader = () => {
2729
</div>
2830
<div>
2931
{/* ToDo: These modals will be contextual in term of selected folder or collection */}
30-
<NewCollection closeFn={() => setNewCollectionModalOpen(false)} open={newCollectionModalOpen} />
32+
<OpenCollectionModal closeFn={() => setOpenCollectionModal(false)} open={openCollectionModal} />
3133
<ImportCollectionModal closeFn={() => setImportCollectionModal(false)} open={importCollectionModal} />
3234
</div>
3335
</>

src/components/molecules/modals/ImportCollectionModal.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ const ImportCollectionModal = ({ closeFn = () => null, open = false }) => {
4141
// This solution will only work in Electron not in webapp
4242
selectDirectory()
4343
.then((dirPath) => {
44-
createCollection(yamlPath, dirPath);
44+
// if user presses cancel in choosing directory dialog, this is returned undefined
45+
if (dirPath) {
46+
createCollection(yamlPath, dirPath);
47+
}
4548
})
4649
.catch((error) => {
4750
console.log(`Failed to create collection: ${error}`);
@@ -89,7 +92,7 @@ const ImportCollectionModal = ({ closeFn = () => null, open = false }) => {
8992
data-import-type='yaml'
9093
>
9194
<DocumentArrowUpIcon className='h-4 w-4' />
92-
Import a YAML file
95+
Import an OpenAPI spec to start a new collection
9396
{/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}
9497
<div className='hidden'>
9598
<input
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { Fragment, useRef } from 'react';
2+
import { PropTypes } from 'prop-types';
3+
import { Dialog, Transition } from '@headlessui/react';
4+
import { DocumentArrowUpIcon } from '@heroicons/react/24/outline';
5+
import ImportCollectionTypes from 'constants/ImportCollectionTypes';
6+
import { createCollection, openCollection } from 'service/collection';
7+
8+
const OpenCollectionModal = ({ closeFn = () => null, open = false }) => {
9+
const importYamlFile = useRef(null);
10+
const handleImportCollectionClick = (event) => {
11+
const elem = event.currentTarget;
12+
const importType = elem.dataset.importType;
13+
importCollectionByType(importType);
14+
};
15+
16+
const importCollectionByType = (importTypeVal) => {
17+
switch (importTypeVal) {
18+
case ImportCollectionTypes.YAML:
19+
importCollectionByYaml();
20+
break;
21+
}
22+
};
23+
24+
const importCollectionByYaml = () => {
25+
importYamlFile.current.click();
26+
};
27+
28+
const selectDirectory = () => {
29+
const { ipcRenderer } = window;
30+
31+
return new Promise((resolve, reject) => {
32+
ipcRenderer.invoke('renderer:open-directory-selection-dialog').then(resolve).catch(reject);
33+
});
34+
};
35+
36+
const handleFileSelection = async (event) => {
37+
const yamlPath = event.target.files[0].path;
38+
39+
closeFn();
40+
41+
// This solution will only work in Electron not in webapp
42+
selectDirectory()
43+
.then((dirPath) => {
44+
// if user presses cancel in choosing directory dialog, this is returned undefined
45+
if (dirPath) {
46+
openCollection(yamlPath, dirPath).catch((error) => {
47+
console.log(`Failed to open collection: ${error}`);
48+
});
49+
}
50+
})
51+
.catch((error) => {
52+
console.log(`Failed to open collection: ${error}`);
53+
});
54+
};
55+
56+
return (
57+
<Transition appear show={open} as={Fragment}>
58+
<Dialog as='div' className='relative z-10' onClose={closeFn}>
59+
<Transition.Child
60+
as={Fragment}
61+
enter='ease-out duration-300'
62+
enterFrom='opacity-0'
63+
enterTo='opacity-100'
64+
leave='ease-in duration-200'
65+
leaveFrom='opacity-100'
66+
leaveTo='opacity-0'
67+
>
68+
<div className='fixed inset-0 bg-black/25' />
69+
</Transition.Child>
70+
71+
<div className='fixed inset-0 overflow-y-auto'>
72+
<div className='flex min-h-full items-center justify-center p-4 text-center'>
73+
<Transition.Child
74+
as={Fragment}
75+
enter='ease-out duration-300'
76+
enterFrom='opacity-0 scale-95'
77+
enterTo='opacity-100 scale-100'
78+
leave='ease-in duration-200'
79+
leaveFrom='opacity-100 scale-100'
80+
leaveTo='opacity-0 scale-95'
81+
>
82+
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all'>
83+
<Dialog.Title
84+
as='h3'
85+
className='border-b border-neutral-300 pb-4 text-center text-lg font-semibold text-gray-900'
86+
>
87+
Collections
88+
</Dialog.Title>
89+
<div className='mt-4'>
90+
<ul className='text-sm font-medium'>
91+
<li
92+
className='flex cursor-pointer items-center justify-start gap-2 rounded-md border border-transparent p-2 hover:bg-slate-100'
93+
onClick={handleImportCollectionClick}
94+
data-import-type='yaml'
95+
>
96+
<DocumentArrowUpIcon className='h-4 w-4' />
97+
Attach OpenAPI spec with exisiting collection
98+
{/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}
99+
<div className='hidden'>
100+
<input
101+
type='file'
102+
id='file'
103+
accept='.yaml,.yml'
104+
ref={importYamlFile}
105+
onChange={handleFileSelection}
106+
/>
107+
</div>
108+
</li>
109+
</ul>
110+
</div>
111+
</Dialog.Panel>
112+
</Transition.Child>
113+
</div>
114+
</div>
115+
</Dialog>
116+
</Transition>
117+
);
118+
};
119+
120+
OpenCollectionModal.propTypes = {
121+
closeFn: PropTypes.func.isRequired,
122+
open: PropTypes.boolean.isRequired,
123+
};
124+
125+
export default OpenCollectionModal;

src/components/molecules/sidebar/Empty.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,22 @@ import React, { useState } from 'react';
22
import { FolderArrowDownIcon } from '@heroicons/react/24/outline';
33
import { PlusIcon } from '@heroicons/react/20/solid';
44
import { BUTTON_TYPES } from 'constants/Common';
5-
import NewCollection from 'components/molecules/modals/create/NewCollectionModal';
65
import ImportCollectionModal from 'components/molecules/modals/ImportCollectionModal';
76
import Button from 'components/atoms/common/Button';
7+
import OpenCollectionModal from '../modals/OpenCollectionModal';
88

99
const Empty = () => {
10-
const [newCollectionModalOpen, setNewCollectionModalOpen] = useState(false);
10+
//const [newCollectionModalOpen, setNewCollectionModalOpen] = useState(false);
11+
const [openCollectionModal, setOpenCollectionModal] = useState(false);
1112
const [importCollectionModal, setImportCollectionModal] = useState(false);
1213
return (
1314
<>
1415
<div className='flex flex-col items-center justify-center p-4'>
15-
<div className='text-xs font-medium'>Create or Import a collection</div>
16+
<div className='text-xs font-medium'>Open or Import a collection</div>
1617
<div className='flex flex-col items-stretch gap-4 mt-4'>
17-
<Button
18-
btnType={BUTTON_TYPES.primary}
19-
isDisabled={false}
20-
onClickHandle={() => setNewCollectionModalOpen(true)}
21-
>
18+
<Button btnType={BUTTON_TYPES.primary} isDisabled={false} onClickHandle={() => setOpenCollectionModal(true)}>
2219
<FolderArrowDownIcon className='w-4 h-4' />
23-
<span className='font-semibold'>New</span>
20+
<span className='font-semibold'>Open</span>
2421
</Button>
2522
<Button
2623
btnType={BUTTON_TYPES.primary}
@@ -33,7 +30,7 @@ const Empty = () => {
3330
</div>
3431
</div>
3532
<div>
36-
<NewCollection closeFn={() => setNewCollectionModalOpen(false)} open={newCollectionModalOpen} />
33+
<OpenCollectionModal closeFn={() => setOpenCollectionModal(false)} open={openCollectionModal} />
3734
<ImportCollectionModal closeFn={() => setImportCollectionModal(false)} open={importCollectionModal} />
3835
</div>
3936
</>

src/service/collection.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@ export const createCollection = (openAPISpecFilePath, collectionFolderPath) => {
1515
});
1616
};
1717

18+
export const openCollection = (openAPISpecFilePath, collectionFolderPath) => {
19+
try {
20+
const collection = useCollectionStore.getState().collections.find((c) => c.pathname === collectionFolderPath);
21+
if (collection) {
22+
return Promise.reject(new Error(`A collection with path: ${collectionFolderPath} already exists`));
23+
} else {
24+
const { ipcRenderer } = window;
25+
26+
return new Promise((resolve, reject) => {
27+
ipcRenderer
28+
.invoke('renderer:open-collection', openAPISpecFilePath, collectionFolderPath)
29+
.then(resolve)
30+
.catch(reject);
31+
});
32+
}
33+
} catch (error) {
34+
console.log(`Error opening collection: ${error}`);
35+
// TODO: show error in UI
36+
}
37+
};
38+
1839
export const deleteCollection = (collectionId) => {
1940
try {
2041
const { ipcRenderer } = window;
@@ -29,7 +50,7 @@ export const deleteCollection = (collectionId) => {
2950
return Promise.reject(new Error('Collection not found'));
3051
}
3152
} catch (error) {
32-
console.log(`Error deleting collection: ${collectionId}`);
53+
console.log(`Error deleting collection: ${error}`);
3354
// TODO: show error in UI
3455
}
3556
};

0 commit comments

Comments
 (0)