mirror of
https://github.com/usebruno/bruno.git
synced 2024-12-23 07:09:01 +01:00
Folder level Headers, Scripts and Tests (#2529)
* [Feature] : Settings on folder level (#1334) * feat(folder_settings): enable settings tab from folder, currently not using folder.bru * feat(folder_settings): read and write in folder settings only in headers, ignore folder.bru file il requests list * feat(folder_settings): merge collection and folder settings when sending network request * feat(folder_settings): remove console, testing headers merging working fine * feat(folder_settings): add missing endl for prettier check, remove redundant imports --------- Co-authored-by: Baptiste POULAIN <baptistepoulain@MAC882.local> Co-authored-by: Anoop M D <anoop.md1421@gmail.com> * feat: folder level scripts and tests * feat: folder level variables (#2530) --------- Co-authored-by: Baptiste Poulain <64689165+bpoulaindev@users.noreply.github.com> Co-authored-by: Baptiste POULAIN <baptistepoulain@MAC882.local> Co-authored-by: lohit <lohit.jiddimani@gmail.com>
This commit is contained in:
parent
fd57b2ce94
commit
45ff36d394
79
package-lock.json
generated
79
package-lock.json
generated
@ -9996,32 +9996,6 @@
|
|||||||
"graphql": ">=0.11 <=16"
|
"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": {
|
"node_modules/har-schema": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@ -10433,7 +10407,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
@ -12985,6 +12958,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/neo-async": {
|
"node_modules/neo-async": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/new-github-issue-url": {
|
"node_modules/new-github-issue-url": {
|
||||||
@ -16443,6 +16417,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -17737,17 +17712,6 @@
|
|||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/underscore": {
|
||||||
"version": "1.6.0"
|
"version": "1.6.0"
|
||||||
},
|
},
|
||||||
@ -18363,10 +18327,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/wordwrap": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -19753,6 +19713,7 @@
|
|||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"http-proxy-agent": "^7.0.0",
|
"http-proxy-agent": "^7.0.0",
|
||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.2",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
@ -20806,6 +20767,7 @@
|
|||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@usebruno/common": "0.1.0",
|
||||||
"@usebruno/query": "0.1.0",
|
"@usebruno/query": "0.1.0",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
@ -20815,7 +20777,6 @@
|
|||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-string": "^1.5.0",
|
"chai-string": "^1.5.0",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"handlebars": "^4.7.8",
|
|
||||||
"json-query": "^2.2.2",
|
"json-query": "^2.2.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
@ -25207,6 +25168,7 @@
|
|||||||
"@usebruno/js": {
|
"@usebruno/js": {
|
||||||
"version": "file:packages/bruno-js",
|
"version": "file:packages/bruno-js",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"@usebruno/common": "0.1.0",
|
||||||
"@usebruno/query": "0.1.0",
|
"@usebruno/query": "0.1.0",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
@ -25216,7 +25178,6 @@
|
|||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-string": "^1.5.0",
|
"chai-string": "^1.5.0",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"handlebars": "^4.7.8",
|
|
||||||
"json-query": "^2.2.2",
|
"json-query": "^2.2.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
@ -26162,6 +26123,7 @@
|
|||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"http-proxy-agent": "^7.0.0",
|
"http-proxy-agent": "^7.0.0",
|
||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.2",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
@ -29284,21 +29246,6 @@
|
|||||||
"graphql-ws": {
|
"graphql-ws": {
|
||||||
"version": "5.12.1"
|
"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": {
|
"har-schema": {
|
||||||
"version": "2.0.0"
|
"version": "2.0.0"
|
||||||
},
|
},
|
||||||
@ -29543,7 +29490,6 @@
|
|||||||
},
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
}
|
}
|
||||||
@ -31169,7 +31115,8 @@
|
|||||||
"version": "0.6.3"
|
"version": "0.6.3"
|
||||||
},
|
},
|
||||||
"neo-async": {
|
"neo-async": {
|
||||||
"version": "2.6.2"
|
"version": "2.6.2",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"new-github-issue-url": {
|
"new-github-issue-url": {
|
||||||
"version": "0.2.1"
|
"version": "0.2.1"
|
||||||
@ -33261,7 +33208,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1"
|
"version": "0.6.1",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"source-map-js": {
|
"source-map-js": {
|
||||||
"version": "1.0.2"
|
"version": "1.0.2"
|
||||||
@ -34060,10 +34008,6 @@
|
|||||||
"uc.micro": {
|
"uc.micro": {
|
||||||
"version": "1.0.6"
|
"version": "1.0.6"
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
|
||||||
"version": "3.17.4",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"underscore": {
|
"underscore": {
|
||||||
"version": "1.6.0"
|
"version": "1.6.0"
|
||||||
},
|
},
|
||||||
@ -34458,9 +34402,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"wordwrap": {
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
|
||||||
"wrap-ansi": {
|
"wrap-ansi": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -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;
|
@ -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 (
|
||||||
|
<StyledWrapper className="w-full">
|
||||||
|
<div className="text-xs mb-4 text-muted">
|
||||||
|
Request headers that will be sent with every request inside this folder.
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>Value</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{headers && headers.length
|
||||||
|
? headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<tr key={header.uid}>
|
||||||
|
<td>
|
||||||
|
<SingleLineEditor
|
||||||
|
value={header.name}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleHeaderValueChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header,
|
||||||
|
'name'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
autocomplete={headerAutoCompleteList}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<SingleLineEditor
|
||||||
|
value={header.value}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleHeaderValueChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header,
|
||||||
|
'value'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={header.enabled}
|
||||||
|
tabIndex="-1"
|
||||||
|
className="mr-3 mousetrap"
|
||||||
|
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
|
||||||
|
/>
|
||||||
|
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
|
||||||
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
|
||||||
|
+ Add Header
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Headers;
|
@ -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;
|
@ -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 (
|
||||||
|
<StyledWrapper className="w-full flex flex-col h-full">
|
||||||
|
<div className="text-xs mb-4 text-muted">
|
||||||
|
Pre and post-request scripts that will run before and after any request inside this folder is sent.
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 mt-2">
|
||||||
|
<div className="mb-1 title text-xs">Pre Request</div>
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={requestScript || ''}
|
||||||
|
theme={displayedTheme}
|
||||||
|
onEdit={onRequestScriptEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
font={get(preferences, 'font.codeFont', 'default')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 mt-6">
|
||||||
|
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={responseScript || ''}
|
||||||
|
theme={displayedTheme}
|
||||||
|
onEdit={onResponseScriptEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
font={get(preferences, 'font.codeFont', 'default')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Script;
|
@ -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;
|
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div``;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -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 (
|
||||||
|
<StyledWrapper className="w-full flex flex-col h-full">
|
||||||
|
<div className="text-xs mb-4 text-muted">These tests will run any time a request in this collection is sent.</div>
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={tests || ''}
|
||||||
|
theme={displayedTheme}
|
||||||
|
onEdit={onEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
font={get(preferences, 'font.codeFont', 'default')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tests;
|
@ -0,0 +1,9 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.title {
|
||||||
|
color: var(--color-tab-inactive);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -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;
|
@ -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 (
|
||||||
|
<StyledWrapper className="w-full">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
{varType === 'request' ? (
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span>Value</span>
|
||||||
|
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
) : (
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span>Expr</span>
|
||||||
|
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{vars && vars.length
|
||||||
|
? vars.map((_var) => {
|
||||||
|
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'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={_var.enabled}
|
||||||
|
tabIndex="-1"
|
||||||
|
className="mr-3 mousetrap"
|
||||||
|
onChange={(e) => handleVarChange(e, _var, 'enabled')}
|
||||||
|
/>
|
||||||
|
<button tabIndex="-1" 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={addVar}>
|
||||||
|
+ Add
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default VarsTable;
|
@ -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 (
|
||||||
|
<StyledWrapper className="w-full flex flex-col">
|
||||||
|
<div className="flex-1 mt-2">
|
||||||
|
<div className="mb-1 title text-xs">Pre Request</div>
|
||||||
|
<VarsTable folder={folder} collection={collection} vars={requestVars} varType="request" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||||
|
<VarsTable folder={folder} collection={collection} vars={responseVars} varType="response" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Vars;
|
75
packages/bruno-app/src/components/FolderSettings/index.js
Normal file
75
packages/bruno-app/src/components/FolderSettings/index.js
Normal file
@ -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 <Headers collection={collection} folder={folder} />;
|
||||||
|
}
|
||||||
|
case 'script': {
|
||||||
|
return <Script collection={collection} folder={folder} />;
|
||||||
|
}
|
||||||
|
case 'test': {
|
||||||
|
return <Tests collection={collection} folder={folder} />;
|
||||||
|
}
|
||||||
|
case 'vars': {
|
||||||
|
return <Vars collection={collection} folder={folder} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTabClassname = (tabName) => {
|
||||||
|
return classnames(`tab select-none ${tabName}`, {
|
||||||
|
active: tabName === tab
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="flex flex-col h-full relative px-4 py-4">
|
||||||
|
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||||
|
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||||
|
Headers
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
|
||||||
|
Script
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('test')} role="tab" onClick={() => setTab('test')}>
|
||||||
|
Test
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
|
||||||
|
Vars
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section className={`flex mt-4 h-full`}>{getTabPanel(tab)}</section>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FolderSettings;
|
@ -18,6 +18,7 @@ import CollectionSettings from 'components/CollectionSettings';
|
|||||||
import { DocExplorer } from '@usebruno/graphql-docs';
|
import { DocExplorer } from '@usebruno/graphql-docs';
|
||||||
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import FolderSettings from 'components/FolderSettings';
|
||||||
|
|
||||||
const MIN_LEFT_PANE_WIDTH = 300;
|
const MIN_LEFT_PANE_WIDTH = 300;
|
||||||
const MIN_RIGHT_PANE_WIDTH = 350;
|
const MIN_RIGHT_PANE_WIDTH = 350;
|
||||||
@ -131,6 +132,10 @@ const RequestTabPanel = () => {
|
|||||||
if (focusedTab.type === 'collection-settings') {
|
if (focusedTab.type === 'collection-settings') {
|
||||||
return <CollectionSettings collection={collection} />;
|
return <CollectionSettings collection={collection} />;
|
||||||
}
|
}
|
||||||
|
if (focusedTab.type === 'folder-settings') {
|
||||||
|
const folder = findItemInCollection(collection, focusedTab.folderUid);
|
||||||
|
return <FolderSettings collection={collection} folder={folder} />;
|
||||||
|
}
|
||||||
|
|
||||||
const item = findItemInCollection(collection, activeTabUid);
|
const item = findItemInCollection(collection, activeTabUid);
|
||||||
if (!item || !item.uid) {
|
if (!item || !item.uid) {
|
||||||
|
@ -44,7 +44,7 @@ const CollectionToolBar = ({ collection }) => {
|
|||||||
<div className="flex items-center p-2">
|
<div className="flex items-center p-2">
|
||||||
<div className="flex flex-1 items-center cursor-pointer hover:underline" onClick={viewCollectionSettings}>
|
<div className="flex flex-1 items-center cursor-pointer hover:underline" onClick={viewCollectionSettings}>
|
||||||
<IconFiles size={18} strokeWidth={1.5} />
|
<IconFiles size={18} strokeWidth={1.5} />
|
||||||
<span className="ml-2 mr-4 font-semibold">{collection.name}</span>
|
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 items-center justify-end">
|
<div className="flex flex-1 items-center justify-end">
|
||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
|
@ -1,22 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconVariable, IconSettings, IconRun } from '@tabler/icons';
|
import { IconVariable, IconSettings, IconRun, IconFolder } from '@tabler/icons';
|
||||||
|
|
||||||
const SpecialTab = ({ handleCloseClick, type }) => {
|
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
||||||
const getTabInfo = (type) => {
|
const getTabInfo = (type, tabName) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'collection-settings': {
|
case 'collection-settings': {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
<span className="ml-1">Collection</span>
|
<span className="ml-1 leading-6">Collection</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'folder-settings': {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center flex-nowrap overflow-hidden">
|
||||||
|
<IconFolder size={18} strokeWidth={1.5} className="text-yellow-600 min-w-[18px]" />
|
||||||
|
<span className="ml-1 leading-6 truncate">{tabName || 'Folder'}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
case 'variables': {
|
case 'variables': {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconVariable size={18} strokeWidth={1.5} className="text-yellow-600" />
|
<IconVariable size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
<span className="ml-1">Variables</span>
|
<span className="ml-1 leading-6">Variables</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -24,7 +32,7 @@ const SpecialTab = ({ handleCloseClick, type }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconRun size={18} strokeWidth={1.5} className="text-yellow-600" />
|
<IconRun size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
<span className="ml-1">Runner</span>
|
<span className="ml-1 leading-6">Runner</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -33,7 +41,7 @@ const SpecialTab = ({ handleCloseClick, type }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center tab-label pl-2">{getTabInfo(type)}</div>
|
<div className="flex items-center tab-label pl-2">{getTabInfo(type, tabName)}</div>
|
||||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||||
<path
|
<path
|
||||||
|
@ -13,7 +13,7 @@ import RequestTabNotFound from './RequestTabNotFound';
|
|||||||
import SpecialTab from './SpecialTab';
|
import SpecialTab from './SpecialTab';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const RequestTab = ({ tab, collection }) => {
|
const RequestTab = ({ tab, collection, folderUid }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { storedTheme } = useTheme();
|
const { storedTheme } = useTheme();
|
||||||
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
||||||
@ -80,11 +80,15 @@ const RequestTab = ({ tab, collection }) => {
|
|||||||
|
|
||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
|
||||||
if (['collection-settings', 'variables', 'collection-runner'].includes(tab.type)) {
|
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner'].includes(tab.type)) {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||||
|
{tab.type === 'folder-settings' ? (
|
||||||
|
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} tabName={folder?.name} />
|
||||||
|
) : (
|
||||||
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} />
|
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} />
|
||||||
|
)}
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,6 @@ const RequestTabs = () => {
|
|||||||
'has-chevrons': showChevrons
|
'has-chevrons': showChevrons
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Todo: Must support ephemeral requests
|
// Todo: Must support ephemeral requests
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className={getRootClassname()}>
|
<StyledWrapper className={getRootClassname()}>
|
||||||
@ -111,7 +110,7 @@ const RequestTabs = () => {
|
|||||||
role="tab"
|
role="tab"
|
||||||
onClick={() => handleClick(tab)}
|
onClick={() => handleClick(tab)}
|
||||||
>
|
>
|
||||||
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} />
|
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} folderUid={tab.folderUid} />
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -24,6 +24,7 @@ import { hideHomePage } from 'providers/ReduxStore/slices/app';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import NetworkError from 'components/ResponsePane/NetworkError/index';
|
import NetworkError from 'components/ResponsePane/NetworkError/index';
|
||||||
|
import { uuid } from 'utils/common';
|
||||||
|
|
||||||
const CollectionItem = ({ item, collection, searchText }) => {
|
const CollectionItem = ({ item, collection, searchText }) => {
|
||||||
const tabs = useSelector((state) => state.tabs.tabs);
|
const tabs = useSelector((state) => state.tabs.tabs);
|
||||||
@ -188,6 +189,16 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
toast.error('URL is required');
|
toast.error('URL is required');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const viewFolderSettings = () => {
|
||||||
|
dispatch(
|
||||||
|
addTab({
|
||||||
|
uid: uuid(),
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
folderUid: item.uid,
|
||||||
|
type: 'folder-settings'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
||||||
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
||||||
|
|
||||||
@ -345,6 +356,17 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</div>
|
</div>
|
||||||
|
{isFolder && (
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
viewFolderSettings();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,10 +14,10 @@ import {
|
|||||||
findParentItemInCollection,
|
findParentItemInCollection,
|
||||||
getItemsToResequence,
|
getItemsToResequence,
|
||||||
isItemAFolder,
|
isItemAFolder,
|
||||||
|
refreshUidsInItem,
|
||||||
isItemARequest,
|
isItemARequest,
|
||||||
moveCollectionItem,
|
moveCollectionItem,
|
||||||
moveCollectionItemToRootOfCollection,
|
moveCollectionItemToRootOfCollection,
|
||||||
refreshUidsInItem,
|
|
||||||
transformRequestToSaveToFilesystem
|
transformRequestToSaveToFilesystem
|
||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
import { uuid, waitForNextTick } from 'utils/common';
|
import { uuid, waitForNextTick } from 'utils/common';
|
||||||
@ -41,6 +41,7 @@ import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
|||||||
import { resolveRequestFilename } from 'utils/common/platform';
|
import { resolveRequestFilename } from 'utils/common/platform';
|
||||||
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
||||||
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
||||||
|
import { name } from 'file-loader';
|
||||||
|
|
||||||
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
||||||
const state = 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 state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
|
||||||
@ -156,7 +192,10 @@ export const sendCollectionOauth2Request = (collectionUid) => (dispatch, getStat
|
|||||||
|
|
||||||
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
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) => {
|
.then((response) => {
|
||||||
if (response?.data?.error) {
|
if (response?.data?.error) {
|
||||||
toast.error(response?.data?.error);
|
toast.error(response?.data?.error);
|
||||||
@ -184,9 +223,8 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
|||||||
const itemCopy = cloneDeep(item || {});
|
const itemCopy = cloneDeep(item || {});
|
||||||
const collectionCopy = cloneDeep(collection);
|
const collectionCopy = cloneDeep(collection);
|
||||||
|
|
||||||
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid);
|
||||||
|
sendNetworkRequest(itemCopy, collectionCopy, environment, collectionCopy.collectionVariables)
|
||||||
sendNetworkRequest(itemCopy, collection, environment, collectionCopy.collectionVariables)
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return dispatch(
|
return dispatch(
|
||||||
responseReceived({
|
responseReceived({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
|
import path from 'path';
|
||||||
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, debounce } from 'lodash';
|
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, debounce } from 'lodash';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import {
|
import {
|
||||||
@ -33,6 +34,8 @@ export const collectionsSlice = createSlice({
|
|||||||
|
|
||||||
collection.settingsSelectedTab = 'headers';
|
collection.settingsSelectedTab = 'headers';
|
||||||
|
|
||||||
|
collection.folderLevelSettingsSelectedTab = {};
|
||||||
|
|
||||||
// TODO: move this to use the nextAction approach
|
// TODO: move this to use the nextAction approach
|
||||||
// last action is used to track the last action performed on the collection
|
// last action is used to track the last action performed on the collection
|
||||||
// this is optional
|
// this is optional
|
||||||
@ -89,7 +92,7 @@ export const collectionsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateSettingsSelectedTab: (state, action) => {
|
updateSettingsSelectedTab: (state, action) => {
|
||||||
const { collectionUid, tab } = action.payload;
|
const { collectionUid, folderUid, tab } = action.payload;
|
||||||
|
|
||||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||||
|
|
||||||
@ -97,6 +100,19 @@ export const collectionsSlice = createSlice({
|
|||||||
collection.settingsSelectedTab = tab;
|
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) => {
|
collectionUnlinkEnvFileEvent: (state, action) => {
|
||||||
const { data: environment, meta } = action.payload;
|
const { data: environment, meta } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
||||||
@ -1114,6 +1130,137 @@ export const collectionsSlice = createSlice({
|
|||||||
set(collection, 'root.docs', action.payload.docs);
|
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) => {
|
addCollectionHeader: (state, action) => {
|
||||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
@ -1155,8 +1302,8 @@ export const collectionsSlice = createSlice({
|
|||||||
collectionAddFileEvent: (state, action) => {
|
collectionAddFileEvent: (state, action) => {
|
||||||
const file = action.payload.file;
|
const file = action.payload.file;
|
||||||
const isCollectionRoot = file.meta.collectionRoot ? true : false;
|
const isCollectionRoot = file.meta.collectionRoot ? true : false;
|
||||||
|
const isFolderRoot = file.meta.folderRoot ? true : false;
|
||||||
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
||||||
|
|
||||||
if (isCollectionRoot) {
|
if (isCollectionRoot) {
|
||||||
if (collection) {
|
if (collection) {
|
||||||
collection.root = file.data;
|
collection.root = file.data;
|
||||||
@ -1164,6 +1311,15 @@ export const collectionsSlice = createSlice({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFolderRoot) {
|
||||||
|
const folderPath = path.dirname(file.meta.pathname);
|
||||||
|
const folderItem = findItemInCollectionByPathname(collection, folderPath);
|
||||||
|
if (folderItem) {
|
||||||
|
folderItem.root = file.data;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const dirname = getDirectoryName(file.meta.pathname);
|
const dirname = getDirectoryName(file.meta.pathname);
|
||||||
const subDirectories = getSubdirectoriesFromRoot(collection.pathname, dirname);
|
const subDirectories = getSubdirectoriesFromRoot(collection.pathname, dirname);
|
||||||
@ -1187,7 +1343,7 @@ export const collectionsSlice = createSlice({
|
|||||||
currentSubItems = childItem.items;
|
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
|
// this happens when you rename a file
|
||||||
// the add event might get triggered first, before the unlink event
|
// the add event might get triggered first, before the unlink event
|
||||||
// this results in duplicate uids causing react renderer to go mad
|
// this results in duplicate uids causing react renderer to go mad
|
||||||
@ -1474,6 +1630,7 @@ export const {
|
|||||||
sortCollections,
|
sortCollections,
|
||||||
updateLastAction,
|
updateLastAction,
|
||||||
updateSettingsSelectedTab,
|
updateSettingsSelectedTab,
|
||||||
|
updatedFolderSettingsSelectedTab,
|
||||||
collectionUnlinkEnvFileEvent,
|
collectionUnlinkEnvFileEvent,
|
||||||
saveEnvironment,
|
saveEnvironment,
|
||||||
selectEnvironment,
|
selectEnvironment,
|
||||||
@ -1521,6 +1678,15 @@ export const {
|
|||||||
addVar,
|
addVar,
|
||||||
updateVar,
|
updateVar,
|
||||||
deleteVar,
|
deleteVar,
|
||||||
|
addFolderHeader,
|
||||||
|
updateFolderHeader,
|
||||||
|
deleteFolderHeader,
|
||||||
|
addFolderVar,
|
||||||
|
updateFolderVar,
|
||||||
|
deleteFolderVar,
|
||||||
|
updateFolderRequestScript,
|
||||||
|
updateFolderResponseScript,
|
||||||
|
updateFolderTests,
|
||||||
addCollectionHeader,
|
addCollectionHeader,
|
||||||
updateCollectionHeader,
|
updateCollectionHeader,
|
||||||
deleteCollectionHeader,
|
deleteCollectionHeader,
|
||||||
|
@ -38,7 +38,8 @@ export const tabsSlice = createSlice({
|
|||||||
requestPaneWidth: null,
|
requestPaneWidth: null,
|
||||||
requestPaneTab: action.payload.requestPaneTab || 'params',
|
requestPaneTab: action.payload.requestPaneTab || 'params',
|
||||||
responsePaneTab: 'response',
|
responsePaneTab: 'response',
|
||||||
type: action.payload.type || 'request'
|
type: action.payload.type || 'request',
|
||||||
|
...(action.payload.folderUid ? { folderUid: action.payload.folderUid } : {})
|
||||||
});
|
});
|
||||||
state.activeTabUid = action.payload.uid;
|
state.activeTabUid = action.payload.uid;
|
||||||
},
|
},
|
||||||
|
@ -179,6 +179,17 @@ const getCollectionRoot = (dir) => {
|
|||||||
return collectionBruToJson(content);
|
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) => {
|
const builder = async (yargs) => {
|
||||||
yargs
|
yargs
|
||||||
.option('r', {
|
.option('r', {
|
||||||
|
@ -40,7 +40,6 @@ const isBruEnvironmentConfig = (pathname, collectionPath) => {
|
|||||||
const isCollectionRootBruFile = (pathname, collectionPath) => {
|
const isCollectionRootBruFile = (pathname, collectionPath) => {
|
||||||
const dirname = path.dirname(pathname);
|
const dirname = path.dirname(pathname);
|
||||||
const basename = path.basename(pathname);
|
const basename = path.basename(pathname);
|
||||||
|
|
||||||
return dirname === collectionPath && basename === 'collection.bru';
|
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)) {
|
if (hasBruExtension(pathname)) {
|
||||||
const file = {
|
const file = {
|
||||||
meta: {
|
meta: {
|
||||||
@ -334,7 +359,6 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
let bruContent = fs.readFileSync(pathname, 'utf8');
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
|
|
||||||
file.data = collectionBruToJson(bruContent);
|
file.data = collectionBruToJson(bruContent);
|
||||||
|
|
||||||
hydrateBruCollectionFileWithUuid(file.data);
|
hydrateBruCollectionFileWithUuid(file.data);
|
||||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||||
return;
|
return;
|
||||||
@ -356,6 +380,7 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
|
|
||||||
const bru = fs.readFileSync(pathname, 'utf8');
|
const bru = fs.readFileSync(pathname, 'utf8');
|
||||||
file.data = bruToJson(bru);
|
file.data = bruToJson(bru);
|
||||||
|
|
||||||
hydrateRequestWithUuid(file.data, pathname);
|
hydrateRequestWithUuid(file.data, pathname);
|
||||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -24,6 +24,16 @@ const collectionBruToJson = (bru) => {
|
|||||||
docs: _.get(json, 'docs', '')
|
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;
|
return transformedJson;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
@ -42,12 +52,22 @@ const jsonToCollectionBru = (json) => {
|
|||||||
},
|
},
|
||||||
vars: {
|
vars: {
|
||||||
req: _.get(json, 'request.vars.req', []),
|
req: _.get(json, 'request.vars.req', []),
|
||||||
res: _.get(json, 'request.vars.req', [])
|
res: _.get(json, 'request.vars.res', [])
|
||||||
},
|
},
|
||||||
tests: _.get(json, 'request.tests', ''),
|
tests: _.get(json, 'request.tests', ''),
|
||||||
docs: _.get(json, 'docs', '')
|
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);
|
return _jsonToCollectionBru(collectionBruJson);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -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) => {
|
ipcMain.handle('renderer:save-collection-root', async (event, collectionPathname, collectionRoot) => {
|
||||||
try {
|
try {
|
||||||
const collectionBruFilePath = path.join(collectionPathname, 'collection.bru');
|
const collectionBruFilePath = path.join(collectionPathname, 'collection.bru');
|
||||||
|
@ -418,7 +418,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
|
|
||||||
// run post-response script
|
// run post-response script
|
||||||
let scriptResult;
|
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
|
os.EOL
|
||||||
);
|
);
|
||||||
if (responseScript?.length) {
|
if (responseScript?.length) {
|
||||||
@ -461,8 +461,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const collectionRoot = get(collection, 'root', {});
|
const collectionRoot = get(collection, 'root', {});
|
||||||
const _request = item.draft ? item.draft.request : item.request;
|
const request = prepareRequest(item, collection);
|
||||||
const request = prepareRequest(_request, collectionRoot, collectionPath);
|
|
||||||
const envVars = getEnvVars(environment);
|
const envVars = getEnvVars(environment);
|
||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
@ -603,8 +602,8 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testFile = compact([
|
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);
|
]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
@ -900,8 +899,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
...eventData
|
...eventData
|
||||||
});
|
});
|
||||||
|
|
||||||
const _request = item.draft ? item.draft.request : item.request;
|
const request = prepareRequest(item, collection);
|
||||||
const request = prepareRequest(_request, collectionRoot, collectionPath);
|
|
||||||
const requestUid = uuid();
|
const requestUid = uuid();
|
||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
|
|
||||||
|
@ -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');
|
const decomment = require('decomment');
|
||||||
var JSONbig = require('json-bigint');
|
var JSONbig = require('json-bigint');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
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) => {
|
const parseFormData = (datas, collectionPath) => {
|
||||||
// make axios work in node using form data
|
// make axios work in node using form data
|
||||||
@ -133,7 +283,10 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
|||||||
return axiosRequest;
|
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 = {};
|
const headers = {};
|
||||||
let contentTypeDefined = false;
|
let contentTypeDefined = false;
|
||||||
let url = request.url;
|
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) => {
|
each(request.headers, (h) => {
|
||||||
if (h.enabled && h.name.length > 0) {
|
if (h.enabled && h.name.length > 0) {
|
||||||
headers[h.name] = h.value;
|
headers[h.name] = h.value;
|
||||||
|
57
packages/bruno-electron/src/utils/collection.js
Normal file
57
packages/bruno-electron/src/utils/collection.js
Normal file
@ -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
|
||||||
|
};
|
@ -7,14 +7,14 @@ describe('prepare-request: prepareRequest', () => {
|
|||||||
it('If request body is valid JSON', async () => {
|
it('If request body is valid JSON', async () => {
|
||||||
const body = { mode: 'json', json: '{\n"test": "{{someVar}}" // comment\n}' };
|
const body = { mode: 'json', json: '{\n"test": "{{someVar}}" // comment\n}' };
|
||||||
const expected = { test: '{{someVar}}' };
|
const expected = { test: '{{someVar}}' };
|
||||||
const result = prepareRequest({ body });
|
const result = prepareRequest({ request: { body } }, {});
|
||||||
expect(result.data).toEqual(expected);
|
expect(result.data).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('If request body is not valid JSON', async () => {
|
it('If request body is not valid JSON', async () => {
|
||||||
const body = { mode: 'json', json: '{\n"test": {{someVar}} // comment\n}' };
|
const body = { mode: 'json', json: '{\n"test": {{someVar}} // comment\n}' };
|
||||||
const expected = '{\n"test": {{someVar}} \n}';
|
const expected = '{\n"test": {{someVar}} \n}';
|
||||||
const result = prepareRequest({ body });
|
const result = prepareRequest({ request: { body } }, {});
|
||||||
expect(result.data).toEqual(expected);
|
expect(result.data).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user