From 1ce8d707f1b4cc461235333174b3bdcb290c9c40 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 9 Oct 2023 06:18:05 +0530 Subject: [PATCH] feat(#334): collection level headers, auth, scripts and tests --- .../Auth/AuthMode/StyledWrapper.js | 28 + .../CollectionSettings/Auth/AuthMode/index.js | 69 ++ .../Auth/BasicAuth/StyledWrapper.js | 16 + .../Auth/BasicAuth/index.js | 71 ++ .../Auth/BearerAuth/StyledWrapper.js | 16 + .../Auth/BearerAuth/index.js | 46 ++ .../CollectionSettings/Auth/StyledWrapper.js | 5 + .../CollectionSettings/Auth/index.js | 42 ++ .../Headers/StyledWrapper.js | 56 ++ .../CollectionSettings/Headers/index.js | 151 +++++ .../CollectionSettings/ProxySettings/index.js | 17 +- .../Script/StyledWrapper.js | 13 + .../CollectionSettings/Script/index.js | 73 +++ .../CollectionSettings/StyledWrapper.js | 26 + .../CollectionSettings/Tests/StyledWrapper.js | 5 + .../CollectionSettings/Tests/index.js | 47 ++ .../components/CollectionSettings/index.js | 64 +- .../RequestTabs/RequestTab/SpecialTab.js | 2 +- .../bruno-app/src/providers/Hotkeys/index.js | 3 +- .../ReduxStore/slices/collections/actions.js | 23 + .../ReduxStore/slices/collections/index.js | 118 ++++ packages/bruno-app/src/styles/globals.css | 13 +- packages/bruno-app/src/utils/network/index.js | 2 +- packages/bruno-electron/src/app/watcher.js | 72 ++- packages/bruno-electron/src/bru/index.js | 57 +- packages/bruno-electron/src/ipc/collection.js | 13 +- .../bruno-electron/src/ipc/network/index.js | 609 +++++++++--------- .../src/ipc/network/prepare-request.js | 30 +- packages/bruno-lang/src/index.js | 16 +- .../bruno-lang/v2/src/jsonToCollectionBru.js | 4 +- readme.md | 1 + 31 files changed, 1380 insertions(+), 328 deletions(-) create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Auth/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Headers/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Headers/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Script/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Script/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Tests/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Tests/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/StyledWrapper.js new file mode 100644 index 000000000..cdbdf8424 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/StyledWrapper.js @@ -0,0 +1,28 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + font-size: 0.8125rem; + + .auth-mode-selector { + background: transparent; + + .auth-mode-label { + color: ${(props) => props.theme.colors.text.yellow}; + } + + .dropdown-item { + padding: 0.2rem 0.6rem !important; + } + + .label-item { + padding: 0.2rem 0.6rem !important; + } + } + + .caret { + color: rgb(140, 140, 140); + fill: rgb(140 140 140); + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js new file mode 100644 index 000000000..2e0abe572 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js @@ -0,0 +1,69 @@ +import React, { useRef, forwardRef } from 'react'; +import get from 'lodash/get'; +import { IconCaretDown } from '@tabler/icons'; +import Dropdown from 'components/Dropdown'; +import { useDispatch } from 'react-redux'; +import { updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections'; +import { humanizeRequestAuthMode } from 'utils/collections'; +import StyledWrapper from './StyledWrapper'; + +const AuthMode = ({ collection }) => { + const dispatch = useDispatch(); + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + const authMode = get(collection, 'root.request.auth.mode'); + + const Icon = forwardRef((props, ref) => { + return ( +
+ {humanizeRequestAuthMode(authMode)} +
+ ); + }); + + const onModeChange = (value) => { + dispatch( + updateCollectionAuthMode({ + collectionUid: collection.uid, + mode: value + }) + ); + }; + + return ( + +
+ } placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + onModeChange('basic'); + }} + > + Basic Auth +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('bearer'); + }} + > + Bearer Token +
+
{ + dropdownTippyRef.current.hide(); + onModeChange('none'); + }} + > + No Auth +
+
+
+
+ ); +}; +export default AuthMode; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/StyledWrapper.js new file mode 100644 index 000000000..c2bb5d207 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/StyledWrapper.js @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js new file mode 100644 index 000000000..b09cf1175 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js @@ -0,0 +1,71 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const BasicAuth = ({ collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const basicAuth = get(collection, 'root.request.auth.basic', {}); + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const handleUsernameChange = (username) => { + dispatch( + updateCollectionAuth({ + mode: 'basic', + collectionUid: collection.uid, + content: { + username: username, + password: basicAuth.password + } + }) + ); + }; + + const handlePasswordChange = (password) => { + dispatch( + updateCollectionAuth({ + mode: 'basic', + collectionUid: collection.uid, + content: { + username: basicAuth.username, + password: password + } + }) + ); + }; + + return ( + + +
+ handleUsernameChange(val)} + collection={collection} + /> +
+ + +
+ handlePasswordChange(val)} + collection={collection} + /> +
+
+ ); +}; + +export default BasicAuth; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/StyledWrapper.js new file mode 100644 index 000000000..c2bb5d207 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/StyledWrapper.js @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js new file mode 100644 index 000000000..701a4d7fa --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js @@ -0,0 +1,46 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const BearerAuth = ({ collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const bearerToken = get(collection, 'root.request.auth.bearer.token'); + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const handleTokenChange = (token) => { + dispatch( + updateCollectionAuth({ + mode: 'bearer', + collectionUid: collection.uid, + content: { + token: token + } + }) + ); + }; + + return ( + + +
+ handleTokenChange(val)} + collection={collection} + /> +
+
+ ); +}; + +export default BearerAuth; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/StyledWrapper.js new file mode 100644 index 000000000..e49220854 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/StyledWrapper.js @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div``; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js new file mode 100644 index 000000000..fe2fd5b33 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js @@ -0,0 +1,42 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useDispatch } from 'react-redux'; +import AuthMode from './AuthMode'; +import BearerAuth from './BearerAuth'; +import BasicAuth from './BasicAuth'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const Auth = ({ collection }) => { + const authMode = get(collection, 'root.request.auth.mode'); + const dispatch = useDispatch(); + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const getAuthView = () => { + switch (authMode) { + case 'basic': { + return ; + } + case 'bearer': { + return ; + } + } + }; + + return ( + +
+ +
+ {getAuthView()} + +
+ +
+
+ ); +}; +export default Auth; diff --git a/packages/bruno-app/src/components/CollectionSettings/Headers/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Headers/StyledWrapper.js new file mode 100644 index 000000000..9f723cb81 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/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/CollectionSettings/Headers/index.js b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js new file mode 100644 index 000000000..9ce78bc1c --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js @@ -0,0 +1,151 @@ +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 { + addCollectionHeader, + updateCollectionHeader, + deleteCollectionHeader +} from 'providers/ReduxStore/slices/collections'; +import { saveCollectionRoot } 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 }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + const headers = get(collection, 'root.request.headers', []); + + const addHeader = () => { + dispatch( + addCollectionHeader({ + collectionUid: collection.uid + }) + ); + }; + + const handleSave = () => dispatch(saveCollectionRoot(collection.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( + updateCollectionHeader({ + header: header, + collectionUid: collection.uid + }) + ); + }; + + const handleRemoveHeader = (header) => { + dispatch( + deleteCollectionHeader({ + headerUid: header.uid, + collectionUid: collection.uid + }) + ); + }; + + return ( + + + + + + + + + + + {headers && headers.length + ? headers.map((header) => { + return ( + + + + + + ); + }) + : null} + +
NameValue
+ + handleHeaderValueChange( + { + target: { + value: newValue + } + }, + header, + 'name' + ) + } + autocomplete={headerAutoCompleteList} + collection={collection} + /> + + + handleHeaderValueChange( + { + target: { + value: newValue + } + }, + header, + 'value' + ) + } + collection={collection} + /> + +
+ handleHeaderValueChange(e, header, 'enabled')} + /> + +
+
+ + +
+ +
+
+ ); +}; +export default Headers; diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index c3746f566..618c3fa84 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -49,15 +49,14 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { return ( -

Proxy Settings

-
+
-
+
@@ -86,7 +85,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
-
+
@@ -106,7 +105,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
{formik.errors.hostname}
) : null}
-
+
@@ -124,7 +123,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { /> {formik.touched.port && formik.errors.port ?
{formik.errors.port}
: null}
-
+
@@ -136,7 +135,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { />
-
+
@@ -156,7 +155,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
{formik.errors.auth.username}
) : null}
-
+
@@ -178,7 +177,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
-
diff --git a/packages/bruno-app/src/components/CollectionSettings/Script/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Script/StyledWrapper.js new file mode 100644 index 000000000..66ba1ed3d --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/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/CollectionSettings/Script/index.js b/packages/bruno-app/src/components/CollectionSettings/Script/index.js new file mode 100644 index 000000000..7cfff272e --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Script/index.js @@ -0,0 +1,73 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useDispatch } from 'react-redux'; +import CodeEditor from 'components/CodeEditor'; +import { updateCollectionRequestScript, updateCollectionResponseScript } from 'providers/ReduxStore/slices/collections'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import { useTheme } from 'providers/Theme'; +import StyledWrapper from './StyledWrapper'; + +const Script = ({ collection }) => { + const dispatch = useDispatch(); + const requestScript = get(collection, 'root.request.script.req', ''); + const responseScript = get(collection, 'root.request.script.res', ''); + + const { storedTheme } = useTheme(); + + const onRequestScriptEdit = (value) => { + dispatch( + updateCollectionRequestScript({ + script: value, + collectionUid: collection.uid + }) + ); + }; + + const onResponseScriptEdit = (value) => { + dispatch( + updateCollectionResponseScript({ + script: value, + collectionUid: collection.uid + }) + ); + }; + + const handleSave = () => { + dispatch(saveCollectionRoot(collection.uid)); + }; + + return ( + +
+
Pre Request
+ +
+
+
Post Response
+ +
+ +
+ +
+
+ ); +}; + +export default Script; diff --git a/packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js index e4d976b0d..b88a31e0d 100644 --- a/packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js +++ b/packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js @@ -1,6 +1,32 @@ 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 { diff --git a/packages/bruno-app/src/components/CollectionSettings/Tests/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Tests/StyledWrapper.js new file mode 100644 index 000000000..ec278887d --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/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/CollectionSettings/Tests/index.js b/packages/bruno-app/src/components/CollectionSettings/Tests/index.js new file mode 100644 index 000000000..469d2b409 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Tests/index.js @@ -0,0 +1,47 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useDispatch } from 'react-redux'; +import CodeEditor from 'components/CodeEditor'; +import { updateCollectionTests } from 'providers/ReduxStore/slices/collections'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import { useTheme } from 'providers/Theme'; +import StyledWrapper from './StyledWrapper'; + +const Tests = ({ collection }) => { + const dispatch = useDispatch(); + const tests = get(collection, 'root.request.tests', ''); + + const { storedTheme } = useTheme(); + + const onEdit = (value) => { + dispatch( + updateCollectionTests({ + tests: value, + collectionUid: collection.uid + }) + ); + }; + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + return ( + + + +
+ +
+
+ ); +}; + +export default Tests; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index 98d8aed48..eaf44e758 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -1,14 +1,29 @@ import React from 'react'; +import classnames from 'classnames'; import get from 'lodash/get'; import cloneDeep from 'lodash/cloneDeep'; import toast from 'react-hot-toast'; import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions'; +import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections'; import { useDispatch } from 'react-redux'; import ProxySettings from './ProxySettings'; +import Headers from './Headers'; +import Auth from './Auth'; +import Script from './Script'; +import Test from './Tests'; import StyledWrapper from './StyledWrapper'; const CollectionSettings = ({ collection }) => { const dispatch = useDispatch(); + const tab = collection.settingsSelectedTab; + const setTab = (tab) => { + dispatch( + updateSettingsSelectedTab({ + collectionUid: collection.uid, + tab + }) + ); + }; const proxyConfig = get(collection, 'brunoConfig.proxy', {}); @@ -22,11 +37,52 @@ const CollectionSettings = ({ collection }) => { .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); }; - return ( - -

Collection Settings

+ const getTabPanel = (tab) => { + switch (tab) { + case 'headers': { + return ; + } + case 'auth': { + return ; + } + case 'script': { + return