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 (
+
+
+
+
+ );
+};
+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 (
+
+
+
+
+
+
+
+ );
+};
+
+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
+
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);
});
});