From bf4c26de3372cfd9f2d0d629abe03dc44a044d6d Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 30 Oct 2022 00:09:24 +0530 Subject: [PATCH 1/2] feat: refactored import collection --- .gitignore | 1 + .../src/components/Modal/StyledWrapper.js | 7 ++++ .../bruno-app/src/components/Modal/index.js | 3 ++ .../Sidebar/ImportCollection/index.js | 40 +++++++++++++++++++ .../src/components/Sidebar/TitleBar/index.js | 16 ++------ .../bruno-app/src/components/Welcome/index.js | 16 +++----- .../bruno-collection.js} | 19 ++++----- .../samples/sample-collection.json | 0 8 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/ImportCollection/index.js rename packages/bruno-app/src/utils/{collections/import.js => importers/bruno-collection.js} (86%) rename packages/bruno-app/src/utils/{collections => importers}/samples/sample-collection.json (100%) diff --git a/.gitignore b/.gitignore index 33e25480..990068b0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ chrome-extension chrome-extension.pem chrome-extension.crx bruno.zip +*.zip # misc .DS_Store diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index 0b140347..b8d0b5c2 100644 --- a/packages/bruno-app/src/components/Modal/StyledWrapper.js +++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js @@ -144,6 +144,13 @@ const Wrapper = styled.div` border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; } + + &.modal-footer-none { + .bruno-modal-content { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } + } `; export default Wrapper; diff --git a/packages/bruno-app/src/components/Modal/index.js b/packages/bruno-app/src/components/Modal/index.js index bc7d9079..0e888cda 100644 --- a/packages/bruno-app/src/components/Modal/index.js +++ b/packages/bruno-app/src/components/Modal/index.js @@ -64,6 +64,9 @@ const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfi if (isClosing) { classes += ' modal--animate-out'; } + if(hideFooter) { + classes += ' modal-footer-none'; + } return (
diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js new file mode 100644 index 00000000..6857b3dc --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { collectionImported } from 'providers/ReduxStore/slices/collections'; +import importBrunoCollection from 'utils/importers/bruno-collection'; +import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; +import { toastError } from 'utils/common/error'; +import toast from 'react-hot-toast'; +import Modal from 'components/Modal'; + +const ImportCollection = ({ onClose }) => { + const dispatch = useDispatch(); + const { activeWorkspaceUid } = useSelector((state) => state.workspaces); + + const handleImportBrunoCollection = () => { + importBrunoCollection() + .then((collection) => { + dispatch(collectionImported({ collection: collection })); + dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid)); + toast.success('Collection imported successfully'); + onClose(); + }) + .catch((err) => toastError(err, 'Import collection failed')); + }; + + return ( + +
+
+ Bruno Collection +
+
Postman Collection
+
+
+ ); +}; + +export default ImportCollection; diff --git a/packages/bruno-app/src/components/Sidebar/TitleBar/index.js b/packages/bruno-app/src/components/Sidebar/TitleBar/index.js index fbce081b..189d9fc3 100644 --- a/packages/bruno-app/src/components/Sidebar/TitleBar/index.js +++ b/packages/bruno-app/src/components/Sidebar/TitleBar/index.js @@ -2,8 +2,8 @@ import toast from 'react-hot-toast'; import Bruno from 'components/Bruno'; import Dropdown from 'components/Dropdown'; import CreateCollection from '../CreateCollection'; -import importCollection from 'utils/collections/import'; import SelectCollection from 'components/Sidebar/Collections/SelectCollection'; +import ImportCollection from 'components/Sidebar/ImportCollection'; import { IconDots } from '@tabler/icons'; import { IconFolders } from '@tabler/icons'; @@ -11,13 +11,13 @@ import { isElectron } from 'utils/common/platform'; import { useState, forwardRef, useRef } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { showHomePage } from 'providers/ReduxStore/slices/app'; -import { collectionImported } from 'providers/ReduxStore/slices/collections'; import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import StyledWrapper from './StyledWrapper'; const TitleBar = () => { const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); + const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false); const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false); const { activeWorkspaceUid } = useSelector((state) => state.workspaces); const isPlatformElectron = isElectron(); @@ -48,18 +48,10 @@ const TitleBar = () => { .catch(() => toast.error('An error occured while adding collection to workspace')); }; - const handleImportCollection = () => { - importCollection() - .then((collection) => { - dispatch(collectionImported({ collection: collection })); - dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid)); - }) - .catch((err) => console.log(err)); - }; - return ( {createCollectionModalOpen ? setCreateCollectionModalOpen(false)} /> : null} + {importCollectionModalOpen ? setImportCollectionModalOpen(false)} /> : null} {addCollectionToWSModalOpen ? ( setAddCollectionToWSModalOpen(false)} onSelect={handleAddCollectionToWorkspace} /> @@ -91,7 +83,7 @@ const TitleBar = () => { className="dropdown-item" onClick={(e) => { menuDropdownTippyRef.current.hide(); - handleImportCollection(); + setImportCollectionModalOpen(true); }} > Import Collection diff --git a/packages/bruno-app/src/components/Welcome/index.js b/packages/bruno-app/src/components/Welcome/index.js index 09801843..33a7eaed 100644 --- a/packages/bruno-app/src/components/Welcome/index.js +++ b/packages/bruno-app/src/components/Welcome/index.js @@ -10,13 +10,15 @@ import { IconBrandGithub, IconPlus, IconUpload, IconFiles, IconFolders, IconPlay import Bruno from 'components/Bruno'; import CreateCollection from 'components/Sidebar/CreateCollection'; import SelectCollection from 'components/Sidebar/Collections/SelectCollection'; -import importCollection, { importSampleCollection } from 'utils/collections/import'; +import { importSampleCollection } from 'utils/importers/bruno-collection'; +import ImportCollection from 'components/Sidebar/ImportCollection'; import StyledWrapper from './StyledWrapper'; const Welcome = () => { const dispatch = useDispatch(); const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false); + const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false); const { activeWorkspaceUid } = useSelector((state) => state.workspaces); const isPlatformElectron = isElectron(); @@ -29,15 +31,6 @@ const Welcome = () => { .catch(() => toast.error('An error occured while adding collection to workspace')); }; - const handleImportCollection = () => { - importCollection() - .then((collection) => { - dispatch(collectionImported({ collection: collection })); - dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid)); - }) - .catch((err) => console.log(err)); - }; - const handleImportSampleCollection = () => { importSampleCollection() .then((collection) => { @@ -58,6 +51,7 @@ const Welcome = () => { return ( {createCollectionModalOpen ? setCreateCollectionModalOpen(false)} /> : null} + {importCollectionModalOpen ? setImportCollectionModalOpen(false)} /> : null} {addCollectionToWSModalOpen ? ( setAddCollectionToWSModalOpen(false)} onSelect={handleAddCollectionToWorkspace} /> @@ -83,7 +77,7 @@ const Welcome = () => { Add Collection to Workspace
-
+
setImportCollectionModalOpen(true)}> Import Collection
diff --git a/packages/bruno-app/src/utils/collections/import.js b/packages/bruno-app/src/utils/importers/bruno-collection.js similarity index 86% rename from packages/bruno-app/src/utils/collections/import.js rename to packages/bruno-app/src/utils/importers/bruno-collection.js index 3b8b0198..040bc8fa 100644 --- a/packages/bruno-app/src/utils/collections/import.js +++ b/packages/bruno-app/src/utils/importers/bruno-collection.js @@ -1,11 +1,11 @@ import each from 'lodash/each'; import get from 'lodash/get'; import fileDialog from 'file-dialog'; -import toast from 'react-hot-toast'; import cloneDeep from 'lodash/cloneDeep'; import { uuid } from 'utils/common'; import { collectionSchema } from '@usebruno/schema'; import { saveCollectionToIdb } from 'utils/idb'; +import { BrunoError } from 'utils/common/error'; import sampleCollection from './samples/sample-collection.json'; const readFile = (files) => { @@ -23,8 +23,8 @@ const parseJsonCollection = (str) => { let parsed = JSON.parse(str); return resolve(parsed); } catch (err) { - toast.error('Unable to parse the collection json file'); - reject(err); + console.log(err); + reject(new BrunoError('Unable to parse the collection json file')); } }); }; @@ -35,8 +35,8 @@ const validateSchema = (collection = {}) => { .validate(collection) .then(() => resolve(collection)) .catch((err) => { - toast.error('The Collection file is corrupted'); - reject(err); + console.log(err); + reject(new BrunoError('The Collection file is corrupted')); }); }); }; @@ -74,13 +74,10 @@ const importCollection = () => { .then(updateUidsInCollection) .then(validateSchema) .then((collection) => saveCollectionToIdb(window.__idb, collection)) - .then((collection) => { - toast.success('Collection imported successfully'); - resolve(collection); - }) + .then((collection) => resolve(collection)) .catch((err) => { - toast.error('Import collection failed'); - reject(err); + console.log(err); + reject(new BrunoError('Import collection failed')); }); }); }; diff --git a/packages/bruno-app/src/utils/collections/samples/sample-collection.json b/packages/bruno-app/src/utils/importers/samples/sample-collection.json similarity index 100% rename from packages/bruno-app/src/utils/collections/samples/sample-collection.json rename to packages/bruno-app/src/utils/importers/samples/sample-collection.json From 481486cd1cdb5bab128688daacc48299be2ddebf Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 30 Oct 2022 02:00:54 +0530 Subject: [PATCH 2/2] feat: import postman collection (#45) --- .../Sidebar/ImportCollection/index.js | 19 +- .../src/utils/importers/bruno-collection.js | 42 +--- .../bruno-app/src/utils/importers/common.js | 44 +++++ .../src/utils/importers/postman-collection.js | 187 ++++++++++++++++++ 4 files changed, 250 insertions(+), 42 deletions(-) create mode 100644 packages/bruno-app/src/utils/importers/common.js create mode 100644 packages/bruno-app/src/utils/importers/postman-collection.js diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js index 6857b3dc..e922d796 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js @@ -2,6 +2,7 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { collectionImported } from 'providers/ReduxStore/slices/collections'; import importBrunoCollection from 'utils/importers/bruno-collection'; +import importPostmanCollection from 'utils/importers/postman-collection'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { toastError } from 'utils/common/error'; import toast from 'react-hot-toast'; @@ -22,6 +23,17 @@ const ImportCollection = ({ onClose }) => { .catch((err) => toastError(err, 'Import collection failed')); }; + const handleImportPostmanCollection = () => { + importPostmanCollection() + .then((collection) => { + dispatch(collectionImported({ collection: collection })); + dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid)); + toast.success('Postman Collection imported successfully'); + onClose(); + }) + .catch((err) => toastError(err, 'Postman Import collection failed')); + }; + return (
@@ -31,7 +43,12 @@ const ImportCollection = ({ onClose }) => { > Bruno Collection
-
Postman Collection
+
+ Postman Collection +
); diff --git a/packages/bruno-app/src/utils/importers/bruno-collection.js b/packages/bruno-app/src/utils/importers/bruno-collection.js index 040bc8fa..847b2f61 100644 --- a/packages/bruno-app/src/utils/importers/bruno-collection.js +++ b/packages/bruno-app/src/utils/importers/bruno-collection.js @@ -1,11 +1,7 @@ -import each from 'lodash/each'; -import get from 'lodash/get'; import fileDialog from 'file-dialog'; -import cloneDeep from 'lodash/cloneDeep'; -import { uuid } from 'utils/common'; -import { collectionSchema } from '@usebruno/schema'; import { saveCollectionToIdb } from 'utils/idb'; import { BrunoError } from 'utils/common/error'; +import { validateSchema, updateUidsInCollection } from './common'; import sampleCollection from './samples/sample-collection.json'; const readFile = (files) => { @@ -29,42 +25,6 @@ const parseJsonCollection = (str) => { }); }; -const validateSchema = (collection = {}) => { - return new Promise((resolve, reject) => { - collectionSchema - .validate(collection) - .then(() => resolve(collection)) - .catch((err) => { - console.log(err); - reject(new BrunoError('The Collection file is corrupted')); - }); - }); -}; - -const updateUidsInCollection = (_collection) => { - const collection = cloneDeep(_collection); - - collection.uid = uuid(); - - const updateItemUids = (items = []) => { - each(items, (item) => { - item.uid = uuid(); - - each(get(item, 'request.headers'), (header) => (header.uid = uuid())); - each(get(item, 'request.params'), (param) => (param.uid = uuid())); - each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid())); - each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid())); - - if (item.items && item.items.length) { - updateItemUids(item.items); - } - }); - }; - updateItemUids(collection.items); - - return collection; -}; - const importCollection = () => { return new Promise((resolve, reject) => { fileDialog({ accept: 'application/json' }) diff --git a/packages/bruno-app/src/utils/importers/common.js b/packages/bruno-app/src/utils/importers/common.js new file mode 100644 index 00000000..eee7108f --- /dev/null +++ b/packages/bruno-app/src/utils/importers/common.js @@ -0,0 +1,44 @@ + +import each from 'lodash/each'; +import get from 'lodash/get'; + +import cloneDeep from 'lodash/cloneDeep'; +import { uuid } from 'utils/common'; +import { collectionSchema } from '@usebruno/schema'; +import { BrunoError } from 'utils/common/error'; + +export const validateSchema = (collection = {}) => { + return new Promise((resolve, reject) => { + collectionSchema + .validate(collection) + .then(() => resolve(collection)) + .catch((err) => { + console.log(err); + reject(new BrunoError('The Collection file is corrupted')); + }); + }); +}; + +export const updateUidsInCollection = (_collection) => { + const collection = cloneDeep(_collection); + + collection.uid = uuid(); + + const updateItemUids = (items = []) => { + each(items, (item) => { + item.uid = uuid(); + + each(get(item, 'request.headers'), (header) => (header.uid = uuid())); + each(get(item, 'request.params'), (param) => (param.uid = uuid())); + each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid())); + each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid())); + + if (item.items && item.items.length) { + updateItemUids(item.items); + } + }); + }; + updateItemUids(collection.items); + + return collection; +}; diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js new file mode 100644 index 00000000..f910ff74 --- /dev/null +++ b/packages/bruno-app/src/utils/importers/postman-collection.js @@ -0,0 +1,187 @@ +import each from 'lodash/each'; +import get from 'lodash/get'; +import fileDialog from 'file-dialog'; +import { uuid } from 'utils/common'; +import { saveCollectionToIdb } from 'utils/idb'; +import { BrunoError } from 'utils/common/error'; +import { validateSchema, updateUidsInCollection } from './common'; + +const readFile = (files) => { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (e) => resolve(e.target.result); + fileReader.onerror = (err) => reject(err); + fileReader.readAsText(files[0]); + }); +}; + +const isItemAFolder = (item) => { + return !item.request; +}; + +const importPostmanV2CollectionItem = (brunoParent, item) => { + brunoParent.items = brunoParent.items || []; + + each(item, (i) => { + if(isItemAFolder(i)) { + const brunoFolderItem = { + uid: uuid(), + name: i.name, + type: 'folder', + items: [] + }; + brunoParent.items.push(brunoFolderItem); + if(i.item && i.item.length) { + importPostmanV2CollectionItem(brunoFolderItem, i.item); + } + } else { + if(i.request) { + const brunoRequestItem = { + uid: uuid(), + name: i.name, + type: 'http-request', + request: { + url: get(i, 'request.url.raw'), + method: i.request.method, + headers: [], + params: [], + body: { + mode: 'none', + json: null, + text: null, + xml: null, + formUrlEncoded: [], + multipartForm: [] + } + } + }; + + const bodyMode = get(i, 'request.body.mode'); + if(bodyMode) { + if(bodyMode === 'formdata') { + brunoRequestItem.request.body.mode = 'multipartForm'; + each(i.request.body.formdata, (param) => { + brunoRequestItem.request.body.formUrlEncoded.push({ + uid: uuid(), + name: param.key, + value: param.value, + description: param.description, + enabled: !param.disabled + }); + }); + } + + if(bodyMode === 'urlencoded') { + brunoRequestItem.request.body.mode = 'formUrlEncoded'; + each(i.request.body.urlencoded, (param) => { + brunoRequestItem.request.body.formUrlEncoded.push({ + uid: uuid(), + name: param.key, + value: param.value, + description: param.description, + enabled: !param.disabled + }); + }); + } + + if(bodyMode === 'raw') { + const language = get(i, 'request.body.options.raw.language'); + if(language === 'json') { + brunoRequestItem.request.body.mode = 'json'; + brunoRequestItem.request.body.json = i.request.body.raw; + } else if (language === 'xml') { + brunoRequestItem.request.body.mode = 'xml'; + brunoRequestItem.request.body.xml = i.request.body.raw; + } else { + brunoRequestItem.request.body.mode = 'text'; + brunoRequestItem.request.body.text = i.request.body.raw; + } + } + } + + each(i.request.header, (header) => { + brunoRequestItem.request.headers.push({ + uid: uuid(), + name: header.key, + value: header.value, + description: header.description, + enabled: !header.disabled + }); + }); + + each(get(i, 'request.url.query'), (param) => { + brunoRequestItem.request.params.push({ + uid: uuid(), + name: param.key, + value: param.value, + description: param.description, + enabled: !param.disabled + }); + }); + + brunoParent.items.push(brunoRequestItem); + } + } + }); +}; + +const importPostmanV2Collection = (collection) => { + const brunoCollection = { + name: collection.info.name, + uid: uuid(), + version: "1", + items: [], + environments: [] + }; + + importPostmanV2CollectionItem(brunoCollection, collection.item); + + return brunoCollection; +}; + + +const parsePostmanCollection = (str) => { + return new Promise((resolve, reject) => { + try { + let collection = JSON.parse(str); + let schema = get(collection, 'info.schema'); + + let v2Schemas = [ + 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json', + 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + ]; + + if(v2Schemas.includes(schema)) { + return resolve(importPostmanV2Collection(collection)); + } + + throw new BrunoError('Unknown postman schema'); + } catch (err) { + console.log(err); + if(err instanceof BrunoError) { + return reject(err); + } + + return reject(new BrunoError('Unable to parse the postman collection json file')); + } + }); +}; + +const importCollection = () => { + return new Promise((resolve, reject) => { + fileDialog({ accept: 'application/json' }) + .then(readFile) + .then(parsePostmanCollection) + .then(validateSchema) + .then(updateUidsInCollection) + .then(validateSchema) + .then((collection) => saveCollectionToIdb(window.__idb, collection)) + .then((collection) => resolve(collection)) + .catch((err) => { + console.log(err); + reject(new BrunoError('Import collection failed')); + }); + }); +}; + +export default importCollection;