From d546709b26ae41b863e2e3c83f1813393f149738 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 15 Oct 2022 04:04:45 +0530 Subject: [PATCH] feat: import collections --- packages/bruno-app/package.json | 1 + .../src/components/Sidebar/TitleBar/index.js | 12 +++ .../bruno-app/src/components/Welcome/index.js | 13 ++- .../bruno-app/src/providers/App/useIdb.js | 2 +- .../ReduxStore/slices/collections/actions.js | 12 ++- .../ReduxStore/slices/collections/index.js | 16 ++-- .../bruno-app/src/utils/collections/import.js | 86 +++++++++++++++++++ packages/bruno-app/src/utils/idb/index.js | 14 +-- 8 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 packages/bruno-app/src/utils/collections/import.js diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 7ec6ca91..2b151196 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -19,6 +19,7 @@ "codemirror": "^5.65.2", "codemirror-graphql": "^1.2.5", "escape-html": "^1.0.3", + "file-dialog": "^0.0.8", "file-saver": "^2.0.5", "formik": "^2.2.9", "graphiql": "^1.5.9", diff --git a/packages/bruno-app/src/components/Sidebar/TitleBar/index.js b/packages/bruno-app/src/components/Sidebar/TitleBar/index.js index 870a72a0..e82ee3e5 100644 --- a/packages/bruno-app/src/components/Sidebar/TitleBar/index.js +++ b/packages/bruno-app/src/components/Sidebar/TitleBar/index.js @@ -3,12 +3,14 @@ import toast from 'react-hot-toast'; import Dropdown from 'components/Dropdown'; import Bruno from 'components/Bruno'; import { useSelector, useDispatch } from 'react-redux'; +import { collectionImported } from 'providers/ReduxStore/slices/collections'; import { createCollection } from 'providers/ReduxStore/slices/collections/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { showHomePage } from 'providers/ReduxStore/slices/app'; import { IconDots } from '@tabler/icons'; import CreateCollection from '../CreateCollection'; import SelectCollection from 'components/Sidebar/Collections/SelectCollection'; +import importCollection from 'utils/collections/import'; import StyledWrapper from './StyledWrapper'; const TitleBar = () => { @@ -47,6 +49,15 @@ 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 ? ( @@ -85,6 +96,7 @@ const TitleBar = () => {
{ menuDropdownTippyRef.current.hide(); + handleImportCollection(); }}> Import Collection
diff --git a/packages/bruno-app/src/components/Welcome/index.js b/packages/bruno-app/src/components/Welcome/index.js index 1d1526d1..45d98702 100644 --- a/packages/bruno-app/src/components/Welcome/index.js +++ b/packages/bruno-app/src/components/Welcome/index.js @@ -11,11 +11,13 @@ import { IconDeviceDesktop } from '@tabler/icons'; import { useSelector, useDispatch } from 'react-redux'; +import { collectionImported } from 'providers/ReduxStore/slices/collections'; import { createCollection } from 'providers/ReduxStore/slices/collections/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import Bruno from 'components/Bruno'; import CreateCollection from 'components/Sidebar/CreateCollection'; import SelectCollection from 'components/Sidebar/Collections/SelectCollection'; +import importCollection from 'utils/collections/import'; import StyledWrapper from './StyledWrapper'; const Welcome = () => { @@ -42,6 +44,15 @@ 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)); + }; + return ( {createCollectionModalOpen ? ( @@ -73,7 +84,7 @@ const Welcome = () => {
setAddCollectionToWSModalOpen(true)}>Add Collection to Workspace
-
+
Import Collection
diff --git a/packages/bruno-app/src/providers/App/useIdb.js b/packages/bruno-app/src/providers/App/useIdb.js index 70bf7474..98545d39 100644 --- a/packages/bruno-app/src/providers/App/useIdb.js +++ b/packages/bruno-app/src/providers/App/useIdb.js @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { openDB } from 'idb'; import { idbConnectionReady } from 'providers/ReduxStore/slices/app' -import { loadCollectionsFromIdb } from 'providers/ReduxStore/slices/collections' +import { loadCollectionsFromIdb } from 'providers/ReduxStore/slices/collections/actions' import { loadWorkspacesFromIdb } from 'providers/ReduxStore/slices/workspaces/actions' import { useDispatch } from 'react-redux'; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 6408c53a..a495027b 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -1,4 +1,5 @@ import axios from 'axios'; +import toast from 'react-hot-toast'; import { uuid } from 'utils/common'; import cloneDeep from 'lodash/cloneDeep'; import { @@ -13,10 +14,11 @@ import { import { collectionSchema } from '@usebruno/schema'; import { waitForNextTick } from 'utils/common'; import cancelTokens, { saveCancelToken, deleteCancelToken } from 'utils/network/cancelTokens'; -import { saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb'; +import { getCollectionsFromIdb, saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb'; import { sendNetworkRequest } from 'utils/network'; import { + loadCollections, requestSent, requestCancelled, responseReceived, @@ -33,6 +35,14 @@ import { import { closeTabs, addTab } from 'providers/ReduxStore/slices/tabs'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; +export const loadCollectionsFromIdb = () => (dispatch) => { + getCollectionsFromIdb(window.__idb) + .then((collections) => dispatch(loadCollections({ + collections: collections + }))) + .catch(() => toast.error("Error occured while loading collections from IndexedDB")); +}; + export const createCollection = (collectionName) => (dispatch, getState) => { const newCollection = { uid: uuid(), diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 304fa846..9d1c56c8 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -15,7 +15,6 @@ import { isItemARequest } from 'utils/collections'; import { parseQueryParams, stringifyQueryParams } from 'utils/url'; -import { getCollectionsFromIdb } from 'utils/idb'; // todo: errors should be tracked in each slice and displayed as toasts @@ -32,6 +31,12 @@ export const collectionsSlice = createSlice({ each(action.payload.collections, (c) => addDepth(c.items)); state.collections = action.payload.collections; }, + collectionImported: (state, action) => { + const { collection } = action.payload; + collapseCollection(collection); + addDepth(collection.items); + state.collections.push(collection); + }, createCollection: (state, action) => { state.collections.push(action.payload); }, @@ -538,6 +543,7 @@ export const collectionsSlice = createSlice({ }); export const { + collectionImported, createCollection, renameCollection, deleteCollection, @@ -571,12 +577,4 @@ export const { updateRequestMethod } = collectionsSlice.actions; -export const loadCollectionsFromIdb = () => (dispatch) => { - getCollectionsFromIdb(window.__idb) - .then((collections) => dispatch(loadCollections({ - collections: collections - }))) - .catch((err) => console.log(err)); -}; - export default collectionsSlice.reducer; diff --git a/packages/bruno-app/src/utils/collections/import.js b/packages/bruno-app/src/utils/collections/import.js new file mode 100644 index 00000000..bc21649e --- /dev/null +++ b/packages/bruno-app/src/utils/collections/import.js @@ -0,0 +1,86 @@ +import each from 'lodash/each'; +import get from 'lodash/get'; +import fileDialog from 'file-dialog'; +import toast from 'react-hot-toast'; +import { uuid } from 'utils/common'; +import { collectionSchema } from '@usebruno/schema'; +import { saveCollectionToIdb } from 'utils/idb'; + +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 parseJsonCollection = (str) => { + return new Promise((resolve, reject) => { + try { + let parsed = JSON.parse(str); + return resolve(parsed); + } catch (err) { + toast.error("Unable to parse the collection json file"); + reject(err); + } + }); +}; + +const validateSchema = (collection = {}) => { + collection.uid = uuid(); + return new Promise((resolve, reject) => { + collectionSchema + .validate(collection) + .then(() => resolve(collection)) + .catch((err) => { + toast.error("The Collection file is corrupted"); + reject(err); + }); + }); +}; + + +const updateUidsInCollection = (collection) => { + collection.uid = uuid(); + + const updateItemUids = (items = []) => { + each(items, (item) => { + item.uid = uuid(); + + each(get(item, 'headers'), (header) => header.uid = uuid()); + each(get(item, 'params'), (param) => param.uid = uuid()); + each(get(item, 'body.multipartForm'), (param) => param.uid = uuid()); + each(get(item, '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'}) + .then(readFile) + .then(parseJsonCollection) + .then(validateSchema) + .then(updateUidsInCollection) + .then(validateSchema) + .then((collection) => saveCollectionToIdb(window.__idb, collection)) + .then((collection) => { + toast.success("Collection imported successfully"); + resolve(collection); + }) + .catch((err) => { + toast.error("Import collection failed"); + reject(err); + }); + }); +}; + +export default importCollection; \ No newline at end of file diff --git a/packages/bruno-app/src/utils/idb/index.js b/packages/bruno-app/src/utils/idb/index.js index f90b1afc..756efaf1 100644 --- a/packages/bruno-app/src/utils/idb/index.js +++ b/packages/bruno-app/src/utils/idb/index.js @@ -1,5 +1,3 @@ -import isArray from 'lodash/isArray'; - export const saveCollectionToIdb = (connection, collection) => { return new Promise((resolve, reject) => { connection @@ -7,15 +5,9 @@ export const saveCollectionToIdb = (connection, collection) => { let tx = db.transaction(`collection`, 'readwrite'); let collectionStore = tx.objectStore('collection'); - if(isArray(collection)) { - for(let c of collection) { - collectionStore.put(c); - } - } else { - collectionStore.put(collection); - } + collectionStore.put(collection); - resolve(); + resolve(collection); }) .catch((err) => reject(err)); }); @@ -28,7 +20,7 @@ export const deleteCollectionInIdb = (connection, collectionUid) => { let tx = db.transaction(`collection`, 'readwrite'); tx.objectStore('collection').delete(collectionUid); - tx.oncomplete = () => resolve(); + tx.oncomplete = () => resolve(collectionUid); tx.onerror = () => reject(tx.error); }) .catch((err) => reject(err));