mirror of
https://github.com/usebruno/bruno.git
synced 2025-06-27 07:21:51 +02:00
Merge pull request #544 from kapami/feature/export-to-postman-collection
Feature/export to postman collection
This commit is contained in:
commit
671fcecb38
@ -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 (
|
||||||
|
<Modal size="sm" title="Export Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||||
|
<div>
|
||||||
|
<div className="text-link hover:underline cursor-pointer" onClick={handleExportBrunoCollection}>
|
||||||
|
Bruno Collection
|
||||||
|
</div>
|
||||||
|
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleExportPostmanCollection}>
|
||||||
|
Postman Collection
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExportCollection;
|
@ -14,6 +14,7 @@ import NewRequest from 'components/Sidebar/NewRequest';
|
|||||||
import NewFolder from 'components/Sidebar/NewFolder';
|
import NewFolder from 'components/Sidebar/NewFolder';
|
||||||
import CollectionItem from './CollectionItem';
|
import CollectionItem from './CollectionItem';
|
||||||
import RemoveCollection from './RemoveCollection';
|
import RemoveCollection from './RemoveCollection';
|
||||||
|
import ExportCollection from './ExportCollection';
|
||||||
import CollectionProperties from './CollectionProperties';
|
import CollectionProperties from './CollectionProperties';
|
||||||
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
||||||
import { isItemAFolder, isItemARequest, transformCollectionToSaveToExportAsFile } from 'utils/collections';
|
import { isItemAFolder, isItemARequest, transformCollectionToSaveToExportAsFile } from 'utils/collections';
|
||||||
@ -26,6 +27,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
|
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
|
||||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||||
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
|
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
|
||||||
|
const [showExportCollectionModal, setShowExportCollectionModal] = useState(false);
|
||||||
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
|
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
|
||||||
const [collectionPropertiesModal, setCollectionPropertiesModal] = useState(false);
|
const [collectionPropertiesModal, setCollectionPropertiesModal] = useState(false);
|
||||||
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
|
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
|
||||||
@ -129,6 +131,9 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
{showRemoveCollectionModal && (
|
{showRemoveCollectionModal && (
|
||||||
<RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />
|
<RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />
|
||||||
)}
|
)}
|
||||||
|
{showExportCollectionModal && (
|
||||||
|
<ExportCollection collection={collection} onClose={() => setShowExportCollectionModal(false)} />
|
||||||
|
)}
|
||||||
{collectionPropertiesModal && (
|
{collectionPropertiesModal && (
|
||||||
<CollectionProperties collection={collection} onClose={() => setCollectionPropertiesModal(false)} />
|
<CollectionProperties collection={collection} onClose={() => setCollectionPropertiesModal(false)} />
|
||||||
)}
|
)}
|
||||||
@ -186,7 +191,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
menuDropdownTippyRef.current.hide();
|
menuDropdownTippyRef.current.hide();
|
||||||
handleExportClick(true);
|
setShowExportCollectionModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Export
|
Export
|
||||||
|
@ -2,7 +2,7 @@ import * as FileSaver from 'file-saver';
|
|||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
|
|
||||||
const deleteUidsInItems = (items) => {
|
export const deleteUidsInItems = (items) => {
|
||||||
each(items, (item) => {
|
each(items, (item) => {
|
||||||
delete item.uid;
|
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
|
* 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
|
* This function is used to transform the models to the Collection Json format
|
||||||
*/
|
*/
|
||||||
const transformItem = (items = []) => {
|
export const transformItem = (items = []) => {
|
||||||
each(items, (item) => {
|
each(items, (item) => {
|
||||||
if (['http-request', 'graphql-request'].includes(item.type)) {
|
if (['http-request', 'graphql-request'].includes(item.type)) {
|
||||||
item.request.query = item.request.params;
|
item.request.query = item.request.params;
|
||||||
@ -47,14 +47,14 @@ const transformItem = (items = []) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUidsInEnvs = (envs) => {
|
export const deleteUidsInEnvs = (envs) => {
|
||||||
each(envs, (env) => {
|
each(envs, (env) => {
|
||||||
delete env.uid;
|
delete env.uid;
|
||||||
each(env.variables, (variable) => delete variable.uid);
|
each(env.variables, (variable) => delete variable.uid);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteSecretsInEnvs = (envs) => {
|
export const deleteSecretsInEnvs = (envs) => {
|
||||||
each(envs, (env) => {
|
each(envs, (env) => {
|
||||||
each(env.variables, (variable) => {
|
each(env.variables, (variable) => {
|
||||||
if (variable.secret) {
|
if (variable.secret) {
|
||||||
@ -64,7 +64,7 @@ const deleteSecretsInEnvs = (envs) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportCollection = (collection) => {
|
export const exportCollection = (collection) => {
|
||||||
// delete uids
|
// delete uids
|
||||||
delete collection.uid;
|
delete collection.uid;
|
||||||
deleteUidsInItems(collection.items);
|
deleteUidsInItems(collection.items);
|
||||||
|
214
packages/bruno-app/src/utils/exporters/postman-collection.js
Normal file
214
packages/bruno-app/src/utils/exporters/postman-collection.js
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import map from 'lodash/map';
|
||||||
|
import * as FileSaver from 'file-saver';
|
||||||
|
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 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) {
|
||||||
|
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 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: itemRequest.url,
|
||||||
|
auth: generateAuth(itemRequest.auth)
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default exportCollection;
|
Loading…
x
Reference in New Issue
Block a user