diff --git a/package-lock.json b/package-lock.json
index 76cfe4c18..f4652d39e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9996,32 +9996,6 @@
"graphql": ">=0.11 <=16"
}
},
- "node_modules/handlebars": {
- "version": "4.7.8",
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.5",
- "neo-async": "^2.6.2",
- "source-map": "^0.6.1",
- "wordwrap": "^1.0.0"
- },
- "bin": {
- "handlebars": "bin/handlebars"
- },
- "engines": {
- "node": ">=0.4.7"
- },
- "optionalDependencies": {
- "uglify-js": "^3.1.4"
- }
- },
- "node_modules/handlebars/node_modules/minimist": {
- "version": "1.2.8",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/har-schema": {
"version": "2.0.0",
"license": "ISC",
@@ -10433,7 +10407,6 @@
},
"node_modules/iconv-lite": {
"version": "0.6.3",
- "dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -12985,6 +12958,7 @@
},
"node_modules/neo-async": {
"version": "2.6.2",
+ "dev": true,
"license": "MIT"
},
"node_modules/new-github-issue-url": {
@@ -16443,6 +16417,7 @@
},
"node_modules/source-map": {
"version": "0.6.1",
+ "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -17737,17 +17712,6 @@
"version": "1.0.6",
"license": "MIT"
},
- "node_modules/uglify-js": {
- "version": "3.17.4",
- "license": "BSD-2-Clause",
- "optional": true,
- "bin": {
- "uglifyjs": "bin/uglifyjs"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/underscore": {
"version": "1.6.0"
},
@@ -18363,10 +18327,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/wordwrap": {
- "version": "1.0.0",
- "license": "MIT"
- },
"node_modules/wrap-ansi": {
"version": "6.2.0",
"license": "MIT",
@@ -19753,6 +19713,7 @@
"graphql": "^16.6.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2",
+ "iconv-lite": "^0.6.3",
"is-valid-path": "^0.1.1",
"js-yaml": "^4.1.0",
"json-bigint": "^1.0.0",
@@ -20806,6 +20767,7 @@
"version": "0.12.0",
"license": "MIT",
"dependencies": {
+ "@usebruno/common": "0.1.0",
"@usebruno/query": "0.1.0",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
@@ -20815,7 +20777,6 @@
"chai": "^4.3.7",
"chai-string": "^1.5.0",
"crypto-js": "^4.1.1",
- "handlebars": "^4.7.8",
"json-query": "^2.2.2",
"lodash": "^4.17.21",
"moment": "^2.29.4",
@@ -25207,6 +25168,7 @@
"@usebruno/js": {
"version": "file:packages/bruno-js",
"requires": {
+ "@usebruno/common": "0.1.0",
"@usebruno/query": "0.1.0",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
@@ -25216,7 +25178,6 @@
"chai": "^4.3.7",
"chai-string": "^1.5.0",
"crypto-js": "^4.1.1",
- "handlebars": "^4.7.8",
"json-query": "^2.2.2",
"lodash": "^4.17.21",
"moment": "^2.29.4",
@@ -26162,6 +26123,7 @@
"graphql": "^16.6.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2",
+ "iconv-lite": "^0.6.3",
"is-valid-path": "^0.1.1",
"js-yaml": "^4.1.0",
"json-bigint": "^1.0.0",
@@ -29284,21 +29246,6 @@
"graphql-ws": {
"version": "5.12.1"
},
- "handlebars": {
- "version": "4.7.8",
- "requires": {
- "minimist": "^1.2.5",
- "neo-async": "^2.6.2",
- "source-map": "^0.6.1",
- "uglify-js": "^3.1.4",
- "wordwrap": "^1.0.0"
- },
- "dependencies": {
- "minimist": {
- "version": "1.2.8"
- }
- }
- },
"har-schema": {
"version": "2.0.0"
},
@@ -29543,7 +29490,6 @@
},
"iconv-lite": {
"version": "0.6.3",
- "dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
@@ -31169,7 +31115,8 @@
"version": "0.6.3"
},
"neo-async": {
- "version": "2.6.2"
+ "version": "2.6.2",
+ "dev": true
},
"new-github-issue-url": {
"version": "0.2.1"
@@ -33261,7 +33208,8 @@
}
},
"source-map": {
- "version": "0.6.1"
+ "version": "0.6.1",
+ "dev": true
},
"source-map-js": {
"version": "1.0.2"
@@ -34060,10 +34008,6 @@
"uc.micro": {
"version": "1.0.6"
},
- "uglify-js": {
- "version": "3.17.4",
- "optional": true
- },
"underscore": {
"version": "1.6.0"
},
@@ -34458,9 +34402,6 @@
"version": "2.0.1",
"dev": true
},
- "wordwrap": {
- "version": "1.0.0"
- },
"wrap-ansi": {
"version": "6.2.0",
"requires": {
diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js
new file mode 100644
index 000000000..9f723cb81
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/Headers/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-header {
+ 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/Headers/index.js b/packages/bruno-app/src/components/FolderSettings/Headers/index.js
new file mode 100644
index 000000000..550a835c2
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/Headers/index.js
@@ -0,0 +1,153 @@
+import React from 'react';
+import get from 'lodash/get';
+import cloneDeep from 'lodash/cloneDeep';
+import { IconTrash } from '@tabler/icons';
+import { useDispatch } from 'react-redux';
+import { useTheme } from 'providers/Theme';
+import { addFolderHeader, updateFolderHeader, deleteFolderHeader } from 'providers/ReduxStore/slices/collections';
+import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
+import SingleLineEditor from 'components/SingleLineEditor';
+import StyledWrapper from './StyledWrapper';
+import { headers as StandardHTTPHeaders } from 'know-your-http-well';
+const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
+
+const Headers = ({ collection, folder }) => {
+ const dispatch = useDispatch();
+ const { storedTheme } = useTheme();
+ const headers = get(folder, 'root.request.headers', []);
+
+ const addHeader = () => {
+ dispatch(
+ addFolderHeader({
+ collectionUid: collection.uid,
+ folderUid: folder.uid
+ })
+ );
+ };
+
+ const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid));
+ const handleHeaderValueChange = (e, _header, type) => {
+ const header = cloneDeep(_header);
+ switch (type) {
+ case 'name': {
+ header.name = e.target.value;
+ break;
+ }
+ case 'value': {
+ header.value = e.target.value;
+ break;
+ }
+ case 'enabled': {
+ header.enabled = e.target.checked;
+ break;
+ }
+ }
+ dispatch(
+ updateFolderHeader({
+ header: header,
+ collectionUid: collection.uid,
+ folderUid: folder.uid
+ })
+ );
+ };
+
+ const handleRemoveHeader = (header) => {
+ dispatch(
+ deleteFolderHeader({
+ headerUid: header.uid,
+ collectionUid: collection.uid,
+ folderUid: folder.uid
+ })
+ );
+ };
+
+ return (
+
+
+ Request headers that will be sent with every request inside this folder.
+
+
+
+
+
+
+
+
+ );
+};
+export default Headers;
diff --git a/packages/bruno-app/src/components/FolderSettings/Script/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Script/StyledWrapper.js
new file mode 100644
index 000000000..66ba1ed3d
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/Script/StyledWrapper.js
@@ -0,0 +1,13 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ div.CodeMirror {
+ height: inherit;
+ }
+
+ div.title {
+ color: var(--color-tab-inactive);
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/FolderSettings/Script/index.js b/packages/bruno-app/src/components/FolderSettings/Script/index.js
new file mode 100644
index 000000000..6c51c062d
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/Script/index.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import get from 'lodash/get';
+import { useDispatch, useSelector } from 'react-redux';
+import CodeEditor from 'components/CodeEditor';
+import { updateFolderRequestScript, updateFolderResponseScript } from 'providers/ReduxStore/slices/collections';
+import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
+import { useTheme } from 'providers/Theme';
+import StyledWrapper from './StyledWrapper';
+
+const Script = ({ collection, folder }) => {
+ const dispatch = useDispatch();
+ const requestScript = get(folder, 'root.request.script.req', '');
+ const responseScript = get(folder, 'root.request.script.res', '');
+
+ const { displayedTheme } = useTheme();
+ const preferences = useSelector((state) => state.app.preferences);
+
+ const onRequestScriptEdit = (value) => {
+ dispatch(
+ updateFolderRequestScript({
+ script: value,
+ collectionUid: collection.uid,
+ folderUid: folder.uid
+ })
+ );
+ };
+
+ const onResponseScriptEdit = (value) => {
+ dispatch(
+ updateFolderResponseScript({
+ script: value,
+ collectionUid: collection.uid,
+ folderUid: folder.uid
+ })
+ );
+ };
+
+ const handleSave = () => {
+ dispatch(saveFolderRoot(collection.uid, folder.uid));
+ };
+
+ return (
+
+
+ Pre and post-request scripts that will run before and after any request inside this folder is sent.
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Script;
diff --git a/packages/bruno-app/src/components/FolderSettings/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/StyledWrapper.js
new file mode 100644
index 000000000..b88a31e0d
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/StyledWrapper.js
@@ -0,0 +1,46 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ max-width: 800px;
+
+ div.tabs {
+ div.tab {
+ padding: 6px 0px;
+ border: none;
+ border-bottom: solid 2px transparent;
+ margin-right: 1.25rem;
+ color: var(--color-tab-inactive);
+ cursor: pointer;
+
+ &:focus,
+ &:active,
+ &:focus-within,
+ &:focus-visible,
+ &:target {
+ outline: none !important;
+ box-shadow: none !important;
+ }
+
+ &.active {
+ color: ${(props) => props.theme.tabs.active.color} !important;
+ border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
+ }
+ }
+ }
+ table {
+ thead,
+ td {
+ border: 1px solid ${(props) => props.theme.table.border};
+
+ li {
+ background-color: ${(props) => props.theme.bg} !important;
+ }
+ }
+ }
+
+ .muted {
+ color: ${(props) => props.theme.colors.text.muted};
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/FolderSettings/Tests/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Tests/StyledWrapper.js
new file mode 100644
index 000000000..ec278887d
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/Tests/StyledWrapper.js
@@ -0,0 +1,5 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div``;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/FolderSettings/Tests/index.js b/packages/bruno-app/src/components/FolderSettings/Tests/index.js
new file mode 100644
index 000000000..b163c6b1e
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/Tests/index.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import get from 'lodash/get';
+import { useDispatch, useSelector } from 'react-redux';
+import CodeEditor from 'components/CodeEditor';
+import { updateFolderTests } from 'providers/ReduxStore/slices/collections';
+import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
+import { useTheme } from 'providers/Theme';
+import StyledWrapper from './StyledWrapper';
+
+const Tests = ({ collection, folder }) => {
+ const dispatch = useDispatch();
+ const tests = get(folder, 'root.request.tests', '');
+
+ const { displayedTheme } = useTheme();
+ const preferences = useSelector((state) => state.app.preferences);
+
+ const onEdit = (value) => {
+ dispatch(
+ updateFolderTests({
+ tests: value,
+ collectionUid: collection.uid,
+ folderUid: folder.uid
+ })
+ );
+ };
+
+ const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid));
+
+ return (
+
+ These tests will run any time a request in this collection is sent.
+
+
+
+
+
+
+ );
+};
+
+export default Tests;
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
new file mode 100644
index 000000000..6dcd9cfd2
--- /dev/null
+++ b/packages/bruno-app/src/components/FolderSettings/index.js
@@ -0,0 +1,75 @@
+import React from 'react';
+import classnames from 'classnames';
+import { updatedFolderSettingsSelectedTab } from 'providers/ReduxStore/slices/collections';
+import { useDispatch } from 'react-redux';
+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();
+ let tab = 'headers';
+ const { folderLevelSettingsSelectedTab } = collection;
+ if (folderLevelSettingsSelectedTab?.[folder.uid]) {
+ tab = folderLevelSettingsSelectedTab[folder.uid];
+ }
+
+ const setTab = (tab) => {
+ dispatch(
+ updatedFolderSettingsSelectedTab({
+ collectionUid: collection.uid,
+ folderUid: folder.uid,
+ tab
+ })
+ );
+ };
+
+ const getTabPanel = (tab) => {
+ switch (tab) {
+ case 'headers': {
+ return ;
+ }
+ case 'script': {
+ return ;
+ }
+ case 'test': {
+ return ;
+ }
+ case 'vars': {
+ return ;
+ }
+ }
+ };
+
+ const getTabClassname = (tabName) => {
+ return classnames(`tab select-none ${tabName}`, {
+ active: tabName === tab
+ });
+ };
+
+ return (
+
+
+
+
setTab('headers')}>
+ Headers
+
+
setTab('script')}>
+ Script
+
+
setTab('test')}>
+ Test
+
+
setTab('vars')}>
+ Vars
+
+
+
+
+
+ );
+};
+
+export default FolderSettings;
diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js
index f719eb0f3..2fd253f4b 100644
--- a/packages/bruno-app/src/components/RequestTabPanel/index.js
+++ b/packages/bruno-app/src/components/RequestTabPanel/index.js
@@ -18,6 +18,7 @@ import CollectionSettings from 'components/CollectionSettings';
import { DocExplorer } from '@usebruno/graphql-docs';
import StyledWrapper from './StyledWrapper';
+import FolderSettings from 'components/FolderSettings';
const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 350;
@@ -131,6 +132,10 @@ const RequestTabPanel = () => {
if (focusedTab.type === 'collection-settings') {
return ;
}
+ if (focusedTab.type === 'folder-settings') {
+ const folder = findItemInCollection(collection, focusedTab.folderUid);
+ return ;
+ }
const item = findItemInCollection(collection, activeTabUid);
if (!item || !item.uid) {
diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js
index ba77d47c9..ce86b820c 100644
--- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js
+++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js
@@ -44,7 +44,7 @@ const CollectionToolBar = ({ collection }) => {
- {collection.name}
+ {collection?.name}
diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
index aebc3db75..f8d7a992a 100644
--- a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
+++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
@@ -1,22 +1,30 @@
import React from 'react';
-import { IconVariable, IconSettings, IconRun } from '@tabler/icons';
+import { IconVariable, IconSettings, IconRun, IconFolder } from '@tabler/icons';
-const SpecialTab = ({ handleCloseClick, type }) => {
- const getTabInfo = (type) => {
+const SpecialTab = ({ handleCloseClick, type, tabName }) => {
+ const getTabInfo = (type, tabName) => {
switch (type) {
case 'collection-settings': {
return (
<>
- Collection
+ Collection
>
);
}
+ case 'folder-settings': {
+ return (
+
+
+ {tabName || 'Folder'}
+
+ );
+ }
case 'variables': {
return (
<>
- Variables
+ Variables
>
);
}
@@ -24,7 +32,7 @@ const SpecialTab = ({ handleCloseClick, type }) => {
return (
<>
- Runner
+ Runner
>
);
}
@@ -33,7 +41,7 @@ const SpecialTab = ({ handleCloseClick, type }) => {
return (
<>
- {getTabInfo(type)}
+ {getTabInfo(type, tabName)}
handleCloseClick(e)}>
+ {isFolder && (
+ {
+ dropdownTippyRef.current.hide();
+ viewFolderSettings();
+ }}
+ >
+ Settings
+
+ )}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index 526b43a1e..483456a91 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -14,10 +14,10 @@ import {
findParentItemInCollection,
getItemsToResequence,
isItemAFolder,
+ refreshUidsInItem,
isItemARequest,
moveCollectionItem,
moveCollectionItemToRootOfCollection,
- refreshUidsInItem,
transformRequestToSaveToFilesystem
} from 'utils/collections';
import { uuid, waitForNextTick } from 'utils/common';
@@ -41,6 +41,7 @@ import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
import { resolveRequestFilename } from 'utils/common/platform';
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
+import { name } from 'file-loader';
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
const state = getState();
@@ -143,7 +144,42 @@ export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => {
});
};
-export const sendCollectionOauth2Request = (collectionUid) => (dispatch, getState) => {
+export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState) => {
+ const state = getState();
+ const collection = findCollectionByUid(state.collections.collections, collectionUid);
+ const folder = findItemInCollection(collection, folderUid);
+
+ return new Promise((resolve, reject) => {
+ if (!collection) {
+ return reject(new Error('Collection not found'));
+ }
+
+ if (!folder) {
+ return reject(new Error('Folder not found'));
+ }
+ console.log(collection);
+
+ const { ipcRenderer } = window;
+
+ const folderData = {
+ name: folder.name,
+ pathname: folder.pathname,
+ root: folder.root
+ };
+ console.log(folderData);
+
+ ipcRenderer
+ .invoke('renderer:save-folder-root', folderData)
+ .then(() => toast.success('Folder Settings saved successfully'))
+ .then(resolve)
+ .catch((err) => {
+ toast.error('Failed to save folder settings!');
+ reject(err);
+ });
+ });
+};
+
+export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
@@ -156,7 +192,10 @@ export const sendCollectionOauth2Request = (collectionUid) => (dispatch, getStat
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
- _sendCollectionOauth2Request(collection, environment, collectionCopy.collectionVariables)
+ const externalSecrets = getExternalCollectionSecretsForActiveEnvironment({ collection });
+ const secretVariables = getFormattedCollectionSecretVariables({ externalSecrets });
+
+ _sendCollectionOauth2Request(collection, environment, collectionCopy.collectionVariables, itemUid, secretVariables)
.then((response) => {
if (response?.data?.error) {
toast.error(response?.data?.error);
@@ -184,9 +223,8 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
const itemCopy = cloneDeep(item || {});
const collectionCopy = cloneDeep(collection);
- const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
-
- sendNetworkRequest(itemCopy, collection, environment, collectionCopy.collectionVariables)
+ const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid);
+ sendNetworkRequest(itemCopy, collectionCopy, environment, collectionCopy.collectionVariables)
.then((response) => {
return dispatch(
responseReceived({
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 2a851c238..90927fa8e 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -1,4 +1,5 @@
import { uuid } from 'utils/common';
+import path from 'path';
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, debounce } from 'lodash';
import { createSlice } from '@reduxjs/toolkit';
import {
@@ -33,6 +34,8 @@ export const collectionsSlice = createSlice({
collection.settingsSelectedTab = 'headers';
+ collection.folderLevelSettingsSelectedTab = {};
+
// TODO: move this to use the nextAction approach
// last action is used to track the last action performed on the collection
// this is optional
@@ -89,7 +92,7 @@ export const collectionsSlice = createSlice({
}
},
updateSettingsSelectedTab: (state, action) => {
- const { collectionUid, tab } = action.payload;
+ const { collectionUid, folderUid, tab } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
@@ -97,6 +100,19 @@ export const collectionsSlice = createSlice({
collection.settingsSelectedTab = tab;
}
},
+ updatedFolderSettingsSelectedTab: (state, action) => {
+ const { collectionUid, folderUid, tab } = action.payload;
+
+ const collection = findCollectionByUid(state.collections, collectionUid);
+
+ if (collection) {
+ const folder = findItemInCollection(collection, folderUid);
+
+ if (folder) {
+ collection.folderLevelSettingsSelectedTab[folderUid] = tab;
+ }
+ }
+ },
collectionUnlinkEnvFileEvent: (state, action) => {
const { data: environment, meta } = action.payload;
const collection = findCollectionByUid(state.collections, meta.collectionUid);
@@ -1114,6 +1130,137 @@ export const collectionsSlice = createSlice({
set(collection, 'root.docs', action.payload.docs);
}
},
+ addFolderHeader: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null;
+ if (folder) {
+ const headers = get(folder, 'root.request.headers', []);
+ headers.push({
+ uid: uuid(),
+ name: '',
+ value: '',
+ description: '',
+ enabled: true
+ });
+ set(folder, 'root.request.headers', headers);
+ }
+ },
+ updateFolderHeader: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null;
+ if (folder) {
+ const headers = get(folder, 'root.request.headers', []);
+ const header = find(headers, (h) => h.uid === action.payload.header.uid);
+ if (header) {
+ header.name = action.payload.header.name;
+ header.value = action.payload.header.value;
+ header.description = action.payload.header.description;
+ header.enabled = action.payload.header.enabled;
+ }
+ }
+ },
+ deleteFolderHeader: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null;
+ if (folder) {
+ let headers = get(folder, 'root.request.headers', []);
+ headers = filter(headers, (h) => h.uid !== action.payload.headerUid);
+ 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;
+ if (folder) {
+ set(folder, 'root.request.script.req', action.payload.script);
+ }
+ },
+ updateFolderResponseScript: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null;
+ if (folder) {
+ set(folder, 'root.request.script.res', action.payload.script);
+ }
+ },
+ updateFolderTests: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null;
+ if (folder) {
+ set(folder, 'root.request.tests', action.payload.tests);
+ }
+ },
addCollectionHeader: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -1155,8 +1302,8 @@ export const collectionsSlice = createSlice({
collectionAddFileEvent: (state, action) => {
const file = action.payload.file;
const isCollectionRoot = file.meta.collectionRoot ? true : false;
+ const isFolderRoot = file.meta.folderRoot ? true : false;
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
-
if (isCollectionRoot) {
if (collection) {
collection.root = file.data;
@@ -1164,6 +1311,15 @@ export const collectionsSlice = createSlice({
return;
}
+ if (isFolderRoot) {
+ const folderPath = path.dirname(file.meta.pathname);
+ const folderItem = findItemInCollectionByPathname(collection, folderPath);
+ if (folderItem) {
+ folderItem.root = file.data;
+ }
+ return;
+ }
+
if (collection) {
const dirname = getDirectoryName(file.meta.pathname);
const subDirectories = getSubdirectoriesFromRoot(collection.pathname, dirname);
@@ -1187,7 +1343,7 @@ export const collectionsSlice = createSlice({
currentSubItems = childItem.items;
}
- if (!currentSubItems.find((f) => f.name === file.meta.name)) {
+ if (file.meta.name != 'folder.bru' && !currentSubItems.find((f) => f.name === file.meta.name)) {
// this happens when you rename a file
// the add event might get triggered first, before the unlink event
// this results in duplicate uids causing react renderer to go mad
@@ -1474,6 +1630,7 @@ export const {
sortCollections,
updateLastAction,
updateSettingsSelectedTab,
+ updatedFolderSettingsSelectedTab,
collectionUnlinkEnvFileEvent,
saveEnvironment,
selectEnvironment,
@@ -1521,6 +1678,15 @@ export const {
addVar,
updateVar,
deleteVar,
+ addFolderHeader,
+ updateFolderHeader,
+ deleteFolderHeader,
+ addFolderVar,
+ updateFolderVar,
+ deleteFolderVar,
+ updateFolderRequestScript,
+ updateFolderResponseScript,
+ updateFolderTests,
addCollectionHeader,
updateCollectionHeader,
deleteCollectionHeader,
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
index 74c503dad..b64a71fad 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
@@ -38,7 +38,8 @@ export const tabsSlice = createSlice({
requestPaneWidth: null,
requestPaneTab: action.payload.requestPaneTab || 'params',
responsePaneTab: 'response',
- type: action.payload.type || 'request'
+ type: action.payload.type || 'request',
+ ...(action.payload.folderUid ? { folderUid: action.payload.folderUid } : {})
});
state.activeTabUid = action.payload.uid;
},
diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js
index 35192b128..e51daa552 100644
--- a/packages/bruno-cli/src/commands/run.js
+++ b/packages/bruno-cli/src/commands/run.js
@@ -179,6 +179,17 @@ const getCollectionRoot = (dir) => {
return collectionBruToJson(content);
};
+const getFolderRoot = (dir) => {
+ const folderRootPath = path.join(dir, 'folder.bru');
+ const exists = fs.existsSync(folderRootPath);
+ if (!exists) {
+ return {};
+ }
+
+ const content = fs.readFileSync(folderRootPath, 'utf8');
+ return collectionBruToJson(content);
+};
+
const builder = async (yargs) => {
yargs
.option('r', {
diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js
index 441bba3b2..2fbd4cc98 100644
--- a/packages/bruno-electron/src/app/watcher.js
+++ b/packages/bruno-electron/src/app/watcher.js
@@ -40,7 +40,6 @@ const isBruEnvironmentConfig = (pathname, collectionPath) => {
const isCollectionRootBruFile = (pathname, collectionPath) => {
const dirname = path.dirname(pathname);
const basename = path.basename(pathname);
-
return dirname === collectionPath && basename === 'collection.bru';
};
@@ -240,6 +239,32 @@ const add = async (win, pathname, collectionUid, collectionPath) => {
}
}
+ // Is this a folder.bru file?
+ if (path.basename(pathname) === 'folder.bru') {
+ console.log('folder.bru file detected');
+ const file = {
+ meta: {
+ collectionUid,
+ pathname,
+ name: path.basename(pathname),
+ folderRoot: true
+ }
+ };
+
+ try {
+ let bruContent = fs.readFileSync(pathname, 'utf8');
+
+ file.data = collectionBruToJson(bruContent);
+
+ hydrateBruCollectionFileWithUuid(file.data);
+ win.webContents.send('main:collection-tree-updated', 'addFile', file);
+ return;
+ } catch (err) {
+ console.error(err);
+ return;
+ }
+ }
+
if (hasBruExtension(pathname)) {
const file = {
meta: {
@@ -334,7 +359,6 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
let bruContent = fs.readFileSync(pathname, 'utf8');
file.data = collectionBruToJson(bruContent);
-
hydrateBruCollectionFileWithUuid(file.data);
win.webContents.send('main:collection-tree-updated', 'change', file);
return;
@@ -356,6 +380,7 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
const bru = fs.readFileSync(pathname, 'utf8');
file.data = bruToJson(bru);
+
hydrateRequestWithUuid(file.data, pathname);
win.webContents.send('main:collection-tree-updated', 'change', file);
} catch (err) {
diff --git a/packages/bruno-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js
index 41c35d53d..07041b93b 100644
--- a/packages/bruno-electron/src/bru/index.js
+++ b/packages/bruno-electron/src/bru/index.js
@@ -24,6 +24,16 @@ const collectionBruToJson = (bru) => {
docs: _.get(json, 'docs', '')
};
+ // add meta if it exists
+ // this is only for folder bru file
+ // in the future, all of this will be replaced by standard bru lang
+ if (json.meta) {
+ transformedJson.meta = {
+ name: json.meta.name,
+ seq: json.meta.seq
+ };
+ }
+
return transformedJson;
} catch (error) {
return Promise.reject(error);
@@ -42,12 +52,22 @@ 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', '')
};
+ // add meta if it exists
+ // this is only for folder bru file
+ // in the future, all of this will be replaced by standard bru lang
+ if (json.meta) {
+ collectionBruJson.meta = {
+ name: json.meta.name,
+ seq: json.meta.seq
+ };
+ }
+
return _jsonToCollectionBru(collectionBruJson);
} catch (error) {
return Promise.reject(error);
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index 5f8b63c3b..029129308 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -152,6 +152,21 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});
+ ipcMain.handle('renderer:save-folder-root', async (event, folder) => {
+ try {
+ const { name: folderName, root: folderRoot, pathname: folderPathname } = folder;
+ const folderBruFilePath = path.join(folderPathname, 'folder.bru');
+
+ folderRoot.meta = {
+ name: folderName
+ };
+
+ const content = jsonToCollectionBru(folderRoot);
+ await writeFile(folderBruFilePath, content);
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ });
ipcMain.handle('renderer:save-collection-root', async (event, collectionPathname, collectionRoot) => {
try {
const collectionBruFilePath = path.join(collectionPathname, 'collection.bru');
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index 4d4e8d8e4..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) {
@@ -461,8 +461,7 @@ const registerNetworkIpc = (mainWindow) => {
});
const collectionRoot = get(collection, 'root', {});
- const _request = item.draft ? item.draft.request : item.request;
- const request = prepareRequest(_request, collectionRoot, collectionPath);
+ const request = prepareRequest(item, collection);
const envVars = getEnvVars(environment);
const processEnvVars = getProcessEnvVars(collectionUid);
const brunoConfig = getBrunoConfig(collectionUid);
@@ -603,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();
@@ -900,8 +899,7 @@ const registerNetworkIpc = (mainWindow) => {
...eventData
});
- const _request = item.draft ? item.draft.request : item.request;
- const request = prepareRequest(_request, collectionRoot, collectionPath);
+ const request = prepareRequest(item, collection);
const requestUid = uuid();
const processEnvVars = getProcessEnvVars(collectionUid);
diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js
index 73b8bf71c..6f0331c2a 100644
--- a/packages/bruno-electron/src/ipc/network/prepare-request.js
+++ b/packages/bruno-electron/src/ipc/network/prepare-request.js
@@ -1,9 +1,159 @@
-const { get, each, filter, extend } = require('lodash');
+const os = require('os');
+const { get, each, filter, extend, compact } = require('lodash');
const decomment = require('decomment');
var JSONbig = require('json-bigint');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
+const { getTreePathFromCollectionToItem } = require('../../utils/collection');
+
+const mergeFolderLevelHeaders = (request, requestTreePath) => {
+ let folderHeaders = new Map();
+
+ for (let i of requestTreePath) {
+ if (i.type === 'folder') {
+ let headers = get(i, 'root.request.headers', []);
+ headers.forEach((header) => {
+ if (header.enabled) {
+ 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);
+ }
+ });
+ }
+ }
+
+ let mergedFolderHeaders = Array.from(folderHeaders, ([name, value]) => ({ name, value, enabled: true }));
+ let requestHeaders = request.headers || [];
+ let requestHeadersMap = new Map();
+
+ for (let header of requestHeaders) {
+ if (header.enabled) {
+ requestHeadersMap.set(header.name, header.value);
+ }
+ }
+
+ mergedFolderHeaders.forEach((header) => {
+ requestHeadersMap.set(header.name, header.value);
+ });
+
+ 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 = '';
+ for (let i of requestTreePath) {
+ if (i.type === 'folder') {
+ let preReqScript = get(i, 'root.request.script.req', '');
+ if (preReqScript && preReqScript.trim() !== '') {
+ folderCombinedPreReqScript.push(preReqScript);
+ }
+
+ let postResScript = get(i, 'root.request.script.res', '');
+ if (postResScript && postResScript.trim() !== '') {
+ folderCombinedPostResScript.push(postResScript);
+ }
+
+ 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);
+ }
+
+ if (folderCombinedPostResScript.length) {
+ request.script.res = compact([request?.script?.res || '', ...folderCombinedPostResScript.reverse()]).join(os.EOL);
+ }
+
+ if (folderCombinedTests.length) {
+ request.tests = `${request?.tests} \n ${folderCombinedTests}`;
+ }
+};
const parseFormData = (datas, collectionPath) => {
// make axios work in node using form data
@@ -133,7 +283,10 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
return axiosRequest;
};
-const prepareRequest = (request, collectionRoot, collectionPath) => {
+const prepareRequest = (item, collection) => {
+ const request = item.draft ? item.draft.request : item.request;
+ const collectionRoot = get(collection, 'root', {});
+ const collectionPath = collection.pathname;
const headers = {};
let contentTypeDefined = false;
let url = request.url;
@@ -148,6 +301,13 @@ const prepareRequest = (request, collectionRoot, collectionPath) => {
}
});
+ const requestTreePath = getTreePathFromCollectionToItem(collection, item);
+ if (requestTreePath && requestTreePath.length > 0) {
+ mergeFolderLevelHeaders(request, requestTreePath);
+ mergeFolderLevelScripts(request, requestTreePath);
+ mergeFolderLevelVars(request, requestTreePath);
+ }
+
each(request.headers, (h) => {
if (h.enabled && h.name.length > 0) {
headers[h.name] = h.value;
diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js
new file mode 100644
index 000000000..5ec40a680
--- /dev/null
+++ b/packages/bruno-electron/src/utils/collection.js
@@ -0,0 +1,57 @@
+const each = require('lodash/each');
+const find = require('lodash/find');
+
+const flattenItems = (items = []) => {
+ const flattenedItems = [];
+
+ const flatten = (itms, flattened) => {
+ each(itms, (i) => {
+ flattened.push(i);
+
+ if (i.items && i.items.length) {
+ flatten(i.items, flattened);
+ }
+ });
+ };
+
+ flatten(items, flattenedItems);
+
+ return flattenedItems;
+};
+
+const findItem = (items = [], itemUid) => {
+ return find(items, (i) => i.uid === itemUid);
+};
+
+const findItemInCollection = (collection, itemUid) => {
+ let flattenedItems = flattenItems(collection.items);
+
+ return findItem(flattenedItems, itemUid);
+};
+
+const findParentItemInCollection = (collection, itemUid) => {
+ let flattenedItems = flattenItems(collection.items);
+
+ return find(flattenedItems, (item) => {
+ return item.items && find(item.items, (i) => i.uid === itemUid);
+ });
+};
+
+const getTreePathFromCollectionToItem = (collection, _item) => {
+ let path = [];
+ let item = findItemInCollection(collection, _item.uid);
+ while (item) {
+ path.unshift(item);
+ item = findParentItemInCollection(collection, item.uid);
+ }
+
+ return path;
+};
+
+module.exports = {
+ flattenItems,
+ findItem,
+ findItemInCollection,
+ findParentItemInCollection,
+ getTreePathFromCollectionToItem
+};
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);
});
});