Skip to content

Commit

Permalink
feature(postman_tests_scripts): automatic tests and scripts translati…
Browse files Browse the repository at this point in the history
…on from postman import (#1151)

* feature(postman_tests_scripts): automatic tests and scripts translation from postman import
---------

Co-authored-by: Baptiste POULAIN <[email protected]>
Co-authored-by: bpoulaindev <[email protected]>
  • Loading branch information
3 people authored Mar 13, 2024
1 parent 2cd0e06 commit 410eecc
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
bun.lockb
node_modules
yarn.lock
pnpm-lock.yaml
Expand Down
1 change: 1 addition & 0 deletions packages/bruno-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@fortawesome/react-fontawesome": "^0.1.16",
"@reduxjs/toolkit": "^1.8.0",
"@tabler/icons": "^1.46.0",
"@tailwindcss/forms": "^0.5.7",
"@tippyjs/react": "^4.2.6",
"@usebruno/common": "0.1.0",
"@usebruno/graphql-docs": "0.1.0",
Expand Down
80 changes: 66 additions & 14 deletions packages/bruno-app/src/components/Sidebar/ImportCollection/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import importBrunoCollection from 'utils/importers/bruno-collection';
import importPostmanCollection from 'utils/importers/postman-collection';
import importInsomniaCollection from 'utils/importers/insomnia-collection';
Expand All @@ -7,6 +7,13 @@ import { toastError } from 'utils/common/error';
import Modal from 'components/Modal';

const ImportCollection = ({ onClose, handleSubmit }) => {
const [options, setOptions] = useState({
enablePostmanTranslations: {
enabled: true,
label: 'Auto translate postman scripts',
subLabel: "When enabled, Bruno will try as best to translate the scripts from the imported collection to Bruno's format."
}
})
const handleImportBrunoCollection = () => {
importBrunoCollection()
.then((collection) => {
Expand All @@ -16,7 +23,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
};

const handleImportPostmanCollection = () => {
importPostmanCollection()
importPostmanCollection(options)
.then((collection) => {
handleSubmit(collection);
})
Expand All @@ -38,21 +45,66 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
})
.catch((err) => toastError(err, 'OpenAPI v3 Import collection failed'));
};

const toggleOptions = (event, optionKey) => {
setOptions({ ...options, [optionKey]: {
...options[optionKey],
enabled: !options[optionKey].enabled
} });
};
const CollectionButton = ({ children, className, onClick }) => {
return (
<button
type="button"
onClick={onClick}
className={`rounded bg-transparent px-2.5 py-1 text-xs font-semibold text-slate-900 dark:text-slate-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
${className}`}
>
{children}
</button>
)
}
return (
<Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div>
<div className="text-link hover:underline cursor-pointer" onClick={handleImportBrunoCollection}>
Bruno Collection
</div>
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportPostmanCollection}>
Postman Collection
</div>
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportInsomniaCollection}>
Insomnia Collection
<div className="flex flex-col">
<h3 className="text-sm">Select the type of your existing collection :</h3>
<div className="mt-4 grid grid-rows-2 grid-flow-col gap-2">
<CollectionButton onClick={handleImportBrunoCollection}>
Bruno Collection
</CollectionButton>
<CollectionButton onClick={handleImportPostmanCollection}>
Postman Collection
</CollectionButton>
<CollectionButton onClick={handleImportInsomniaCollection}>
Insomnia Collection
</CollectionButton>
<CollectionButton onClick={handleImportOpenapiCollection}>
OpenAPI V3 Spec
</CollectionButton>
</div>
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportOpenapiCollection}>
OpenAPI V3 Spec
<div className="flex justify-start w-full mt-4 max-w-[450px]">
{Object.entries(options || {}).map(([key, option]) => (
<div className="relative flex items-start">
<div className="flex h-6 items-center">
<input
id="comments"
aria-describedby="comments-description"
name="comments"
type="checkbox"
checked={option.enabled}
onChange={(e) => toggleOptions(e,key)}
className="h-3.5 w-3.5 rounded border-zinc-300 dark:ring-offset-zinc-800 bg-transparent text-indigo-600 dark:text-indigo-500 focus:ring-indigo-600 dark:focus:ring-indigo-500"
/>
</div>
<div className="ml-2 text-sm leading-6">
<label htmlFor="comments" className="font-medium text-gray-900 dark:text-zinc-50">
{option.label}
</label>
<p id="comments-description" className="text-zinc-500 text-xs dark:text-zinc-400">
{option.subLabel}
</p>
</div>
</div>
))}
</div>
</div>
</Modal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
const onSubmit = () => formik.handleSubmit();

