mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-21 23:43:15 +01:00
feat: import collections
This commit is contained in:
parent
6feca9937e
commit
d546709b26
@ -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",
|
||||
|
@ -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 (
|
||||
<StyledWrapper className="px-2 py-2">
|
||||
{createCollectionModalOpen ? (
|
||||
@ -85,6 +96,7 @@ const TitleBar = () => {
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
handleImportCollection();
|
||||
}}>
|
||||
Import Collection
|
||||
</div>
|
||||
|
@ -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 (
|
||||
<StyledWrapper className="pb-4 px-6 mt-6">
|
||||
{createCollectionModalOpen ? (
|
||||
@ -73,7 +84,7 @@ const Welcome = () => {
|
||||
<div className="flex items-center ml-6">
|
||||
<IconFiles size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setAddCollectionToWSModalOpen(true)}>Add Collection to Workspace</span>
|
||||
</div>
|
||||
<div className="flex items-center ml-6">
|
||||
<div className="flex items-center ml-6" onClick={handleImportCollection}>
|
||||
<IconUpload size={18} strokeWidth={2}/><span className="label ml-2">Import Collection</span>
|
||||
</div>
|
||||
<div className="flex items-center ml-6">
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
|
86
packages/bruno-app/src/utils/collections/import.js
Normal file
86
packages/bruno-app/src/utils/collections/import.js
Normal file
@ -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;
|
@ -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);
|
||||
}
|
||||
|
||||
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));
|
||||
|
Loading…
Reference in New Issue
Block a user