forked from extern/bruno
Merge pull request #280 from Beedhan/feature/code-generator
Feature/code generator
This commit is contained in:
commit
4603ec4d5e
@ -30,6 +30,7 @@
|
||||
"graphiql": "^1.5.9",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"httpsnippet": "^3.0.1",
|
||||
"idb": "^7.0.0",
|
||||
"immer": "^9.0.15",
|
||||
"know-your-http-well": "^0.5.0",
|
||||
|
@ -119,7 +119,7 @@ export default class CodeEditor extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<StyledWrapper
|
||||
className="h-full"
|
||||
className="h-full w-full"
|
||||
aria-label="Code Editor"
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
|
@ -0,0 +1,21 @@
|
||||
import CodeEditor from 'components/CodeEditor/index';
|
||||
import HTTPSnippet from 'httpsnippet';
|
||||
import { useTheme } from 'providers/Theme/index';
|
||||
import { buildHarRequest } from 'utils/codegenerator/har';
|
||||
|
||||
const index = ({ language, item }) => {
|
||||
const { target, client, language: lang } = language;
|
||||
const snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client);
|
||||
const { storedTheme } = useTheme();
|
||||
return (
|
||||
<CodeEditor
|
||||
readOnly
|
||||
// value={JSON.stringify(item, null, 2)}
|
||||
value={snippet}
|
||||
theme={storedTheme}
|
||||
mode={lang}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default index;
|
@ -0,0 +1,39 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
margin-inline: -1rem;
|
||||
margin-block: -1.5rem;
|
||||
position: absolute;
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.bg};
|
||||
|
||||
.generate-code-sidebar {
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
|
||||
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.generate-code-item {
|
||||
min-width: 150px;
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 8px 10px;
|
||||
border-left: solid 2px transparent;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg};
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important;
|
||||
border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border};
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
@ -0,0 +1,94 @@
|
||||
import Modal from 'components/Modal/index';
|
||||
import { useState } from 'react';
|
||||
import CodeView from './CodeView';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ErrorBoundary from 'src/pages/ErrorBoundary/index';
|
||||
import { isValidUrl } from 'utils/url/index';
|
||||
|
||||
const languages = [
|
||||
{
|
||||
name: 'HTTP',
|
||||
target: 'http',
|
||||
client: 'http1.1'
|
||||
},
|
||||
{
|
||||
name: 'JavaScript-Fetch',
|
||||
target: 'javascript',
|
||||
client: 'fetch'
|
||||
},
|
||||
{
|
||||
name: 'Javascript-jQuery',
|
||||
target: 'javascript',
|
||||
client: 'jquery'
|
||||
},
|
||||
{
|
||||
name: 'Javascript-axios',
|
||||
target: 'javascript',
|
||||
client: 'axios'
|
||||
},
|
||||
{
|
||||
name: 'Python-Python3',
|
||||
target: 'python',
|
||||
client: 'python3'
|
||||
},
|
||||
{
|
||||
name: 'Python-Requests',
|
||||
target: 'python',
|
||||
client: 'requests'
|
||||
},
|
||||
{
|
||||
name: 'PHP',
|
||||
target: 'php',
|
||||
client: 'curl'
|
||||
},
|
||||
{
|
||||
name: 'Shell-curl',
|
||||
target: 'shell',
|
||||
client: 'curl'
|
||||
},
|
||||
{
|
||||
name: 'Shell-httpie',
|
||||
target: 'shell',
|
||||
client: 'httpie'
|
||||
}
|
||||
];
|
||||
const index = ({ item, onClose }) => {
|
||||
const [selectedLanguage, setSelectedLanguage] = useState(languages[0]);
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
|
||||
<div className="flex">
|
||||
<div>
|
||||
<div className="generate-code-sidebar">
|
||||
{languages &&
|
||||
languages.length &&
|
||||
languages.map((language) => (
|
||||
<div
|
||||
key={language.name}
|
||||
className={
|
||||
language.name === selectedLanguage.name ? 'generate-code-item active' : 'generate-code-item'
|
||||
}
|
||||
onClick={() => setSelectedLanguage(language)}
|
||||
>
|
||||
<span className="capitalize">{language.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{isValidUrl(item.request.url) ? (
|
||||
<CodeView language={selectedLanguage} item={item} />
|
||||
) : (
|
||||
<div className="flex flex-col justify-center items-center w-full">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold">Invalid URL</h1>
|
||||
<p className="text-gray-500">Please check the URL and try again</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default index;
|
@ -16,6 +16,7 @@ import RenameCollectionItem from './RenameCollectionItem';
|
||||
import CloneCollectionItem from './CloneCollectionItem';
|
||||
import DeleteCollectionItem from './DeleteCollectionItem';
|
||||
import RunCollectionItem from './RunCollectionItem';
|
||||
import GenerateCodeItem from './GenerateCodeItem';
|
||||
import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
|
||||
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
|
||||
import { getDefaultRequestPaneTab } from 'utils/collections';
|
||||
@ -32,6 +33,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
|
||||
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
|
||||
const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false);
|
||||
const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false);
|
||||
const [newRequestModalOpen, setNewRequestModalOpen] = useState(false);
|
||||
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
|
||||
const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false);
|
||||
@ -166,6 +168,9 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
{runCollectionModalOpen && (
|
||||
<RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />
|
||||
)}
|
||||
{generateCodeItemModalOpen && (
|
||||
<GenerateCodeItem item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
|
||||
)}
|
||||
<div className={itemRowClassName} ref={(node) => drag(drop(node))}>
|
||||
<div className="flex items-center h-full w-full">
|
||||
{indents && indents.length
|
||||
@ -264,6 +269,18 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
Clone
|
||||
</div>
|
||||
)}
|
||||
{!isFolder && item.type === 'http-request' && (
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
dropdownTippyRef.current.hide();
|
||||
setGenerateCodeItemModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Generate Code
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="dropdown-item delete-item"
|
||||
onClick={(e) => {
|
||||
|
86
packages/bruno-app/src/utils/codegenerator/har.js
Normal file
86
packages/bruno-app/src/utils/codegenerator/har.js
Normal file
@ -0,0 +1,86 @@
|
||||
const createContentType = (mode) => {
|
||||
switch (mode) {
|
||||
case 'json':
|
||||
return 'application/json';
|
||||
case 'xml':
|
||||
return 'application/xml';
|
||||
case 'formUrlEncoded':
|
||||
return 'application/x-www-form-urlencoded';
|
||||
case 'multipartForm':
|
||||
return 'multipart/form-data';
|
||||
default:
|
||||
return 'application/json';
|
||||
}
|
||||
};
|
||||
|
||||
const createHeaders = (headers, mode) => {
|
||||
const contentType = createContentType(mode);
|
||||
const headersArray = headers
|
||||
.filter((header) => header.enabled)
|
||||
.map((header) => {
|
||||
return {
|
||||
name: header.name,
|
||||
value: header.value
|
||||
};
|
||||
});
|
||||
const headerNames = headersArray.map((header) => header.name);
|
||||
if (!headerNames.includes('Content-Type')) {
|
||||
return [...headersArray, { name: 'Content-Type', value: contentType }];
|
||||
}
|
||||
return headersArray;
|
||||
};
|
||||
|
||||
const createQuery = (url) => {
|
||||
const params = new URLSearchParams(url);
|
||||
return params.forEach((value, name) => {
|
||||
return {
|
||||
name,
|
||||
value
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const createPostData = (body) => {
|
||||
const contentType = createContentType(body.mode);
|
||||
if (body.mode === 'formUrlEncoded' || body.mode === 'multipartForm') {
|
||||
return {
|
||||
mimeType: contentType,
|
||||
params: body[body.mode]
|
||||
.filter((param) => param.enabled)
|
||||
.map((param) => ({ name: param.name, value: param.value }))
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
mimeType: contentType,
|
||||
text: body[body.mode]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const createUrl = (request) => {
|
||||
let url = request.url;
|
||||
const variablePattern = /\{\{([^}]+)\}\}/g;
|
||||
const variables = request.url.match(variablePattern);
|
||||
if (variables) {
|
||||
variables.forEach((variable) => {
|
||||
const variableName = variable.replaceAll('{', '').replaceAll('}', '');
|
||||
const variableValue = request.vars.req.find((v) => v.name === variableName).value;
|
||||
url = url.replace(variable, variableValue);
|
||||
});
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const buildHarRequest = (request) => {
|
||||
return {
|
||||
method: request.method,
|
||||
url: createUrl(request),
|
||||
httpVersion: 'HTTP/1.1',
|
||||
cookies: [],
|
||||
headers: createHeaders(request.headers, request.body.mode),
|
||||
queryString: createQuery(request.url),
|
||||
postData: createPostData(request.body),
|
||||
headersSize: 0,
|
||||
bodySize: 0
|
||||
};
|
||||
};
|
@ -53,3 +53,12 @@ export const splitOnFirst = (str, char) => {
|
||||
|
||||
return [str.slice(0, index), str.slice(index + 1)];
|
||||
};
|
||||
|
||||
export const isValidUrl = (url) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user