return (
<Modal size="sm" title="Import Collection" confirmText="Import" handleConfirm={onSubmit} handleCancel={onClose}>
<Modal
size="sm"
title="Import Collection"
confirmText="Import"
handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="collectionName" className="block font-semibold">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const TitleBar = () => {
setImportCollectionLocationModalOpen(true);
};

const handleImportCollectionLocation = (collectionLocation) => {
const handleImportCollectionLocation = (collectionLocation, useTranslation) => {
dispatch(importCollection(importedCollection, collectionLocation));
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
Expand Down
4 changes: 2 additions & 2 deletions packages/bruno-app/src/components/Welcome/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const Welcome = () => {
setImportCollectionLocationModalOpen(true);
};

const handleImportCollectionLocation = (collectionLocation) => {
dispatch(importCollection(importedCollection, collectionLocation));
const handleImportCollectionLocation = (collectionLocation, enableTRanslation = true) => {
dispatch(importCollection(importedCollection, collectionLocation, enableTranslation));
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
toast.success('Collection imported successfully');
Expand Down
11 changes: 11 additions & 0 deletions packages/bruno-app/src/providers/Theme/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,31 @@ export const ThemeProvider = (props) => {
const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches;
const [displayedTheme, setDisplayedTheme] = useState(isBrowserThemeLight ? 'light' : 'dark');
const [storedTheme, setStoredTheme] = useLocalStorage('bruno.theme', 'system');
const toggleHtml = () => {
const html = document.querySelector('html');
if (html) {
html.classList.toggle('dark');
}
};

useEffect(() => {
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
if (storedTheme !== 'system') return;
setDisplayedTheme(e.matches ? 'light' : 'dark');
toggleHtml();
});
}, []);

useEffect(() => {
const root = window.document.documentElement
root.classList.remove("light", "dark");
if (storedTheme === 'system') {
const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches;
setDisplayedTheme(isBrowserThemeLight ? 'light' : 'dark');
root.classList.add(isBrowserThemeLight ? 'light' : 'dark')
} else {
setDisplayedTheme(storedTheme);
root.classList.add(storedTheme)
}
}, [storedTheme, setDisplayedTheme, window.matchMedia]);

Expand Down
2 changes: 1 addition & 1 deletion packages/bruno-app/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ body::-webkit-scrollbar-thumb,
border-radius: 5rem;
}

/*
/*
* todo: this will be supported in the future to be changed via applying a theme
* making all the checkboxes and radios bigger
* input[type='checkbox'],
Expand Down
33 changes: 21 additions & 12 deletions packages/bruno-app/src/utils/importers/postman-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fileDialog from 'file-dialog';
import { uuid } from 'utils/common';
import { BrunoError } from 'utils/common/error';
import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } from './common';
import { postmanTranslation } from 'utils/importers/translators/postman_translation';

const readFile = (files) => {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -53,7 +54,7 @@ const convertV21Auth = (array) => {
}, {});
};

const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => {
brunoParent.items = brunoParent.items || [];
const folderMap = {};

Expand All @@ -77,7 +78,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
brunoParent.items.push(brunoFolderItem);
folderMap[folderName] = brunoFolderItem;
if (i.item && i.item.length) {
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth);
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options);
}
} else {
if (i.request) {
Expand Down Expand Up @@ -121,19 +122,27 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
brunoRequestItem.request.script = {};
}
if (Array.isArray(event.script.exec)) {
brunoRequestItem.request.script.req = event.script.exec.map((line) => `// ${line}`).join('\n');
brunoRequestItem.request.script.req = event.script.exec
.map((line) => options.enablePostmanTranslations.enabled ?
postmanTranslation(line) : `// ${line}`).join('\n')
} else {
brunoRequestItem.request.script.req = `// ${event.script.exec[0]} `;
brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled ?
postmanTranslation(event.script.exec[0]) :
`// ${event.script.exec[0]} `;
}
}
if (event.listen === 'test' && event.script && event.script.exec) {
if (!brunoRequestItem.request.tests) {
brunoRequestItem.request.tests = {};
}
if (Array.isArray(event.script.exec)) {
brunoRequestItem.request.tests = event.script.exec.map((line) => `// ${line}`).join('\n');
brunoRequestItem.request.tests = event.script.exec
.map((line) => options.enablePostmanTranslations.enabled ?
postmanTranslation(line) : `// ${line}`).join('\n');
} else {
brunoRequestItem.request.tests = `// ${event.script.exec[0]} `;
brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled ?
postmanTranslation(event.script.exec[0]) :
`// ${event.script.exec[0]} `;
}
}
});
Expand Down Expand Up @@ -263,7 +272,7 @@ const searchLanguageByHeader = (headers) => {
return contentType;
};

const importPostmanV2Collection = (collection) => {
const importPostmanV2Collection = (collection, options) => {
const brunoCollection = {
name: collection.info.name,
uid: uuid(),
Expand All @@ -272,12 +281,12 @@ const importPostmanV2Collection = (collection) => {
environments: []
};

importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth);
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options);

return brunoCollection;
};

const parsePostmanCollection = (str) => {
const parsePostmanCollection = (str, options) => {
return new Promise((resolve, reject) => {
try {
let collection = JSON.parse(str);
Expand All @@ -289,7 +298,7 @@ const parsePostmanCollection = (str) => {
];

if (v2Schemas.includes(schema)) {
return resolve(importPostmanV2Collection(collection));
return resolve(importPostmanV2Collection(collection, options));
}

throw new BrunoError('Unknown postman schema');
Expand All @@ -304,11 +313,11 @@ const parsePostmanCollection = (str) => {
});
};

const importCollection = () => {
const importCollection = (options) => {
return new Promise((resolve, reject) => {
fileDialog({ accept: 'application/json' })
.then(readFile)
.then(parsePostmanCollection)
.then((str) => parsePostmanCollection(str, options))
.then(transformItemsInCollection)
.then(hydrateSeqInCollection)
.then(validateSchema)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const replacements = {
'pm\\.environment\\.get\\(([\'"])([^\'"]*)\\1\\)': 'bru.getEnvVar($1$2$1)',
'pm\\.environment\\.set\\(([\'"])([^\'"]*)\\1, ([\'"])([^\'"]*)\\3\\)': 'bru.setEnvVar($1$2$1, $3$4$3)',
'pm\\.variables\\.get\\(([\'"])([^\'"]*)\\1\\)': 'bru.getVar($1$2$1)',
'pm\\.variables\\.set\\(([\'"])([^\'"]*)\\1, ([\'"])([^\'"]*)\\3\\)': 'bru.setVar($1$2$1, $3$4$3)'
};

export const postmanTranslation = (script) => {
try {
const modifiedScript = Object.entries(replacements || {})
.map(([pattern, replacement]) => {
const regex = new RegExp(pattern, 'g');
return script?.replace(regex, replacement);
})
.find((modified) => modified !== script);
if (modifiedScript) {
// translation successful
return modifiedScript;
} else {
// non-translatable script
return script?.includes('pm.') ? `// ${script}` : script;
}
} catch (e) {
// non-translatable script
return script?.includes('pm.') ? `// ${script}` : script;
}
};
22 changes: 18 additions & 4 deletions packages/bruno-app/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
darkMode: ["class"],
content: [
'./pages/**/*.{js,jsx}',
'./components/**/*.{js,jsx}',
'./app/**/*.{js,jsx}',
'./src/**/*.{js,jsx}',
],
prefix: "",
theme: {
extend: {}
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {},
},
plugins: []
};
plugins: [require("@tailwindcss/forms")],
}
2 changes: 1 addition & 1 deletion packages/bruno-electron/src/ipc/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});

ipcMain.handle('renderer:import-collection', async (event, collection, collectionLocation) => {
ipcMain.handle('renderer:import-collection', async (event, collection, collectionLocation, enableTranslation) => {
try {
let collectionName = sanitizeDirectoryName(collection.name);
let collectionPath = path.join(collectionLocation, collectionName);
Expand Down

0 comments on commit 410eecc

Please sign in to comment.