feat(#280): code generator polishing and support url interpolation

This commit is contained in:
Anoop M D 2023-10-02 16:52:02 +05:30
parent 4603ec4d5e
commit 95532102ba
7 changed files with 95 additions and 60 deletions

View File

@ -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",
"handlebars": "^4.7.8",
"httpsnippet": "^3.0.1", "httpsnippet": "^3.0.1",
"idb": "^7.0.0", "idb": "^7.0.0",
"immer": "^9.0.15", "immer": "^9.0.15",

View File

@ -1,21 +1,21 @@
import CodeEditor from 'components/CodeEditor/index'; import CodeEditor from 'components/CodeEditor/index';
import HTTPSnippet from 'httpsnippet'; import { HTTPSnippet } from 'httpsnippet';
import { useTheme } from 'providers/Theme/index'; import { useTheme } from 'providers/Theme/index';
import { buildHarRequest } from 'utils/codegenerator/har'; import { buildHarRequest } from 'utils/codegenerator/har';
const index = ({ language, item }) => { const CodeView = ({ language, item }) => {
const { target, client, language: lang } = language;
const snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client);
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
return ( const { target, client, language: lang } = language;
<CodeEditor let snippet = '';
readOnly
// value={JSON.stringify(item, null, 2)} try {
value={snippet} snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client);
theme={storedTheme} } catch (e) {
mode={lang} console.error(e);
/> snippet = 'Error generating code snippet';
); }
return <CodeEditor readOnly value={snippet} theme={storedTheme} mode={lang} />;
}; };
export default index; export default CodeView;

View File

@ -3,7 +3,6 @@ import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
margin-inline: -1rem; margin-inline: -1rem;
margin-block: -1.5rem; margin-block: -1.5rem;
position: absolute;
background-color: ${(props) => props.theme.collection.environment.settings.bg}; background-color: ${(props) => props.theme.collection.environment.settings.bg};
.generate-code-sidebar { .generate-code-sidebar {

View File

@ -2,8 +2,28 @@ import Modal from 'components/Modal/index';
import { useState } from 'react'; import { useState } from 'react';
import CodeView from './CodeView'; import CodeView from './CodeView';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import ErrorBoundary from 'src/pages/ErrorBoundary/index';
import { isValidUrl } from 'utils/url/index'; import { isValidUrl } from 'utils/url/index';
import get from 'lodash/get';
import handlebars from 'handlebars';
import { findEnvironmentInCollection } from 'utils/collections';
const interpolateUrl = ({ url, envVars, collectionVariables, processEnvVars }) => {
if (!url || !url.length || typeof url !== 'string') {
return str;
}
const template = handlebars.compile(url, { noEscape: true });
return template({
...envVars,
...collectionVariables,
process: {
env: {
...processEnvVars
}
}
});
};
const languages = [ const languages = [
{ {
@ -52,12 +72,32 @@ const languages = [
client: 'httpie' client: 'httpie'
} }
]; ];
const index = ({ item, onClose }) => {
const GenerateCodeItem = ({ collection, item, onClose }) => {
const url = get(item, 'request.url') || '';
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
let envVars = {};
if (environment) {
const vars = get(environment, 'variables', []);
envVars = vars.reduce((acc, curr) => {
acc[curr.name] = curr.value;
return acc;
}, {});
}
const interpolatedUrl = interpolateUrl({
url,
envVars,
collectionVariables: collection.collectionVariables,
processEnvVars: collection.processEnvVariables
});
const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); const [selectedLanguage, setSelectedLanguage] = useState(languages[0]);
return ( return (
<StyledWrapper> <Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}> <StyledWrapper>
<div className="flex"> <div className="flex w-full">
<div> <div>
<div className="generate-code-sidebar"> <div className="generate-code-sidebar">
{languages && {languages &&
@ -75,20 +115,31 @@ const index = ({ item, onClose }) => {
))} ))}
</div> </div>
</div> </div>
{isValidUrl(item.request.url) ? ( <div className="flex-grow p-4">
<CodeView language={selectedLanguage} item={item} /> {isValidUrl(interpolatedUrl) ? (
) : ( <CodeView
<div className="flex flex-col justify-center items-center w-full"> language={selectedLanguage}
<div className="text-center"> item={{
<h1 className="text-2xl font-bold">Invalid URL</h1> ...item,
<p className="text-gray-500">Please check the URL and try again</p> request: {
...item.request,
url: interpolatedUrl
}
}}
/>
) : (
<div className="flex flex-col justify-center items-center w-full">
<div className="text-center">
<h1 className="text-2xl font-bold">Invalid URL: {interpolatedUrl}</h1>
<p className="text-gray-500">Please check the URL and try again</p>
</div>
</div> </div>
</div> )}
)} </div>
</div> </div>
</Modal> </StyledWrapper>
</StyledWrapper> </Modal>
); );
}; };
export default index; export default GenerateCodeItem;

