diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Vars/StyledWrapper.js new file mode 100644 index 000000000..44b01b464 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/StyledWrapper.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + div.title { + color: var(--color-tab-inactive); + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/StyledWrapper.js new file mode 100644 index 000000000..efacc8288 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/StyledWrapper.js @@ -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; diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js new file mode 100644 index 000000000..dcd84d73c --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js @@ -0,0 +1,161 @@ +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 { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import SingleLineEditor from 'components/SingleLineEditor'; +import Tooltip from 'components/Tooltip'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import { variableNameRegex } from 'utils/common/regex'; +import { addFolderVar, deleteFolderVar, updateFolderVar } from 'providers/ReduxStore/slices/collections/index'; + +const VarsTable = ({ folder, collection, vars, varType }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const addVar = () => { + dispatch( + addFolderVar({ + collectionUid: collection.uid, + folderUid: folder.uid, + type: varType + }) + ); + }; + + const onSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); + const handleVarChange = (e, v, type) => { + const _var = cloneDeep(v); + switch (type) { + case 'name': { + const value = e.target.value; + + if (variableNameRegex.test(value) === false) { + toast.error( + 'Variable contains invalid characters! Variables must only contain alpha-numeric characters, "-", "_", "."' + ); + return; + } + + _var.name = value; + break; + } + case 'value': { + _var.value = e.target.value; + break; + } + case 'enabled': { + _var.enabled = e.target.checked; + break; + } + } + dispatch( + updateFolderVar({ + type: varType, + var: _var, + folderUid: folder.uid, + collectionUid: collection.uid + }) + ); + }; + + const handleRemoveVar = (_var) => { + dispatch( + deleteFolderVar({ + type: varType, + varUid: _var.uid, + folderUid: folder.uid, + collectionUid: collection.uid + }) + ); + }; + + return ( + + + + + + {varType === 'request' ? ( + + ) : ( + + )} + + + + + {vars && vars.length + ? vars.map((_var) => { + return ( + + + + + + ); + }) + : null} + +
Name +
+ Value + +
+
+
+ Expr + +
+
+ handleVarChange(e, _var, 'name')} + /> + + + handleVarChange( + { + target: { + value: newValue + } + }, + _var, + 'value' + ) + } + collection={collection} + /> + +
+ handleVarChange(e, _var, 'enabled')} + /> + +
+
+ +
+ ); +}; +export default VarsTable; diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/index.js b/packages/bruno-app/src/components/FolderSettings/Vars/index.js new file mode 100644 index 000000000..8f9cab4d2 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Vars/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import get from 'lodash/get'; +import VarsTable from './VarsTable'; +import StyledWrapper from './StyledWrapper'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import { useDispatch } from 'react-redux'; + +const Vars = ({ collection, folder }) => { + const dispatch = useDispatch(); + const requestVars = get(folder, 'root.request.vars.req', []); + const responseVars = get(folder, 'root.request.vars.res', []); + const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); + return ( + +
+
Pre Request
+ +
+
+
Post Response
+ +
+
+ +
+
+ ); +}; + +export default Vars; diff --git a/packages/bruno-app/src/components/FolderSettings/index.js b/packages/bruno-app/src/components/FolderSettings/index.js index 233cd1139..6dcd9cfd2 100644 --- a/packages/bruno-app/src/components/FolderSettings/index.js +++ b/packages/bruno-app/src/components/FolderSettings/index.js @@ -6,6 +6,7 @@ import Headers from './Headers'; import Script from './Script'; import Tests from './Tests'; import StyledWrapper from './StyledWrapper'; +import Vars from './Vars'; const FolderSettings = ({ collection, folder }) => { const dispatch = useDispatch(); @@ -36,6 +37,9 @@ const FolderSettings = ({ collection, folder }) => { case 'test': { return ; } + case 'vars': { + return ; + } } }; @@ -58,6 +62,9 @@ const FolderSettings = ({ collection, folder }) => {
setTab('test')}> Test
+
setTab('vars')}> + Vars +
{getTabPanel(tab)}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index ac54e6701..90927fa8e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1168,6 +1168,78 @@ export const collectionsSlice = createSlice({ set(folder, 'root.request.headers', headers); } }, + addFolderVar: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; + const type = action.payload.type; + if (folder) { + if (type === 'request') { + const vars = get(folder, 'root.request.vars.req', []); + vars.push({ + uid: uuid(), + name: '', + value: '', + type: 'request', + enabled: true + }); + set(folder, 'root.request.vars.req', vars); + } else if (type === 'response') { + const vars = get(folder, 'root.request.vars.res', []); + vars.push({ + uid: uuid(), + name: '', + value: '', + type: 'response', + enabled: true + }); + set(folder, 'root.request.vars.res', vars); + } + } + }, + updateFolderVar: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; + const type = action.payload.type; + if (folder) { + if (type === 'request') { + let vars = get(folder, 'root.request.vars.req', []); + const _var = find(vars, (h) => h.uid === action.payload.var.uid); + if (_var) { + _var.name = action.payload.var.name; + _var.value = action.payload.var.value; + _var.description = action.payload.var.description; + _var.enabled = action.payload.var.enabled; + } + set(folder, 'root.request.vars.req', vars); + } else if (type === 'response') { + let vars = get(folder, 'root.request.vars.res', []); + const _var = find(vars, (h) => h.uid === action.payload.var.uid); + if (_var) { + _var.name = action.payload.var.name; + _var.value = action.payload.var.value; + _var.description = action.payload.var.description; + _var.enabled = action.payload.var.enabled; + } + set(folder, 'root.request.vars.res', vars); + } + } + }, + deleteFolderVar: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; + const type = action.payload.type; + if (folder) { + if (type === 'request') { + let vars = get(folder, 'root.request.vars.req', []); + vars = filter(vars, (h) => h.uid !== action.payload.varUid); + set(folder, 'root.request.vars.req', vars); + } else if (type === 'response') { + let vars = get(folder, 'root.request.vars.res', []); + vars = filter(vars, (h) => h.uid !== action.payload.varUid); + set(folder, 'root.request.vars.res', vars); + } + } + }, updateFolderRequestScript: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; @@ -1609,6 +1681,9 @@ export const { addFolderHeader, updateFolderHeader, deleteFolderHeader, + addFolderVar, + updateFolderVar, + deleteFolderVar, updateFolderRequestScript, updateFolderResponseScript, updateFolderTests, diff --git a/packages/bruno-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js index 84d2e3099..07041b93b 100644 --- a/packages/bruno-electron/src/bru/index.js +++ b/packages/bruno-electron/src/bru/index.js @@ -52,7 +52,7 @@ const jsonToCollectionBru = (json) => { }, vars: { req: _.get(json, 'request.vars.req', []), - res: _.get(json, 'request.vars.req', []) + res: _.get(json, 'request.vars.res', []) }, tests: _.get(json, 'request.tests', ''), docs: _.get(json, 'docs', '') diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 6ebb8071c..f4223c2a7 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -418,7 +418,7 @@ const registerNetworkIpc = (mainWindow) => { // run post-response script let scriptResult; - const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( + const responseScript = compact([get(request, 'script.res'), get(collectionRoot, 'request.script.res')]).join( os.EOL ); if (responseScript?.length) { @@ -602,8 +602,8 @@ const registerNetworkIpc = (mainWindow) => { // run tests const testFile = compact([ - get(collectionRoot, 'request.tests'), - item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests') + item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests'), + get(collectionRoot, 'request.tests') ]).join(os.EOL); if (typeof testFile === 'string') { const testRuntime = new TestRuntime(); diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 08082a251..6f0331c2a 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -18,6 +18,13 @@ const mergeFolderLevelHeaders = (request, requestTreePath) => { folderHeaders.set(header.name, header.value); } }); + } else { + let headers = get(i, 'request.headers', []); + headers.forEach((header) => { + if (header.enabled) { + folderHeaders.set(header.name, header.value); + } + }); } } @@ -38,10 +45,84 @@ const mergeFolderLevelHeaders = (request, requestTreePath) => { request.headers = Array.from(requestHeadersMap, ([name, value]) => ({ name, value, enabled: true })); }; +const mergeFolderLevelVars = (request, requestTreePath) => { + let folderReqVars = new Map(); + for (let i of requestTreePath) { + if (i.type === 'folder') { + let vars = get(i, 'root.request.vars.req', []); + vars.forEach((_var) => { + if (_var.enabled) { + folderReqVars.set(_var.name, _var.value); + } + }); + } else { + let vars = get(i, 'request.vars.req', []); + vars.forEach((_var) => { + if (_var.enabled) { + folderReqVars.set(_var.name, _var.value); + } + }); + } + } + let mergedFolderReqVars = Array.from(folderReqVars, ([name, value]) => ({ name, value, enabled: true })); + let requestReqVars = request?.vars?.req || []; + let requestReqVarsMap = new Map(); + for (let _var of requestReqVars) { + if (_var.enabled) { + requestReqVarsMap.set(_var.name, _var.value); + } + } + mergedFolderReqVars.forEach((_var) => { + requestReqVarsMap.set(_var.name, _var.value); + }); + request.vars.req = Array.from(requestReqVarsMap, ([name, value]) => ({ + name, + value, + enabled: true, + type: 'request' + })); + + let folderResVars = new Map(); + for (let i of requestTreePath) { + if (i.type === 'folder') { + let vars = get(i, 'root.request.vars.res', []); + vars.forEach((_var) => { + if (_var.enabled) { + folderResVars.set(_var.name, _var.value); + } + }); + } else { + let vars = get(i, 'request.vars.res', []); + vars.forEach((_var) => { + if (_var.enabled) { + folderResVars.set(_var.name, _var.value); + } + }); + } + } + let mergedFolderResVars = Array.from(folderResVars, ([name, value]) => ({ name, value, enabled: true })); + let requestResVars = request?.vars?.res || []; + let requestResVarsMap = new Map(); + for (let _var of requestResVars) { + if (_var.enabled) { + requestResVarsMap.set(_var.name, _var.value); + } + } + mergedFolderResVars.forEach((_var) => { + requestResVarsMap.set(_var.name, _var.value); + }); + request.vars.res = Array.from(requestResVarsMap, ([name, value]) => ({ + name, + value, + enabled: true, + type: 'response' + })); +}; + const mergeFolderLevelScripts = (request, requestTreePath) => { let folderCombinedPreReqScript = []; let folderCombinedPostResScript = []; - let folderCombinedTests = []; + let folderCombinedTests = ''; for (let i of requestTreePath) { if (i.type === 'folder') { let preReqScript = get(i, 'root.request.script.req', ''); @@ -54,16 +135,15 @@ const mergeFolderLevelScripts = (request, requestTreePath) => { folderCombinedPostResScript.push(postResScript); } - let tests = get(i, 'root.request.tests', []); - if (tests && tests?.trim() !== '') { - folderCombinedTests.push(tests); + let tests = get(i, 'root.request.tests', ''); + if (tests?.trim?.() !== '') { + folderCombinedTests = `${folderCombinedTests} \n ${tests} \n`; } } } if (folderCombinedPreReqScript.length) { request.script.req = compact([...folderCombinedPreReqScript, request?.script?.req || '']).join(os.EOL); - console.log('request.script.req', request.script.req); } if (folderCombinedPostResScript.length) { @@ -71,7 +151,7 @@ const mergeFolderLevelScripts = (request, requestTreePath) => { } if (folderCombinedTests.length) { - request.tests = compact([request?.tests || '', ...folderCombinedTests.reverse()]).join(os.EOL); + request.tests = `${request?.tests} \n ${folderCombinedTests}`; } }; @@ -225,6 +305,7 @@ const prepareRequest = (item, collection) => { if (requestTreePath && requestTreePath.length > 0) { mergeFolderLevelHeaders(request, requestTreePath); mergeFolderLevelScripts(request, requestTreePath); + mergeFolderLevelVars(request, requestTreePath); } each(request.headers, (h) => { diff --git a/packages/bruno-electron/tests/network/prepare-request.spec.js b/packages/bruno-electron/tests/network/prepare-request.spec.js index 833f58310..e3441953b 100644 --- a/packages/bruno-electron/tests/network/prepare-request.spec.js +++ b/packages/bruno-electron/tests/network/prepare-request.spec.js @@ -7,14 +7,14 @@ describe('prepare-request: prepareRequest', () => { it('If request body is valid JSON', async () => { const body = { mode: 'json', json: '{\n"test": "{{someVar}}" // comment\n}' }; const expected = { test: '{{someVar}}' }; - const result = prepareRequest({ body }); + const result = prepareRequest({ request: { body } }, {}); expect(result.data).toEqual(expected); }); it('If request body is not valid JSON', async () => { const body = { mode: 'json', json: '{\n"test": {{someVar}} // comment\n}' }; const expected = '{\n"test": {{someVar}} \n}'; - const result = prepareRequest({ body }); + const result = prepareRequest({ request: { body } }, {}); expect(result.data).toEqual(expected); }); });