From 481486cd1cdb5bab128688daacc48299be2ddebf Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 30 Oct 2022 02:00:54 +0530 Subject: [PATCH] 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;