feat: vars implementation in UI

This commit is contained in:
Anoop M D 2023-02-21 13:05:51 +05:30
parent d07744d5c2
commit 925af1f26f
14 changed files with 385 additions and 8 deletions

View File

@ -7,6 +7,7 @@ import QueryParams from 'components/RequestPane/QueryParams';
import RequestHeaders from 'components/RequestPane/RequestHeaders'; import RequestHeaders from 'components/RequestPane/RequestHeaders';
import RequestBody from 'components/RequestPane/RequestBody'; import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode'; import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
import Vars from 'components/RequestPane/Vars';
import Script from 'components/RequestPane/Script'; import Script from 'components/RequestPane/Script';
import Tests from 'components/RequestPane/Tests'; import Tests from 'components/RequestPane/Tests';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
@ -36,6 +37,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
case 'headers': { case 'headers': {
return <RequestHeaders item={item} collection={collection} />; return <RequestHeaders item={item} collection={collection} />;
} }
case 'vars': {
return <Vars item={item} collection={collection} />;
}
case 'script': { case 'script': {
return <Script item={item} collection={collection} />; return <Script item={item} collection={collection} />;
} }
@ -75,6 +79,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}> <div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers Headers
</div> </div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}> <div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script Script
</div> </div>
@ -89,7 +96,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div> </div>
) : null} ) : null}
</div> </div>
<section className={`flex w-full ${focusedTab.requestPaneTab === 'script' ? '' : 'mt-5'}`}>{getTabPanel(focusedTab.requestPaneTab)}</section> <section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>{getTabPanel(focusedTab.requestPaneTab)}</section>
</StyledWrapper> </StyledWrapper>
); );
}; };

View File

