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

View File

@ -1,21 +1,21 @@
import CodeEditor from 'components/CodeEditor/index';
import HTTPSnippet from 'httpsnippet';
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 CodeView = ({ language, item }) => {
const { storedTheme } = useTheme();
return (
<CodeEditor
readOnly
// value={JSON.stringify(item, null, 2)}
value={snippet}
theme={storedTheme}
mode={lang}
/>
);
const { target, client, language: lang } = language;
let snippet = '';
try {
snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client);
} catch (e) {
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`
margin-inline: -1rem;
margin-block: -1.5rem;
position: absolute;
background-color: ${(props) => props.theme.collection.environment.settings.bg};
.generate-code-sidebar {

View File

@ -2,8 +2,28 @@ 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';
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 = [
{
@ -52,12 +72,32 @@ const languages = [
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]);
return (
<StyledWrapper>
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
<div className="flex">
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
<StyledWrapper>
<div className="flex w-full">
<div>
<div className="generate-code-sidebar">
{languages &&
@ -75,20 +115,31 @@ const index = ({ item, onClose }) => {
))}
</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 className="flex-grow p-4">
{isValidUrl(interpolatedUrl) ? (
<CodeView
language={selectedLanguage}
item={{
...item,
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>
</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)} />
)}
{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="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
// and take some extra action based on that
// 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;
console.log(collection)
collapseCollection(collection);
addDepth(collection.items);
@ -73,16 +72,16 @@ export const collectionsSlice = createSlice({
state.collections = filter(state.collections, (c) => c.uid !== action.payload.collectionUid);
},
sortCollections: (state, action) => {
state.collectionSortOrder = action.payload.order
state.collectionSortOrder = action.payload.order;
switch (action.payload.order) {
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;
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;
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;
}
},

View File

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