mirror of
https://github.com/usebruno/bruno.git
synced 2024-12-24 07:38:59 +01:00
feat:added code generator to http-requests
This commit is contained in:
parent
cedcd2cf35
commit
3d0c9cc0ae
@ -30,6 +30,7 @@
|
|||||||
"graphiql": "^1.5.9",
|
"graphiql": "^1.5.9",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"graphql-request": "^3.7.0",
|
"graphql-request": "^3.7.0",
|
||||||
|
"httpsnippet": "^3.0.1",
|
||||||
"idb": "^7.0.0",
|
"idb": "^7.0.0",
|
||||||
"immer": "^9.0.15",
|
"immer": "^9.0.15",
|
||||||
"know-your-http-well": "^0.5.0",
|
"know-your-http-well": "^0.5.0",
|
||||||
|
@ -119,7 +119,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper
|
<StyledWrapper
|
||||||
className="h-full"
|
className="h-full w-full"
|
||||||
aria-label="Code Editor"
|
aria-label="Code Editor"
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
this._node = 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;
|
||||||
|
|
||||||
|
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 CloneCollectionItem from './CloneCollectionItem';
|
||||||
import DeleteCollectionItem from './DeleteCollectionItem';
|
import DeleteCollectionItem from './DeleteCollectionItem';
|
||||||
import RunCollectionItem from './RunCollectionItem';
|
import RunCollectionItem from './RunCollectionItem';
|
||||||
|
import GenerateCodeItem from './GenerateCodeItem';
|
||||||
import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
|
import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
|
||||||
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
|
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
|
||||||
import { getDefaultRequestPaneTab } from 'utils/collections';
|
import { getDefaultRequestPaneTab } from 'utils/collections';
|
||||||
@ -32,6 +33,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
|
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
|
||||||
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
|
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
|
||||||
const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false);
|
const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false);
|
||||||
|
const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false);
|
||||||
const [newRequestModalOpen, setNewRequestModalOpen] = useState(false);
|
const [newRequestModalOpen, setNewRequestModalOpen] = useState(false);
|
||||||
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
|
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
|
||||||
const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false);
|
const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false);
|
||||||
@ -166,6 +168,9 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
{runCollectionModalOpen && (
|
{runCollectionModalOpen && (
|
||||||
<RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />
|
<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={itemRowClassName} ref={(node) => drag(drop(node))}>
|
||||||
<div className="flex items-center h-full w-full">
|
<div className="flex items-center h-full w-full">
|
||||||
{indents && indents.length
|
{indents && indents.length
|
||||||
@ -264,6 +269,18 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
Clone
|
Clone
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!isFolder && item.type === 'http-request' && (
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
setGenerateCodeItemModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Generate Code
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className="dropdown-item delete-item"
|
className="dropdown-item delete-item"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
72
packages/bruno-app/src/utils/codegenerator/har.js
Normal file
72
packages/bruno-app/src/utils/codegenerator/har.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildHarRequest = (request) => {
|
||||||
|
return {
|
||||||
|
method: request.method,
|
||||||
|
url: request.url,
|
||||||
|
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)];
|
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