@ -68,7 +68,7 @@ const QueryParams = ({ item, collection }) => {
<table> <table>
<thead> <thead>
<tr> <tr>
<td>Key</td> <td>Name</td>
<td>Value</td> <td>Value</td>
<td></td> <td></td>
</tr> </tr>

View File

@ -65,7 +65,7 @@ const RequestHeaders = ({ item, collection }) => {
<table> <table>
<thead> <thead>
<tr> <tr>
<td>Key</td> <td>Name</td>
<td>Value</td> <td>Value</td>
<td></td> <td></td>
</tr> </tr>

View File

@ -6,8 +6,7 @@ const StyledWrapper = styled.div`
} }
div.title { div.title {
color: rgb(155 155 155); color: var(--color-tab-inactive);
font-weight: 500;
} }
`; `;

View File

@ -41,8 +41,8 @@ const Script = ({ item, collection }) => {
return ( return (
<StyledWrapper className="w-full flex flex-col"> <StyledWrapper className="w-full flex flex-col">
<div className='flex-1'> <div className='flex-1 mt-2'>
<div className='mb-1 title'>Request</div> <div className='mb-1 title text-xs'>Pre Request</div>
<CodeEditor <CodeEditor
collection={collection} value={requestScript || ''} collection={collection} value={requestScript || ''}
theme={storedTheme} theme={storedTheme}
@ -53,7 +53,7 @@ const Script = ({ item, collection }) => {
/> />
</div> </div>
<div className='flex-1 mt-6'> <div className='flex-1 mt-6'>
<div className='mt-1 mb-1 title'>Response</div> <div className='mt-1 mb-1 title text-xs'>Post Response</div>
<CodeEditor <CodeEditor
collection={collection} value={responseScript || ''} collection={collection} value={responseScript || ''}
theme={storedTheme} theme={storedTheme}

View File

@ -0,0 +1,9 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.title {
color: var(--color-tab-inactive);
}
`;
export default StyledWrapper;

View File

@ -0,0 +1,56 @@
import styled from 'styled-components';
const Wrapper = styled.div`
table {
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem;
user-select: none;
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}
.btn-add-var {
font-size: 0.8125rem;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;
border: solid 1px transparent;
}
}
input[type='checkbox'] {
cursor: pointer;
position: relative;
top: 1px;
}
`;
export default Wrapper;

View File

@ -0,0 +1,130 @@
import React from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
const VarsTable = ({ item, collection, vars, varType }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const handleAddVar = () => {
dispatch(
addVar({
type: varType,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleVarChange = (e, v, type) => {
const _var = cloneDeep(v);
switch (type) {
case 'name': {
_var.name = e.target.value;
break;
}
case 'value': {
_var.value = e.target.value;
break;
}
case 'enabled': {
_var.enabled = e.target.checked;
break;
}
}
dispatch(
updateVar({
type: varType,
var: _var,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const handleRemoveVar = (_var) => {
dispatch(
deleteVar({
type: varType,
varUid: _var.uid,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Name</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
{vars && vars.length
? vars.map((_var, index) => {
return (
<tr key={_var.uid}>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={_var.name}
className="mousetrap"
onChange={(e) => handleVarChange(e, _var, 'name')}
/>
</td>
<td>
<SingleLineEditor
value={_var.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) => handleVarChange({
target: {
value: newValue
}
}, _var, 'value')}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={_var.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, _var, 'enabled')}
/>
<button onClick={() => handleRemoveVar(_var)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={handleAddVar}>
+ Add
</button>
</StyledWrapper>
);
};
export default VarsTable;

View File

@ -0,0 +1,56 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import VarsTable from './VarsTable';
import StyledWrapper from './StyledWrapper';
const Vars = ({ item, collection }) => {
const dispatch = useDispatch();
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
const {
storedTheme
} = useTheme();
const onRequestScriptEdit = (value) => {
dispatch(
updateRequestScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onResponseScriptEdit = (value) => {
dispatch(
updateResponseScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full flex flex-col">
<div className='flex-1 mt-2'>
<div className='mb-1 title text-xs'>Pre Request</div>
<VarsTable item={item} collection={collection} vars={requestVars} varType='request'/>
</div>
<div className='flex-1'>
<div className='mt-1 mb-1 title text-xs'>Post Response</div>
<VarsTable item={item} collection={collection} vars={responseVars} varType='response'/>
</div>
</StyledWrapper>
);
};
export default Vars;

View File

@ -707,6 +707,100 @@ export const collectionsSlice = createSlice({
} }
} }
}, },
addVar: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
const type = action.payload.type;
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
if(type === 'request') {
item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.req = item.draft.request.vars.req || [];
item.draft.request.vars.req.push({
uid: uuid(),
name: '',
value: '',
local: false,
enabled: true
});
} else if(type === 'response') {
item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.res = item.draft.request.vars.res || [];
item.draft.request.vars.res.push({
uid: uuid(),
name: '',
value: '',
local: false,
enabled: true
});
}
}
}
},
updateVar: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
const type = action.payload.type;
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
if(type === 'request') {
item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.req = item.draft.request.vars.req || [];
const reqVar = find(item.draft.request.vars.req, (v) => v.uid === action.payload.var.uid);
if (reqVar) {
reqVar.name = action.payload.var.name;
reqVar.value = action.payload.var.value;
reqVar.description = action.payload.var.description;
reqVar.enabled = action.payload.var.enabled;
}
} else if(type === 'response') {
item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.res = item.draft.request.vars.res || [];
const resVar = find(item.draft.request.vars.res, (v) => v.uid === action.payload.var.uid);
if (resVar) {
resVar.name = action.payload.var.name;
resVar.value = action.payload.var.value;
resVar.description = action.payload.var.description;
resVar.enabled = action.payload.var.enabled;
}
}
}
}
},
deleteVar: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
const type = action.payload.type;
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
if(type === 'request') {
item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.req = item.draft.request.vars.req || [];
item.draft.request.vars.req = item.draft.request.vars.req.filter((v) => v.uid !== action.payload.varUid);
} else if(type === 'response') {
item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.res = item.draft.request.vars.res || [];
item.draft.request.vars.res = item.draft.request.vars.res.filter((v) => v.uid !== action.payload.varUid);
}
}
}
},
collectionAddFileEvent: (state, action) => { collectionAddFileEvent: (state, action) => {
const file = action.payload.file; const file = action.payload.file;
const collection = findCollectionByUid(state.collections, file.meta.collectionUid); const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
@ -1028,6 +1122,9 @@ export const {
updateResponseScript, updateResponseScript,
updateRequestTests, updateRequestTests,
updateRequestMethod, updateRequestMethod,
addVar,
updateVar,
deleteVar,
collectionAddFileEvent, collectionAddFileEvent,
collectionAddDirectoryEvent, collectionAddDirectoryEvent,
collectionChangeFileEvent, collectionChangeFileEvent,

View File

@ -349,6 +349,7 @@ export const transformRequestToSaveToFilesystem = (item) => {
headers: [], headers: [],
body: _item.request.body, body: _item.request.body,
script: _item.request.script, script: _item.request.script,
vars: _item.request.vars,
tests: _item.request.tests tests: _item.request.tests
} }
}; };

View File

@ -40,11 +40,15 @@ const hydrateRequestWithUuid = (request, pathname) => {
const params = _.get(request, 'request.params', []); const params = _.get(request, 'request.params', []);
const headers = _.get(request, 'request.headers', []); const headers = _.get(request, 'request.headers', []);
const requestVars = _.get(request, 'request.vars.req', []);
const responseVars = _.get(request, 'request.vars.res', []);
const bodyFormUrlEncoded = _.get(request, 'request.body.formUrlEncoded', []); const bodyFormUrlEncoded = _.get(request, 'request.body.formUrlEncoded', []);
const bodyMultipartForm = _.get(request, 'request.body.multipartForm', []); const bodyMultipartForm = _.get(request, 'request.body.multipartForm', []);
params.forEach((param) => param.uid = uuid()); params.forEach((param) => param.uid = uuid());
headers.forEach((header) => header.uid = uuid()); headers.forEach((header) => header.uid = uuid());
requestVars.forEach((variable) => variable.uid = uuid());
responseVars.forEach((variable) => variable.uid = uuid());
bodyFormUrlEncoded.forEach((param) => param.uid = uuid()); bodyFormUrlEncoded.forEach((param) => param.uid = uuid());
bodyMultipartForm.forEach((param) => param.uid = uuid()); bodyMultipartForm.forEach((param) => param.uid = uuid());

View File

@ -68,6 +68,7 @@ const bruToJson = (bru) => {
"headers": _.get(json, "headers", []), "headers": _.get(json, "headers", []),
"body": _.get(json, "body", {}), "body": _.get(json, "body", {}),
"script": _.get(json, "script", {}), "script": _.get(json, "script", {}),
"vars": _.get(json, "vars", {}),
"tests": _.get(json, "tests", "") "tests": _.get(json, "tests", "")
} }
}; };
@ -113,6 +114,10 @@ const jsonToBru = (json) => {
headers: _.get(json, 'request.headers', []), headers: _.get(json, 'request.headers', []),
body: _.get(json, 'request.body', {}), body: _.get(json, 'request.body', {}),
script: _.get(json, 'request.script', {}), script: _.get(json, 'request.script', {}),
vars: {
req: _.get(json, 'request.vars.req', []),
res: _.get(json, 'request.vars.res', [])
},
tests: _.get(json, 'request.tests', ''), tests: _.get(json, 'request.tests', ''),
}; };

View File

@ -26,6 +26,15 @@ const keyValueSchema = Yup.object({
enabled: Yup.boolean() enabled: Yup.boolean()
}).noUnknown(true).strict(); }).noUnknown(true).strict();
const varsSchema = Yup.object({
uid: uidSchema,
name: Yup.string().nullable(),
value: Yup.string().nullable(),
description: Yup.string().nullable(),
local: Yup.boolean(),
enabled: Yup.boolean()
}).noUnknown(true).strict();
const requestUrlSchema = Yup.string().min(0).defined(); const requestUrlSchema = Yup.string().min(0).defined();
const requestMethodSchema = Yup.string().oneOf(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']).required('method is required'); const requestMethodSchema = Yup.string().oneOf(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']).required('method is required');
@ -57,6 +66,10 @@ const requestSchema = Yup.object({
req: Yup.string().nullable(), req: Yup.string().nullable(),
res: Yup.string().nullable() res: Yup.string().nullable()
}).noUnknown(true).strict(), }).noUnknown(true).strict(),
vars: Yup.object({
req: Yup.array().of(varsSchema).nullable(),
res: Yup.array().of(varsSchema).nullable()
}).noUnknown(true).strict().nullable(),
tests: Yup.string().nullable() tests: Yup.string().nullable()
}).noUnknown(true).strict(); }).noUnknown(true).strict();