View File

@ -169,7 +169,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
<RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} /> <RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />
)} )}
{generateCodeItemModalOpen && ( {generateCodeItemModalOpen && (
<GenerateCodeItem item={item} onClose={() => setGenerateCodeItemModalOpen(false)} /> <GenerateCodeItem collection={collection} 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">

View File

@ -44,9 +44,8 @@ export const collectionsSlice = createSlice({
// this is used in scenarios where we want to know the last action performed on the collection // this is used in scenarios where we want to know the last action performed on the collection
// and take some extra action based on that // and take some extra action based on that
// for example, when a env is created, we want to auto select it the env modal // for example, when a env is created, we want to auto select it the env modal
collection.importedAt = new Date().getTime() collection.importedAt = new Date().getTime();
collection.lastAction = null; collection.lastAction = null;
console.log(collection)
collapseCollection(collection); collapseCollection(collection);
addDepth(collection.items); addDepth(collection.items);
@ -73,16 +72,16 @@ export const collectionsSlice = createSlice({
state.collections = filter(state.collections, (c) => c.uid !== action.payload.collectionUid); state.collections = filter(state.collections, (c) => c.uid !== action.payload.collectionUid);
}, },
sortCollections: (state, action) => { sortCollections: (state, action) => {
state.collectionSortOrder = action.payload.order state.collectionSortOrder = action.payload.order;
switch (action.payload.order) { switch (action.payload.order) {
case 'default': case 'default':
state.collections = state.collections.sort((a, b) => a.importedAt - b.importedAt) state.collections = state.collections.sort((a, b) => a.importedAt - b.importedAt);
break; break;
case 'alphabetical': case 'alphabetical':
state.collections = state.collections.sort((a, b) => a.name.localeCompare(b.name)) state.collections = state.collections.sort((a, b) => a.name.localeCompare(b.name));
break; break;
case 'reverseAlphabetical': case 'reverseAlphabetical':
state.collections = state.collections.sort((a, b) => b.name.localeCompare(a.name)) state.collections = state.collections.sort((a, b) => b.name.localeCompare(a.name));
break; break;
} }
}, },

View File

@ -30,12 +30,11 @@ const createHeaders = (headers, mode) => {
return headersArray; return headersArray;
}; };
const createQuery = (url) => { const createQuery = (queryParams = []) => {
const params = new URLSearchParams(url); return queryParams.map((param) => {
return params.forEach((value, name) => {
return { return {
name, name: param.name,
value value: param.value
}; };
}); });
}; };
@ -57,28 +56,14 @@ const createPostData = (body) => {
} }
}; };
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) => { export const buildHarRequest = (request) => {
return { return {
method: request.method, method: request.method,
url: createUrl(request), url: request.url,
httpVersion: 'HTTP/1.1', httpVersion: 'HTTP/1.1',
cookies: [], cookies: [],
headers: createHeaders(request.headers, request.body.mode), headers: createHeaders(request.headers, request.body.mode),
queryString: createQuery(request.url), queryString: createQuery(request.params),
postData: createPostData(request.body), postData: createPostData(request.body),
headersSize: 0, headersSize: 0,
bodySize: 0 bodySize: 0