From 6cbdbad09d70846dcbb56d4ab3bfc3ec0cd5f7af Mon Sep 17 00:00:00 2001 From: Miroslav Kapa Date: Wed, 11 Oct 2023 18:20:36 +0200 Subject: [PATCH 1/3] UI addition for Postman export,partial generator --- .../Collection/ExportCollection/index.js | 37 +++++ .../Sidebar/Collections/Collection/index.js | 8 +- .../bruno-app/src/utils/collections/export.js | 10 +- .../src/utils/exporters/postman-collection.js | 151 ++++++++++++++++++ 4 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/ExportCollection/index.js create mode 100644 packages/bruno-app/src/utils/exporters/postman-collection.js diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/ExportCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/ExportCollection/index.js new file mode 100644 index 0000000000..0a1c7cd8a0 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/ExportCollection/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import exportBrunoCollection from 'utils/collections/export'; +import exportPostmanCollection from 'utils/exporters/postman-collection'; +import { toastError } from 'utils/common/error'; +import cloneDeep from 'lodash/cloneDeep'; +import Modal from 'components/Modal'; +import { transformCollectionToSaveToExportAsFile } from 'utils/collections/index'; + +const ExportCollection = ({ onClose, collection }) => { + const handleExportBrunoCollection = () => { + const collectionCopy = cloneDeep(collection); + exportBrunoCollection(transformCollectionToSaveToExportAsFile(collectionCopy)); + onClose(); + }; + + const handleExportPostmanCollection = () => { + const collectionCopy = cloneDeep(collection); + exportPostmanCollection(collectionCopy); + // exportPostmanCollection(transformCollectionToSaveToExportAsFile(collectionCopy)); + onClose(); + }; + + return ( + +
+
+ Bruno Collection +
+
+ Postman Collection +
+
+
+ ); +}; + +export default ExportCollection; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index b150ade8c3..2f7b876a5d 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -14,6 +14,7 @@ import NewRequest from 'components/Sidebar/NewRequest'; import NewFolder from 'components/Sidebar/NewFolder'; import CollectionItem from './CollectionItem'; import RemoveCollection from './RemoveCollection'; +import ExportCollection from './ExportCollection'; import CollectionProperties from './CollectionProperties'; import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search'; import { isItemAFolder, isItemARequest, transformCollectionToSaveToExportAsFile } from 'utils/collections'; @@ -26,6 +27,7 @@ const Collection = ({ collection, searchText }) => { const [showNewFolderModal, setShowNewFolderModal] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false); + const [showExportCollectionModal, setShowExportCollectionModal] = useState(false); const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false); const [collectionPropertiesModal, setCollectionPropertiesModal] = useState(false); const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed); @@ -68,6 +70,7 @@ const Collection = ({ collection, searchText }) => { }; const handleExportClick = () => { + //EXPORT KOLEKCIE const collectionCopy = cloneDeep(collection); exportCollection(transformCollectionToSaveToExportAsFile(collectionCopy)); }; @@ -115,6 +118,9 @@ const Collection = ({ collection, searchText }) => { {showRemoveCollectionModal && ( setShowRemoveCollectionModal(false)} /> )} + {showExportCollectionModal && ( + setShowExportCollectionModal(false)} /> + )} {collectionPropertiesModal && ( setCollectionPropertiesModal(false)} /> )} @@ -172,7 +178,7 @@ const Collection = ({ collection, searchText }) => { className="dropdown-item" onClick={(e) => { menuDropdownTippyRef.current.hide(); - handleExportClick(true); + setShowExportCollectionModal(true); }} > Export diff --git a/packages/bruno-app/src/utils/collections/export.js b/packages/bruno-app/src/utils/collections/export.js index 64fc0da914..e5a68bc66a 100644 --- a/packages/bruno-app/src/utils/collections/export.js +++ b/packages/bruno-app/src/utils/collections/export.js @@ -2,7 +2,7 @@ import * as FileSaver from 'file-saver'; import get from 'lodash/get'; import each from 'lodash/each'; -const deleteUidsInItems = (items) => { +export const deleteUidsInItems = (items) => { each(items, (item) => { delete item.uid; @@ -26,7 +26,7 @@ const deleteUidsInItems = (items) => { * Some of the models in the app are not consistent with the Collection Json format * This function is used to transform the models to the Collection Json format */ -const transformItem = (items = []) => { +export const transformItem = (items = []) => { each(items, (item) => { if (['http-request', 'graphql-request'].includes(item.type)) { item.request.query = item.request.params; @@ -47,14 +47,14 @@ const transformItem = (items = []) => { }); }; -const deleteUidsInEnvs = (envs) => { +export const deleteUidsInEnvs = (envs) => { each(envs, (env) => { delete env.uid; each(env.variables, (variable) => delete variable.uid); }); }; -const deleteSecretsInEnvs = (envs) => { +export const deleteSecretsInEnvs = (envs) => { each(envs, (env) => { each(env.variables, (variable) => { if (variable.secret) { @@ -64,7 +64,7 @@ const deleteSecretsInEnvs = (envs) => { }); }; -const exportCollection = (collection) => { +export const exportCollection = (collection) => { // delete uids delete collection.uid; deleteUidsInItems(collection.items); diff --git a/packages/bruno-app/src/utils/exporters/postman-collection.js b/packages/bruno-app/src/utils/exporters/postman-collection.js new file mode 100644 index 0000000000..38fc6fbf72 --- /dev/null +++ b/packages/bruno-app/src/utils/exporters/postman-collection.js @@ -0,0 +1,151 @@ +import { BrunoError } from 'utils/common/error'; +import map from 'lodash/map'; +import { deleteSecretsInEnvs, deleteUidsInEnvs, deleteUidsInItems } from 'utils/collections/export'; + +export const exportCollection = (collection) => { + delete collection.uid; + deleteUidsInItems(collection.items); + deleteUidsInEnvs(collection.environments); + deleteSecretsInEnvs(collection.environments); + + const generateInfoSection = () => { + return { + name: collection.name, + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }; + }; + + const generateEventSection = (item) => { + const eventArray = []; + if (item.request.tests.length) { + eventArray.push({ + listen: 'test', + script: { + exec: item.request.tests.split('\n') + // type: 'text/javascript' + } + }); + } + if (item.request.script.req) { + eventArray.push({ + listen: 'prerequest', + script: { + exec: item.request.script.req.split('\n') + // type: 'text/javascript' + } + }); + } + return eventArray; + }; + + const generateHeaders = (headersArray) => { + return map(headersArray, (item) => { + return { + key: item.name, + value: item.value, + disabled: !item.enabled, + type: 'default' + }; + }); + }; + + const generateBody = (body) => { + switch (body.mode) { + case 'formUrlEncoded': + return { + mode: 'urlencoded', + urlencoded: map(body.formUrlEncoded, (bodyItem) => { + return { + key: bodyItem.name, + value: bodyItem.value, + disabled: !bodyItem.enabled, + type: 'default' + }; + }) + }; + case 'multipartForm': + return { + mode: 'formdata', + formdata: map(body.multipartForm, (bodyItem) => { + return { + key: bodyItem.name, + value: bodyItem.value, + disabled: !bodyItem.enabled, + type: 'default' + }; + }) + }; + case 'json': + return { + mode: 'raw', + raw: body.json, + options: { + raw: { + language: 'json' + } + } + }; + case 'xml': + return { + mode: 'raw', + raw: body.xml, + options: { + raw: { + language: 'xml' + } + } + }; + case 'text': + return { + mode: 'raw', + raw: body.text, + options: { + raw: { + language: 'text' + } + } + }; + } + }; + + const generateRequestSection = (itemRequest) => { + const requestObject = { + method: itemRequest.method, + header: generateHeaders(itemRequest.headers), + url: { + raw: itemRequest.url, + protocol: itemRequest.url.split('://')[0] + } + // host: TODO + // path: TODO + }; + + if (itemRequest.body.mode != 'none') { + requestObject.body = generateBody(itemRequest.body); + } + return requestObject; + }; + + const generateItemSection = (itemsArray) => { + return map(itemsArray, (item) => { + if (item.type === 'folder') { + return { + name: item.name, + item: item.items.length ? generateItemSection(item.items) : [] + }; + } else { + return { + name: item.name, + event: generateEventSection(item), + request: generateRequestSection(item.request) + }; + } + }); + }; + const collectionToExport = {}; + collectionToExport.info = generateInfoSection(); + collectionToExport.item = generateItemSection(collection.items); + console.log(collectionToExport); +}; + +export default exportCollection; From 542735e22097f7c8d70443f171cf2d098c4776ba Mon Sep 17 00:00:00 2001 From: Miroslav Kapa Date: Thu, 12 Oct 2023 00:30:58 +0200 Subject: [PATCH 2/3] added file saving + added export of collection var --- .../src/utils/exporters/postman-collection.js | 80 +++++++++++++++++-- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/packages/bruno-app/src/utils/exporters/postman-collection.js b/packages/bruno-app/src/utils/exporters/postman-collection.js index 38fc6fbf72..01b64e58c3 100644 --- a/packages/bruno-app/src/utils/exporters/postman-collection.js +++ b/packages/bruno-app/src/utils/exporters/postman-collection.js @@ -1,5 +1,5 @@ -import { BrunoError } from 'utils/common/error'; import map from 'lodash/map'; +import * as FileSaver from 'file-saver'; import { deleteSecretsInEnvs, deleteUidsInEnvs, deleteUidsInItems } from 'utils/collections/export'; export const exportCollection = (collection) => { @@ -15,6 +15,37 @@ export const exportCollection = (collection) => { }; }; + const generateCollectionVars = (collection) => { + const pattern = /{{[^{}]+}}/g; + let listOfVars = []; + + const findOccurrences = (obj, results) => { + if (typeof obj === 'object') { + if (Array.isArray(obj)) { + obj.forEach((item) => findOccurrences(item, results)); + } else { + for (const key in obj) { + findOccurrences(obj[key], results); + } + } + } else if (typeof obj === 'string') { + obj.replace(pattern, (match) => { + results.push(match.replace(/{{|}}/g, '')); + }); + } + }; + + findOccurrences(collection, listOfVars); + + const finalArrayOfVars = [...new Set(listOfVars)]; + + return finalArrayOfVars.map((variable) => ({ + key: variable, + value: '', + type: 'default' + })); + }; + const generateEventSection = (item) => { const eventArray = []; if (item.request.tests.length) { @@ -108,16 +139,43 @@ export const exportCollection = (collection) => { } }; + const generateAuth = (itemAuth) => { + switch (itemAuth) { + case 'bearer': + return { + type: 'bearer', + bearer: { + key: 'token', + value: itemAuth.bearer.token, + type: 'string' + } + }; + case 'basic': { + return { + type: 'basic', + basic: [ + { + key: 'password', + value: itemAuth.basic.password, + type: 'string' + }, + { + key: 'username', + value: itemAuth.basic.username, + type: 'string' + } + ] + }; + } + } + }; + const generateRequestSection = (itemRequest) => { const requestObject = { method: itemRequest.method, header: generateHeaders(itemRequest.headers), - url: { - raw: itemRequest.url, - protocol: itemRequest.url.split('://')[0] - } - // host: TODO - // path: TODO + url: itemRequest.url, + auth: generateAuth(itemRequest.auth) }; if (itemRequest.body.mode != 'none') { @@ -145,7 +203,13 @@ export const exportCollection = (collection) => { const collectionToExport = {}; collectionToExport.info = generateInfoSection(); collectionToExport.item = generateItemSection(collection.items); - console.log(collectionToExport); + collectionToExport.variable = generateCollectionVars(collection); + + const fileName = `${collection.name}.json`; + const fileBlob = new Blob([JSON.stringify(collection, null, 2)], { type: 'application/json' }); + + FileSaver.saveAs(fileBlob, fileName); + // console.log(collectionToExport); }; export default exportCollection; From 6e246410d5a7ff58994023a7fabf389ef9d1b654 Mon Sep 17 00:00:00 2001 From: Miroslav Kapa Date: Thu, 12 Oct 2023 00:38:05 +0200 Subject: [PATCH 3/3] remove of garbage --- .../src/components/Sidebar/Collections/Collection/index.js | 1 - packages/bruno-app/src/utils/exporters/postman-collection.js | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index 63c48873c4..37683d6ee8 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -84,7 +84,6 @@ const Collection = ({ collection, searchText }) => { }; const handleExportClick = () => { - //EXPORT KOLEKCIE const collectionCopy = cloneDeep(collection); exportCollection(transformCollectionToSaveToExportAsFile(collectionCopy)); }; diff --git a/packages/bruno-app/src/utils/exporters/postman-collection.js b/packages/bruno-app/src/utils/exporters/postman-collection.js index 01b64e58c3..36bbff7477 100644 --- a/packages/bruno-app/src/utils/exporters/postman-collection.js +++ b/packages/bruno-app/src/utils/exporters/postman-collection.js @@ -209,7 +209,6 @@ export const exportCollection = (collection) => { const fileBlob = new Blob([JSON.stringify(collection, null, 2)], { type: 'application/json' }); FileSaver.saveAs(fileBlob, fileName); - // console.log(collectionToExport); }; export default exportCollection;