From c91fef2264a076c55178c5e8b5aeae6034298e30 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 22 Sep 2023 20:38:45 +0530 Subject: [PATCH 01/10] chore: refactor electron storage related modules --- packages/bruno-electron/src/index.js | 2 +- packages/bruno-electron/src/ipc/collection.js | 2 +- packages/bruno-electron/src/ipc/network/index.js | 2 +- .../src/{app => store}/last-opened-collections.js | 0 packages/bruno-electron/src/{app => store}/preferences.js | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename packages/bruno-electron/src/{app => store}/last-opened-collections.js (100%) rename packages/bruno-electron/src/{app => store}/preferences.js (100%) diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index e3aaae712..42eef5a21 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -5,7 +5,7 @@ const { BrowserWindow, app, Menu } = require('electron'); const { setContentSecurityPolicy } = require('electron-util'); const menuTemplate = require('./app/menu-template'); -const LastOpenedCollections = require('./app/last-opened-collections'); +const LastOpenedCollections = require('./store/last-opened-collections'); const registerNetworkIpc = require('./ipc/network'); const registerCollectionsIpc = require('./ipc/collection'); const Watcher = require('./app/watcher'); diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index fd8975d2e..ff29a2432 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -17,7 +17,7 @@ const { stringifyJson } = require('../utils/common'); const { openCollectionDialog, openCollection } = require('../app/collections'); const { generateUidBasedOnHash } = require('../utils/common'); const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); -const { setPreferences } = require('../app/preferences'); +const { setPreferences } = require('../store/preferences'); const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollections) => { // browse directory diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 4858d61d6..7c5f95e19 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -12,7 +12,7 @@ const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../util const { uuid } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); -const { getPreferences } = require('../../app/preferences'); +const { getPreferences } = require('../../store/preferences'); // override the default escape function to prevent escaping Mustache.escape = function (value) { diff --git a/packages/bruno-electron/src/app/last-opened-collections.js b/packages/bruno-electron/src/store/last-opened-collections.js similarity index 100% rename from packages/bruno-electron/src/app/last-opened-collections.js rename to packages/bruno-electron/src/store/last-opened-collections.js diff --git a/packages/bruno-electron/src/app/preferences.js b/packages/bruno-electron/src/store/preferences.js similarity index 100% rename from packages/bruno-electron/src/app/preferences.js rename to packages/bruno-electron/src/store/preferences.js From e3ce420216aeda12ae9d7f1d481f5d23183a5242 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 23 Sep 2023 02:55:54 +0530 Subject: [PATCH 02/10] feat(#122): supporting process.env vars in UI and electron layer --- .../EnvironmentVariables/StyledWrapper.js | 12 ++- .../EnvironmentVariables/index.js | 15 ++-- .../src/components/Modal/StyledWrapper.js | 4 +- .../SingleLineEditor/StyledWrapper.js | 1 + .../src/components/SingleLineEditor/index.js | 1 + .../VariablesView/VariablesTable/index.js | 17 +++- .../providers/App/useCollectionTreeSync.js | 9 +- .../ReduxStore/slices/collections/index.js | 9 ++ packages/bruno-app/src/styles/_buttons.scss | 1 - packages/bruno-app/src/styles/app.scss | 2 +- packages/bruno-app/src/styles/globals.css | 17 ++-- .../src/utils/codemirror/brunoVarInfo.js | 3 +- .../bruno-app/src/utils/collections/index.js | 7 +- .../bruno-app/src/utils/common/codemirror.js | 11 ++- packages/bruno-electron/package.json | 1 + packages/bruno-electron/src/app/watcher.js | 47 ++++++++++ .../bruno-electron/src/ipc/network/index.js | 65 +++++++++----- .../src/ipc/network/interpolate-vars.js | 43 ++++++++-- .../bruno-electron/src/store/process-env.js | 37 ++++++++ packages/bruno-electron/src/utils/common.js | 31 ++++++- .../bruno-electron/tests/utils/common.spec.js | 85 +++++++++++++++++++ packages/bruno-lang/src/index.js | 5 +- 22 files changed, 367 insertions(+), 56 deletions(-) create mode 100644 packages/bruno-electron/src/store/process-env.js create mode 100644 packages/bruno-electron/tests/utils/common.spec.js diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js index 22872cd46..c0762a441 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js @@ -5,10 +5,20 @@ const Wrapper = styled.div` width: 100%; border-collapse: collapse; font-weight: 600; + table-layout: fixed; thead, td { border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}; + padding: 4px 10px; + + &:nth-child(1) { + width: 30%; + } + + &:nth-child(3) { + width: 70px; + } } thead { @@ -16,7 +26,7 @@ const Wrapper = styled.div` font-size: 0.8125rem; user-select: none; } - td { + thead td { padding: 6px 10px; } } diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js index e3ad6e080..b50c8de6a 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js @@ -2,13 +2,16 @@ import React, { useReducer } from 'react'; import toast from 'react-hot-toast'; import cloneDeep from 'lodash/cloneDeep'; import { IconTrash } from '@tabler/icons'; +import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import reducer from './reducer'; +import SingleLineEditor from 'components/SingleLineEditor'; import StyledWrapper from './StyledWrapper'; const EnvironmentVariables = ({ environment, collection }) => { const dispatch = useDispatch(); + const { storedTheme } = useTheme(); const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] }); const { variables, hasChanges } = state; @@ -86,15 +89,11 @@ const EnvironmentVariables = ({ environment, collection }) => { /> - handleVarChange(e, variable, 'value')} + theme={storedTheme} + onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')} + collection={collection} /> diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index 583bfe2ec..f0cb79353 100644 --- a/packages/bruno-app/src/components/Modal/StyledWrapper.js +++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js @@ -19,7 +19,7 @@ const Wrapper = styled.div` align-items: flex-start; justify-content: center; overflow-y: auto; - z-index: 1003; + z-index: 10; } .bruno-modal-card { @@ -28,7 +28,7 @@ const Wrapper = styled.div` background: var(--color-background-top); border-radius: var(--border-radius); position: relative; - z-index: 1003; + z-index: 10; max-width: calc(100% - var(--spacing-base-unit)); box-shadow: var(--box-shadow-base); display: flex; diff --git a/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js b/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js index fbd05bb53..c63a0f2a1 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js +++ b/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js @@ -19,6 +19,7 @@ const StyledWrapper = styled.div` .CodeMirror-scroll { overflow: hidden !important; + padding-bottom: 50px !important; } .CodeMirror-hscrollbar { diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index 5925b598e..eef157ff9 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -31,6 +31,7 @@ class SingleLineEditor extends Component { brunoVarInfo: { variables: getAllVariables(this.props.collection) }, + scrollbarStyle: null, extraKeys: { Enter: () => { if (this.props.onRun) { diff --git a/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js b/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js index b56cea098..3707359f2 100644 --- a/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js +++ b/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js @@ -1,5 +1,6 @@ import React from 'react'; import forOwn from 'lodash/forOwn'; +import isObject from 'lodash/isObject'; import cloneDeep from 'lodash/cloneDeep'; import { uuid } from 'utils/common'; import StyledWrapper from './StyledWrapper'; @@ -15,6 +16,14 @@ const VariablesTable = ({ variables, collectionVariables }) => { }); }); + const getValueToDisplay = (value) => { + if (value === undefined) { + return ''; + } + + return isObject(value) ? JSON.stringify(value) : value; + }; + return (
@@ -24,7 +33,9 @@ const VariablesTable = ({ variables, collectionVariables }) => { return (
{variable.name}
-
{variable.value}
+
+ {getValueToDisplay(variable.value)} +
); }) @@ -38,7 +49,9 @@ const VariablesTable = ({ variables, collectionVariables }) => { return (
{variable.name}
-
{variable.value}
+
+ {getValueToDisplay(variable.value)} +
); }) diff --git a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js index 1f3c02c6f..858682e82 100644 --- a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js +++ b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js @@ -8,6 +8,7 @@ import { collectionUnlinkDirectoryEvent, collectionUnlinkEnvFileEvent, scriptEnvironmentUpdateEvent, + processEnvUpdateEvent, collectionRenamedEvent, runRequestEvent, runFolderEvent @@ -97,6 +98,10 @@ const useCollectionTreeSync = () => { dispatch(scriptEnvironmentUpdateEvent(val)); }; + const _processEnvUpdate = (val) => { + dispatch(processEnvUpdateEvent(val)); + }; + const _collectionRenamed = (val) => { dispatch(collectionRenamedEvent(val)); }; @@ -119,7 +124,8 @@ const useCollectionTreeSync = () => { const removeListener6 = ipcRenderer.on('main:collection-renamed', _collectionRenamed); const removeListener7 = ipcRenderer.on('main:run-folder-event', _runFolderEvent); const removeListener8 = ipcRenderer.on('main:run-request-event', _runRequestEvent); - const removeListener9 = ipcRenderer.on('main:console-log', (val) => { + const removeListener9 = ipcRenderer.on('main:process-env-update', _processEnvUpdate); + const removeListener10 = ipcRenderer.on('main:console-log', (val) => { console[val.type](...val.args); }); @@ -133,6 +139,7 @@ const useCollectionTreeSync = () => { removeListener7(); removeListener8(); removeListener9(); + removeListener10(); }; }, [isElectron]); }; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index c12b81eed..e1137aae7 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -177,6 +177,14 @@ export const collectionsSlice = createSlice({ collection.collectionVariables = collectionVariables; } }, + processEnvUpdateEvent: (state, action) => { + const { collectionUid, processEnvVariables } = action.payload; + const collection = findCollectionByUid(state.collections, collectionUid); + + if (collection) { + collection.processEnvVariables = processEnvVariables; + } + }, requestCancelled: (state, action) => { const { itemUid, collectionUid } = action.payload; const collection = findCollectionByUid(state.collections, collectionUid); @@ -1158,6 +1166,7 @@ export const { renameItem, cloneItem, scriptEnvironmentUpdateEvent, + processEnvUpdateEvent, requestCancelled, responseReceived, saveRequest, diff --git a/packages/bruno-app/src/styles/_buttons.scss b/packages/bruno-app/src/styles/_buttons.scss index 8b1378917..e69de29bb 100644 --- a/packages/bruno-app/src/styles/_buttons.scss +++ b/packages/bruno-app/src/styles/_buttons.scss @@ -1 +0,0 @@ - diff --git a/packages/bruno-app/src/styles/app.scss b/packages/bruno-app/src/styles/app.scss index 64423a4bb..46e81fc09 100644 --- a/packages/bruno-app/src/styles/app.scss +++ b/packages/bruno-app/src/styles/app.scss @@ -1 +1 @@ -@import "buttons"; +@import 'buttons'; diff --git a/packages/bruno-app/src/styles/globals.css b/packages/bruno-app/src/styles/globals.css index 69f95ddcb..fb8eb5b5f 100644 --- a/packages/bruno-app/src/styles/globals.css +++ b/packages/bruno-app/src/styles/globals.css @@ -1,4 +1,3 @@ - :root { --color-brand: #546de5; --color-text: rgb(52 52 52); @@ -21,7 +20,8 @@ --color-method-head: rgb(52 52 52); } -html, body { +html, +body { margin: 0; padding: 0; font-size: 1rem; @@ -38,15 +38,18 @@ body { font-size: 0.875rem; } -body::-webkit-scrollbar, .CodeMirror-vscrollbar::-webkit-scrollbar { +body::-webkit-scrollbar, +.CodeMirror-vscrollbar::-webkit-scrollbar { width: 0.6rem; } - -body::-webkit-scrollbar-track, .CodeMirror-vscrollbar::-webkit-scrollbar-track { + +body::-webkit-scrollbar-track, +.CodeMirror-vscrollbar::-webkit-scrollbar-track { background-color: #f1f1f1; } - -body::-webkit-scrollbar-thumb, .CodeMirror-vscrollbar::-webkit-scrollbar-thumb { + +body::-webkit-scrollbar-thumb, +.CodeMirror-vscrollbar::-webkit-scrollbar-thumb { background-color: #cdcdcd; border-radius: 5rem; } diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js index 7a1a928b3..50f314dac 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js @@ -8,6 +8,7 @@ let CodeMirror; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; +const { get } = require('lodash'); if (!SERVER_RENDERED) { CodeMirror = require('codemirror'); @@ -20,7 +21,7 @@ if (!SERVER_RENDERED) { // str is of format {{variableName}}, extract variableName // we are seeing that from the gql query editor, the token string is of format variableName const variableName = str.replace('{{', '').replace('}}', '').trim(); - const variableValue = options.variables[variableName]; + const variableValue = get(options.variables, variableName); const into = document.createElement('div'); const descriptionDiv = document.createElement('div'); diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 3a0591324..80fe41dd3 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -542,6 +542,11 @@ export const getAllVariables = (collection) => { return { ...environmentVariables, - ...collection.collectionVariables + ...collection.collectionVariables, + process: { + env: { + ...collection.processEnvVariables + } + } }; }; diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index a565062e9..59daee837 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -1,3 +1,6 @@ +import get from 'lodash/get'; +import isString from 'lodash/isString'; + let CodeMirror; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; @@ -5,6 +8,11 @@ if (!SERVER_RENDERED) { CodeMirror = require('codemirror'); } +const pathFoundInVariables = (path, obj) => { + const value = get(obj, path); + return isString(value); +}; + export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { CodeMirror.defineMode('brunovariables', function (config, parserConfig) { let variablesOverlay = { @@ -15,7 +23,8 @@ export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { while ((ch = stream.next()) != null) { if (ch == '}' && stream.next() == '}') { stream.eat('}'); - if (word in variables) { + let found = pathFoundInVariables(word, variables); + if (found) { return 'variable-valid'; } else { return 'variable-invalid'; diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index d9f89baa3..5ec2fcfc4 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -27,6 +27,7 @@ "form-data": "^4.0.0", "fs-extra": "^10.1.0", "graphql": "^16.6.0", + "handlebars": "^4.7.8", "is-valid-path": "^0.1.1", "lodash": "^4.17.21", "mustache": "^4.2.0", diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 6edd0b617..5e6b27206 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -4,11 +4,13 @@ const path = require('path'); const chokidar = require('chokidar'); const { hasJsonExtension, hasBruExtension, writeFile } = require('../utils/filesystem'); const { bruToEnvJson, envJsonToBru, bruToJson, jsonToBru } = require('../bru'); +const { dotenvToJson } = require('@usebruno/lang'); const { isLegacyEnvFile, migrateLegacyEnvFile, isLegacyBruFile, migrateLegacyBruFile } = require('../bru/migrate'); const { itemSchema } = require('@usebruno/schema'); const { uuid } = require('../utils/common'); const { getRequestUid } = require('../cache/requestUids'); +const { setDotEnvVars } = require('../store/process-env'); const isJsonEnvironmentConfig = (pathname, collectionPath) => { const dirname = path.dirname(pathname); @@ -17,6 +19,13 @@ const isJsonEnvironmentConfig = (pathname, collectionPath) => { return dirname === collectionPath && basename === 'environments.json'; }; +const isDotEnvFile = (pathname, collectionPath) => { + const dirname = path.dirname(pathname); + const basename = path.basename(pathname); + + return dirname === collectionPath && basename === '.env'; +}; + const isBruEnvironmentConfig = (pathname, collectionPath) => { const dirname = path.dirname(pathname); const envDirectory = path.join(collectionPath, 'environments'); @@ -125,6 +134,25 @@ const unlinkEnvironmentFile = async (win, pathname, collectionUid) => { const add = async (win, pathname, collectionUid, collectionPath) => { console.log(`watcher add: ${pathname}`); + if (isDotEnvFile(pathname, collectionPath)) { + try { + const content = fs.readFileSync(pathname, 'utf8'); + const jsonData = dotenvToJson(content); + + setDotEnvVars(collectionUid, jsonData); + const payload = { + collectionUid, + processEnvVariables: { + ...process.env, + ...jsonData + } + }; + win.webContents.send('main:process-env-update', payload); + } catch (err) { + console.error(err); + } + } + if (isJsonEnvironmentConfig(pathname, collectionPath)) { try { const dirname = path.dirname(pathname); @@ -220,6 +248,25 @@ const addDirectory = (win, pathname, collectionUid, collectionPath) => { }; const change = async (win, pathname, collectionUid, collectionPath) => { + if (isDotEnvFile(pathname, collectionPath)) { + try { + const content = fs.readFileSync(pathname, 'utf8'); + const jsonData = dotenvToJson(content); + + setDotEnvVars(collectionUid, jsonData); + const payload = { + collectionUid, + processEnvVariables: { + ...process.env, + ...jsonData + } + }; + win.webContents.send('main:process-env-update', payload); + } catch (err) { + console.error(err); + } + } + if (isBruEnvironmentConfig(pathname, collectionPath)) { return changeEnvironmentFile(win, pathname, collectionUid); } diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 7c5f95e19..bc8deb73e 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -13,6 +13,7 @@ const { uuid } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); const { getPreferences } = require('../../store/preferences'); +const { getProcessEnvVars } = require('../../store/process-env'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -129,12 +130,14 @@ const registerNetworkIpc = (mainWindow) => { collectionPath ); - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } } // run pre-request script @@ -158,7 +161,9 @@ const registerNetworkIpc = (mainWindow) => { }); } - interpolateVars(request, envVars, collectionVariables); + const processEnvVars = getProcessEnvVars(collectionUid); + + interpolateVars(request, envVars, collectionVariables, processEnvVars); // stringify the request url encoded params if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { @@ -222,12 +227,14 @@ const registerNetworkIpc = (mainWindow) => { collectionPath ); - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } } // run post-response script @@ -520,7 +527,21 @@ const registerNetworkIpc = (mainWindow) => { const preRequestVars = get(request, 'vars.req', []); if (preRequestVars && preRequestVars.length) { const varsRuntime = new VarsRuntime(); - varsRuntime.runPreRequestVars(preRequestVars, request, envVars, collectionVariables, collectionPath); + const result = varsRuntime.runPreRequestVars( + preRequestVars, + request, + envVars, + collectionVariables, + collectionPath + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + collectionUid + }); + } } // run pre-request script @@ -543,8 +564,10 @@ const registerNetworkIpc = (mainWindow) => { }); } + const processEnvVars = getProcessEnvVars(collectionUid); + // interpolate variables inside request - interpolateVars(request, envVars, collectionVariables); + interpolateVars(request, envVars, collectionVariables, processEnvVars); // todo: // i have no clue why electron can't send the request object @@ -587,11 +610,13 @@ const registerNetworkIpc = (mainWindow) => { collectionPath ); - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + collectionUid + }); + } } // run response script diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 8c90e00a5..bfb0601da 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -1,24 +1,51 @@ -const Mustache = require('mustache'); -const { each, get, forOwn } = require('lodash'); +const Handlebars = require('handlebars'); +const { each, forOwn, cloneDeep } = require('lodash'); -// override the default escape function to prevent escaping -Mustache.escape = function (value) { - return value; +const interpolateEnvVars = (str, processEnvVars) => { + if (!str || !str.length || typeof str !== 'string') { + return str; + } + + const template = Handlebars.compile(str, { noEscape: true }); + + return template({ + process: { + env: { + ...processEnvVars + } + } + }); }; -const interpolateVars = (request, envVars = {}, collectionVariables = {}) => { +const interpolateVars = (request, envVars = {}, collectionVariables = {}, processEnvVars = {}) => { + // we clone envVars because we don't want to modify the original object + envVars = cloneDeep(envVars); + + // envVars can inturn have values as {{process.env.VAR_NAME}} + // so we need to interpolate envVars first with processEnvVars + forOwn(envVars, (value, key) => { + envVars[key] = interpolateEnvVars(value, processEnvVars); + }); + const interpolate = (str) => { if (!str || !str.length || typeof str !== 'string') { return str; } + const template = Handlebars.compile(str, { noEscape: true }); + // collectionVariables take precedence over envVars const combinedVars = { ...envVars, - ...collectionVariables + ...collectionVariables, + process: { + env: { + ...processEnvVars + } + } }; - return Mustache.render(str, combinedVars); + return template(combinedVars); }; request.url = interpolate(request.url); diff --git a/packages/bruno-electron/src/store/process-env.js b/packages/bruno-electron/src/store/process-env.js new file mode 100644 index 000000000..578d8df71 --- /dev/null +++ b/packages/bruno-electron/src/store/process-env.js @@ -0,0 +1,37 @@ +/** + * This file stores all the process.env variables under collection scope + * + * process.env variables are sourced from 2 places: + * 1. .env file in the root of the project + * 2. process.env variables set in the OS + * + * Multiple collections can be opened in the same electron app. + * Each collection's .env file can have different values for the same process.env variable. + */ + +const dotEnvVars = {}; + +// collectionUid is a hash based on the collection path) +const getProcessEnvVars = (collectionUid) => { + // if there are no .env vars for this collection, return the process.env + if (!dotEnvVars[collectionUid]) { + return { + ...process.env + }; + } + + // if there are .env vars for this collection, return the process.env merged with the .env vars + return { + ...process.env, + ...dotEnvVars[collectionUid] + }; +}; + +const setDotEnvVars = (collectionUid, envVars) => { + dotEnvVars[collectionUid] = envVars; +}; + +module.exports = { + getProcessEnvVars, + setDotEnvVars +}; diff --git a/packages/bruno-electron/src/utils/common.js b/packages/bruno-electron/src/utils/common.js index d85d137dd..83d22c7ce 100644 --- a/packages/bruno-electron/src/utils/common.js +++ b/packages/bruno-electron/src/utils/common.js @@ -41,10 +41,39 @@ const generateUidBasedOnHash = (str) => { return `${hash}`.padEnd(21, '0'); }; +const flattenDataForDotNotation = (data) => { + var result = {}; + function recurse(current, prop) { + if (Object(current) !== current) { + result[prop] = current; + } else if (Array.isArray(current)) { + for (var i = 0, l = current.length; i < l; i++) { + recurse(current[i], prop + '[' + i + ']'); + } + if (l == 0) { + result[prop] = []; + } + } else { + var isEmpty = true; + for (var p in current) { + isEmpty = false; + recurse(current[p], prop ? prop + '.' + p : p); + } + if (isEmpty && prop) { + result[prop] = {}; + } + } + } + + recurse(data, ''); + return result; +}; + module.exports = { uuid, stringifyJson, parseJson, simpleHash, - generateUidBasedOnHash + generateUidBasedOnHash, + flattenDataForDotNotation }; diff --git a/packages/bruno-electron/tests/utils/common.spec.js b/packages/bruno-electron/tests/utils/common.spec.js new file mode 100644 index 000000000..077aac16d --- /dev/null +++ b/packages/bruno-electron/tests/utils/common.spec.js @@ -0,0 +1,85 @@ +const { flattenDataForDotNotation } = require('../../src/utils/common'); + +describe('utils: flattenDataForDotNotation', () => { + test('Flatten a simple object with dot notation', () => { + const input = { + person: { + name: 'John', + age: 30, + }, + }; + + const expectedOutput = { + 'person.name': 'John', + 'person.age': 30, + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an object with nested arrays', () => { + const input = { + users: [ + { name: 'Alice', age: 25 }, + { name: 'Bob', age: 28 }, + ], + }; + + const expectedOutput = { + 'users[0].name': 'Alice', + 'users[0].age': 25, + 'users[1].name': 'Bob', + 'users[1].age': 28, + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an empty object', () => { + const input = {}; + + const expectedOutput = {}; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an object with nested objects', () => { + const input = { + person: { + name: 'Alice', + address: { + city: 'New York', + zipcode: '10001', + }, + }, + }; + + const expectedOutput = { + 'person.name': 'Alice', + 'person.address.city': 'New York', + 'person.address.zipcode': '10001', + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an object with arrays of objects', () => { + const input = { + teams: [ + { name: 'Team A', members: ['Alice', 'Bob'] }, + { name: 'Team B', members: ['Charlie', 'David'] }, + ], + }; + + const expectedOutput = { + 'teams[0].name': 'Team A', + 'teams[0].members[0]': 'Alice', + 'teams[0].members[1]': 'Bob', + 'teams[1].name': 'Team B', + 'teams[1].members[0]': 'Charlie', + 'teams[1].members[1]': 'David', + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); +}); \ No newline at end of file diff --git a/packages/bruno-lang/src/index.js b/packages/bruno-lang/src/index.js index bb7f35a80..f27179c45 100644 --- a/packages/bruno-lang/src/index.js +++ b/packages/bruno-lang/src/index.js @@ -4,6 +4,7 @@ const bruToJsonV2 = require('../v2/src/bruToJson'); const jsonToBruV2 = require('../v2/src/jsonToBru'); const bruToEnvJsonV2 = require('../v2/src/envToJson'); const envJsonToBruV2 = require('../v2/src/jsonToEnv'); +const dotenvToJson = require('../v2/src/dotenvToJson'); module.exports = { bruToJson, @@ -14,5 +15,7 @@ module.exports = { bruToJsonV2, jsonToBruV2, bruToEnvJsonV2, - envJsonToBruV2 + envJsonToBruV2, + + dotenvToJson }; From 1c89ab34508ba30fcc3fb5680fadaf3e24d3c00b Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 23 Sep 2023 03:14:27 +0530 Subject: [PATCH 03/10] fix: fixed issue where vm2 instantiated objects were not being sent to renderer process --- .../bruno-js/src/runtime/script-runtime.js | 13 ++++++------ packages/bruno-js/src/runtime/test-runtime.js | 9 ++++---- packages/bruno-js/src/utils.js | 21 ++++++++++++++++++- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index ac9d954a0..9f4871ae6 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -10,6 +10,7 @@ const punycode = require('punycode'); const Bru = require('../bru'); const BrunoRequest = require('../bruno-request'); const BrunoResponse = require('../bruno-response'); +const { cleanJson } = require('../utils'); // Inbuilt Library Support const atob = require('atob'); @@ -37,7 +38,7 @@ class ScriptRuntime { if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { - onConsoleLog(type, args); + onConsoleLog(type, cleanJson(args)); }; }; context.console = { @@ -81,8 +82,8 @@ class ScriptRuntime { await asyncVM(); return { request, - envVariables, - collectionVariables + envVariables: cleanJson(envVariables), + collectionVariables: cleanJson(collectionVariables) }; } @@ -100,7 +101,7 @@ class ScriptRuntime { if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { - onConsoleLog(type, args); + onConsoleLog(type, cleanJson(args)); }; }; context.console = { @@ -136,8 +137,8 @@ class ScriptRuntime { return { response, - envVariables, - collectionVariables + envVariables: cleanJson(envVariables), + collectionVariables: cleanJson(collectionVariables) }; } } diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 323a001ab..427506524 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -6,6 +6,7 @@ const BrunoRequest = require('../bruno-request'); const BrunoResponse = require('../bruno-response'); const Test = require('../test'); const TestResults = require('../test-results'); +const { cleanJson } = require('../utils'); // Inbuilt Library Support const atob = require('atob'); @@ -49,7 +50,7 @@ class TestRuntime { if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { - onConsoleLog(type, args); + onConsoleLog(type, cleanJson(args)); }; }; context.console = { @@ -82,9 +83,9 @@ class TestRuntime { return { request, - envVariables, - collectionVariables, - results: __brunoTestResults.getResults() + envVariables: cleanJson(envVariables), + collectionVariables: cleanJson(collectionVariables), + results: cleanJson(__brunoTestResults.getResults()) }; } } diff --git a/packages/bruno-js/src/utils.js b/packages/bruno-js/src/utils.js index a1ba5a7ed..aca101962 100644 --- a/packages/bruno-js/src/utils.js +++ b/packages/bruno-js/src/utils.js @@ -118,9 +118,28 @@ const createResponseParser = (response = {}) => { return res; }; +/** + * Objects that are created inside vm2 execution context result in an serilaization error when sent to the renderer process + * Error sending from webFrameMain: Error: Failed to serialize arguments + * at s.send (node:electron/js2c/browser_init:169:631) + * at g.send (node:electron/js2c/browser_init:165:2156) + * How to reproduce + * Remove the cleanJson fix and execute the below post response script + * bru.setVar("a", {b:3}); + * Todo: Find a better fix + */ +const cleanJson = (data) => { + try { + return JSON.parse(JSON.stringify(data)); + } catch (e) { + return data; + } +}; + module.exports = { evaluateJsExpression, evaluateJsTemplateLiteral, createResponseParser, - internalExpressionCache + internalExpressionCache, + cleanJson }; From aea1cbba9e10d30210f01c9aacc6a588b12b37b1 Mon Sep 17 00:00:00 2001 From: Jeff Printy Date: Fri, 22 Sep 2023 23:13:54 -0500 Subject: [PATCH 04/10] Issue #203 Add about-window to help menu --- package.json | 1 + packages/bruno-electron/src/app/menu-template.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3aef7fa12..ced62e608 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@faker-js/faker": "^7.6.0", "@jest/globals": "^29.2.0", "@playwright/test": "^1.27.1", + "about-window": "^1.15.2", "husky": "^8.0.3", "jest": "^29.2.0", "pretty-quick": "^3.1.3", diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index 2e3beb82b..3c19599d4 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -1,4 +1,6 @@ const { ipcMain } = require('electron'); +const openAboutWindow = require('about-window').default; +const { join } = require('path'); const template = [ { @@ -42,7 +44,18 @@ const template = [ }, { role: 'help', - submenu: [{ label: 'Learn More' }] + submenu: [ + { + label: 'About Bruno', + click: () => + openAboutWindow({ + icon_path: join(__dirname, '../../resources/icons/png/128x128.png'), + homepage: 'https://www.usebruno.com/', + package_json_dir: join(__dirname, '../..') + }) + }, + { label: 'Learn More' } + ] } ]; From 0a172ddce8c874fbeda6daa135bcc6f385857548 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 24 Sep 2023 02:07:31 +0530 Subject: [PATCH 05/10] feat(#206): Collection and Env variables viewer --- packages/bruno-app/package.json | 1 + .../src/components/RequestTabPanel/index.js | 5 + .../RequestTabs/CollectionToolBar/index.js | 19 +++- .../RequestTabs/RequestTab/SpecialTab.js | 23 +++++ .../RequestTabs/RequestTab/index.js | 9 ++ .../src/components/RequestTabs/index.js | 2 +- .../VariablesEditor/StyledWrapper.js | 20 ++++ .../src/components/VariablesEditor/index.js | 92 +++++++++++++++++++ .../VariablesView/Popover/StyledWrapper.js | 19 ---- .../components/VariablesView/Popover/index.js | 26 ------ .../components/VariablesView/StyledWrapper.js | 15 --- .../VariablesTable/StyledWrapper.js | 19 ---- .../VariablesView/VariablesTable/index.js | 66 ------------- .../src/components/VariablesView/index.js | 48 ---------- .../src/providers/ReduxStore/slices/tabs.js | 20 +++- 15 files changed, 185 insertions(+), 199 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js create mode 100644 packages/bruno-app/src/components/VariablesEditor/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/VariablesEditor/index.js delete mode 100644 packages/bruno-app/src/components/VariablesView/Popover/StyledWrapper.js delete mode 100644 packages/bruno-app/src/components/VariablesView/Popover/index.js delete mode 100644 packages/bruno-app/src/components/VariablesView/StyledWrapper.js delete mode 100644 packages/bruno-app/src/components/VariablesView/VariablesTable/StyledWrapper.js delete mode 100644 packages/bruno-app/src/components/VariablesView/VariablesTable/index.js delete mode 100644 packages/bruno-app/src/components/VariablesView/index.js diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 810f0e6ff..f8ad53c01 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -47,6 +47,7 @@ "react-dom": "18.2.0", "react-github-btn": "^1.4.0", "react-hot-toast": "^2.4.0", + "react-inspector": "^6.0.2", "react-redux": "^7.2.6", "react-tooltip": "^5.5.2", "sass": "^1.46.0", diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js index 371778fbf..511fb5cd4 100644 --- a/packages/bruno-app/src/components/RequestTabPanel/index.js +++ b/packages/bruno-app/src/components/RequestTabPanel/index.js @@ -13,6 +13,7 @@ import RequestNotFound from './RequestNotFound'; import QueryUrl from 'components/RequestPane/QueryUrl'; import NetworkError from 'components/ResponsePane/NetworkError'; import RunnerResults from 'components/RunnerResults'; +import VariablesEditor from 'components/VariablesEditor'; import { DocExplorer } from '@usebruno/graphql-docs'; import StyledWrapper from './StyledWrapper'; @@ -123,6 +124,10 @@ const RequestTabPanel = () => { return ; } + if (focusedTab.type === 'variables') { + return ; + } + const item = findItemInCollection(collection, activeTabUid); if (!item || !item.uid) { return ; diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js index 1d96df94b..c461e3bb4 100644 --- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js +++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js @@ -1,7 +1,8 @@ import React from 'react'; -import { IconFiles, IconRun } from '@tabler/icons'; +import { uuid } from 'utils/common'; +import { IconFiles, IconRun, IconEye } from '@tabler/icons'; import EnvironmentSelector from 'components/Environments/EnvironmentSelector'; -import VariablesView from 'components/VariablesView'; +import { addTab } from 'providers/ReduxStore/slices/tabs'; import { useDispatch } from 'react-redux'; import { toggleRunnerView } from 'providers/ReduxStore/slices/collections'; import StyledWrapper from './StyledWrapper'; @@ -17,6 +18,16 @@ const CollectionToolBar = ({ collection }) => { ); }; + const viewVariables = () => { + dispatch( + addTab({ + uid: uuid(), + collectionUid: collection.uid, + type: 'variables' + }) + ); + }; + return (
@@ -28,7 +39,9 @@ const CollectionToolBar = ({ collection }) => { - + + +
diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js new file mode 100644 index 000000000..d086d5b90 --- /dev/null +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { IconVariable } from '@tabler/icons'; + +const SpecialTab = ({ handleCloseClick, text }) => { + return ( + <> +
+ + {text} +
+
handleCloseClick(e)}> + + + +
+ + ); +}; + +export default SpecialTab; diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index a10fd8994..744f85c27 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -5,6 +5,7 @@ import { useDispatch } from 'react-redux'; import { findItemInCollection } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; import RequestTabNotFound from './RequestTabNotFound'; +import SpecialTab from './SpecialTab'; const RequestTab = ({ tab, collection }) => { const dispatch = useDispatch(); @@ -56,6 +57,14 @@ const RequestTab = ({ tab, collection }) => { return color; }; + if (tab.type === 'variables') { + return ( + + + + ); + } + const item = findItemInCollection(collection, tab.uid); if (!item) { diff --git a/packages/bruno-app/src/components/RequestTabs/index.js b/packages/bruno-app/src/components/RequestTabs/index.js index a92c55271..c51da327f 100644 --- a/packages/bruno-app/src/components/RequestTabs/index.js +++ b/packages/bruno-app/src/components/RequestTabs/index.js @@ -114,7 +114,7 @@ const RequestTabs = () => { role="tab" onClick={() => handleClick(tab)} > - + ); }) diff --git a/packages/bruno-app/src/components/VariablesEditor/StyledWrapper.js b/packages/bruno-app/src/components/VariablesEditor/StyledWrapper.js new file mode 100644 index 000000000..e4d976b0d --- /dev/null +++ b/packages/bruno-app/src/components/VariablesEditor/StyledWrapper.js @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + table { + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + + li { + background-color: ${(props) => props.theme.bg} !important; + } + } + } + + .muted { + color: ${(props) => props.theme.colors.text.muted}; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/VariablesEditor/index.js b/packages/bruno-app/src/components/VariablesEditor/index.js new file mode 100644 index 000000000..80176065e --- /dev/null +++ b/packages/bruno-app/src/components/VariablesEditor/index.js @@ -0,0 +1,92 @@ +import React from 'react'; +import get from 'lodash/get'; +import filter from 'lodash/filter'; +import { Inspector } from 'react-inspector'; +import { useTheme } from 'providers/Theme'; +import { findEnvironmentInCollection } from 'utils/collections'; +import StyledWrapper from './StyledWrapper'; + +const KeyValueExplorer = ({ data, theme }) => { + data = data || {}; + + return ( +
+ + + {Object.entries(data).map(([key, value]) => ( + + + + + ))} + +
{key} + +
+
+ ); +}; + +const EnvVariables = ({ collection, theme }) => { + const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); + + if (!environment) { + return ( + <> +

Environment Variables

+
No environment selected
+ + ); + } + + const envVars = get(environment, 'variables', []); + const enabledEnvVars = filter(envVars, (variable) => variable.enabled); + const envVarsObj = enabledEnvVars.reduce((acc, curr) => { + acc[curr.name] = curr.value; + return acc; + }, {}); + + return ( + <> +
+

Environment Variables

+ ({environment.name}) +
+ {enabledEnvVars.length > 0 ? ( + + ) : ( +
No environment variables found
+ )} + + ); +}; + +const CollectionVariables = ({ collection, theme }) => { + const collectionVariablesFound = Object.keys(collection.collectionVariables).length > 0; + + return ( + <> +

Collection Variables

+ {collectionVariablesFound ? ( + + ) : ( +
No collection variables found
+ )} + + ); +}; + +const VariablesEditor = ({ collection }) => { + const { storedTheme } = useTheme(); + + const reactInspectorTheme = storedTheme === 'light' ? 'chromeLight' : 'chromeDark'; + + return ( + + + + + ); +}; + +export default VariablesEditor; diff --git a/packages/bruno-app/src/components/VariablesView/Popover/StyledWrapper.js b/packages/bruno-app/src/components/VariablesView/Popover/StyledWrapper.js deleted file mode 100644 index 45fa9b60b..000000000 --- a/packages/bruno-app/src/components/VariablesView/Popover/StyledWrapper.js +++ /dev/null @@ -1,19 +0,0 @@ -import styled from 'styled-components'; - -const Wrapper = styled.div` - position: absolute; - min-width: fit-content; - font-size: 14px; - top: 36px; - right: 0; - white-space: nowrap; - z-index: 1000; - background-color: ${(props) => props.theme.variables.bg}; - - .popover { - border-radius: 2px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - } -`; - -export default Wrapper; diff --git a/packages/bruno-app/src/components/VariablesView/Popover/index.js b/packages/bruno-app/src/components/VariablesView/Popover/index.js deleted file mode 100644 index ab1700f88..000000000 --- a/packages/bruno-app/src/components/VariablesView/Popover/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useRef } from 'react'; -import StyledWrapper from './StyledWrapper'; -import useOnClickOutside from 'hooks/useOnClickOutside'; - -const PopOver = ({ children, iconRef, handleClose }) => { - const popOverRef = useRef(null); - - useOnClickOutside(popOverRef, (e) => { - if (iconRef && iconRef.current) { - if (e.target == iconRef.current || iconRef.current.contains(e.target)) { - return; - } - } - handleClose(); - }); - - return ( - -
-
{children}
-
-
- ); -}; - -export default PopOver; diff --git a/packages/bruno-app/src/components/VariablesView/StyledWrapper.js b/packages/bruno-app/src/components/VariablesView/StyledWrapper.js deleted file mode 100644 index f280aad41..000000000 --- a/packages/bruno-app/src/components/VariablesView/StyledWrapper.js +++ /dev/null @@ -1,15 +0,0 @@ -import styled from 'styled-components'; - -const StyledWrapper = styled.div` - position: relative; - align-self: stretch; - display: flex; - align-items: center; - - .view-environment { - width: 1rem; - font-size: 10px; - } -`; - -export default StyledWrapper; diff --git a/packages/bruno-app/src/components/VariablesView/VariablesTable/StyledWrapper.js b/packages/bruno-app/src/components/VariablesView/VariablesTable/StyledWrapper.js deleted file mode 100644 index a3970cdb1..000000000 --- a/packages/bruno-app/src/components/VariablesView/VariablesTable/StyledWrapper.js +++ /dev/null @@ -1,19 +0,0 @@ -import styled from 'styled-components'; - -const StyledWrapper = styled.div` - .variable-name { - color: ${(props) => props.theme.variables.name.color}; - } - - .variable-name { - min-width: 180px; - } - - .variable-value { - max-width: 600px; - inline-size: 600px; - overflow-wrap: break-word; - } -`; - -export default StyledWrapper; diff --git a/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js b/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js deleted file mode 100644 index 3707359f2..000000000 --- a/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import forOwn from 'lodash/forOwn'; -import isObject from 'lodash/isObject'; -import cloneDeep from 'lodash/cloneDeep'; -import { uuid } from 'utils/common'; -import StyledWrapper from './StyledWrapper'; - -const VariablesTable = ({ variables, collectionVariables }) => { - const collectionVars = []; - - forOwn(cloneDeep(collectionVariables), (value, key) => { - collectionVars.push({ - uid: uuid(), - name: key, - value: value - }); - }); - - const getValueToDisplay = (value) => { - if (value === undefined) { - return ''; - } - - return isObject(value) ? JSON.stringify(value) : value; - }; - - return ( - -
-
Environment Variables
- {variables && variables.length ? ( - variables.map((variable) => { - return ( -
-
{variable.name}
-
- {getValueToDisplay(variable.value)} -
-
- ); - }) - ) : ( - No env variables found - )} - -
Collection Variables
- {collectionVars && collectionVars.length ? ( - collectionVars.map((variable) => { - return ( -
-
{variable.name}
-
- {getValueToDisplay(variable.value)} -
-
- ); - }) - ) : ( - No collection variables found - )} -
-
- ); -}; - -export default VariablesTable; diff --git a/packages/bruno-app/src/components/VariablesView/index.js b/packages/bruno-app/src/components/VariablesView/index.js deleted file mode 100644 index 8f04fed70..000000000 --- a/packages/bruno-app/src/components/VariablesView/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useState, useRef } from 'react'; -import get from 'lodash/get'; -import filter from 'lodash/filter'; -import { findEnvironmentInCollection } from 'utils/collections'; -import VariablesTable from './VariablesTable'; -import StyledWrapper from './StyledWrapper'; -import PopOver from './Popover'; -import { IconEye } from '@tabler/icons'; - -const VariablesView = ({ collection }) => { - const iconRef = useRef(null); - const [popOverOpen, setPopOverOpen] = useState(false); - - const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); - const variables = get(environment, 'variables', []); - const enabledVariables = filter(variables, (variable) => variable.enabled); - const showVariablesTable = - enabledVariables.length > 0 || - (collection.collectionVariables && Object.keys(collection.collectionVariables).length > 0); - - return ( - -
setPopOverOpen(true)} - onMouseEnter={() => setPopOverOpen(true)} - onMouseLeave={() => setPopOverOpen(false)} - > -
- -
- {popOverOpen && ( - setPopOverOpen(false)}> -
- {showVariablesTable ? ( - - ) : ( - 'No variables found' - )} -
-
- )} -
-
- ); -}; - -export default VariablesView; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js index e1181aac7..4f8778a5e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js @@ -19,12 +19,22 @@ export const tabsSlice = createSlice({ if (alreadyExists) { return; } + + if (action.payload.type === 'variables') { + const tab = find(state.tabs, (t) => t.collectionUid === action.payload.collectionUid && t.type === 'variables'); + if (tab) { + state.activeTabUid = tab.uid; + return; + } + } + state.tabs.push({ uid: action.payload.uid, collectionUid: action.payload.collectionUid, requestPaneWidth: null, requestPaneTab: action.payload.requestPaneTab || 'params', - responsePaneTab: 'response' + responsePaneTab: 'response', + type: action.payload.type || 'request' }); state.activeTabUid = action.payload.uid; }, @@ -55,16 +65,22 @@ export const tabsSlice = createSlice({ closeTabs: (state, action) => { const activeTab = find(state.tabs, (t) => t.uid === state.activeTabUid); const tabUids = action.payload.tabUids || []; + + // remove the tabs from the state state.tabs = filter(state.tabs, (t) => !tabUids.includes(t.uid)); if (activeTab && state.tabs.length) { const { collectionUid } = activeTab; const activeTabStillExists = find(state.tabs, (t) => t.uid === state.activeTabUid); + // if the active tab no longer exists, set the active tab to the last tab in the list + // this implies that the active tab was closed if (!activeTabStillExists) { - // attempt to load sibling tabs (based on collections) of the dead tab + // load sibling tabs of the current collection const siblingTabs = filter(state.tabs, (t) => t.collectionUid === collectionUid); + // if there are sibling tabs, set the active tab to the last sibling tab + // otherwise, set the active tab to the last tab in the list if (siblingTabs && siblingTabs.length) { state.activeTabUid = last(siblingTabs).uid; } else { From c5a86cb343feb1ad1c6fcfe5a012f58a05985d1d Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 24 Sep 2023 02:33:58 +0530 Subject: [PATCH 06/10] feat: documentation link in app titlebar --- package.json | 1 + packages/bruno-electron/package.json | 2 +- packages/bruno-electron/src/app/menu-template.js | 7 ++++--- packages/bruno-electron/src/ipc/collection.js | 7 ++++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ced62e608..107b0d744 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "packages/bruno-testbench", "packages/bruno-graphql-docs" ], + "homepage": "https://usebruno.com", "devDependencies": { "@faker-js/faker": "^7.6.0", "@jest/globals": "^29.2.0", diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 5ec2fcfc4..bb5314957 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,7 +1,7 @@ { "version": "v0.14.1", "name": "bruno", - "description": "Opensource API Client", + "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", "private": true, "main": "src/index.js", diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index 3c19599d4..16bf18dee 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -7,7 +7,7 @@ const template = [ label: 'Collection', submenu: [ { - label: 'Open Local Collection', + label: 'Open Collection', click() { ipcMain.emit('main:open-collection'); } @@ -49,12 +49,13 @@ const template = [ label: 'About Bruno', click: () => openAboutWindow({ - icon_path: join(__dirname, '../../resources/icons/png/128x128.png'), + product_name: 'Bruno', + icon_path: join(__dirname, '../../resources/icons/png/256x256.png'), homepage: 'https://www.usebruno.com/', package_json_dir: join(__dirname, '../..') }) }, - { label: 'Learn More' } + { label: 'Documentation', click: () => ipcMain.emit('main:open-docs') } ] } ]; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index ff29a2432..cf931a072 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -1,7 +1,7 @@ const _ = require('lodash'); const fs = require('fs'); const path = require('path'); -const { ipcMain } = require('electron'); +const { ipcMain, shell } = require('electron'); const { envJsonToBru, bruToJson, jsonToBru } = require('../bru'); const { @@ -443,6 +443,11 @@ const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) = } }); + ipcMain.on('main:open-docs', () => { + const docsURL = 'https://docs.usebruno.com'; + shell.openExternal(docsURL); + }); + ipcMain.on('main:collection-opened', (win, pathname, uid) => { watcher.addWatcher(win, pathname, uid); lastOpenedCollections.add(pathname); From a5a17cf8eb032bf11941da8b03750daab142ae6d Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sun, 24 Sep 2023 02:47:05 +0530 Subject: [PATCH 07/10] fix(#131): fixed macos ctrl+a select all issue --- packages/bruno-electron/src/app/menu-template.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index 16bf18dee..45125bb97 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -23,7 +23,8 @@ const template = [ { type: 'separator' }, { role: 'cut' }, { role: 'copy' }, - { role: 'paste' } + { role: 'paste' }, + { role: 'selectAll' } ] }, { From 74282706aa84f81fcfa6bd0851cf0a2f8d3efc37 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Thu, 21 Sep 2023 22:17:46 +0200 Subject: [PATCH 08/10] #199 add CLI feature to use command line parameter '--env-var secret=xzy123' --- packages/bruno-cli/src/commands/run.js | 48 ++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 039932c58..45643ea5f 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -1,7 +1,7 @@ const fs = require('fs'); const chalk = require('chalk'); const path = require('path'); -const { exists, isFile, isDirectory, getSubDirectories } = require('../utils/filesystem'); +const { exists, isFile, isDirectory } = require('../utils/filesystem'); const { runSingleRequest } = require('../runner/run-single-request'); const { bruToEnvJson, getEnvVars } = require('../utils/bru'); const { rpad } = require('../utils/common'); @@ -103,8 +103,7 @@ const getBruFilesRecursively = (dir) => { return bruJsons; }; - const bruJsons = getFilesInOrder(dir); - return bruJsons; + return getFilesInOrder(dir); }; const builder = async (yargs) => { @@ -122,6 +121,10 @@ const builder = async (yargs) => { describe: 'Environment variables', type: 'string' }) + .option('env-var', { + describe: 'Overwrite a single environment variable, multiple usages possible', + type: 'string' + }) .option('insecure', { type: 'boolean', description: 'Allow insecure server connections' @@ -129,12 +132,16 @@ const builder = async (yargs) => { .example('$0 run request.bru', 'Run a request') .example('$0 run request.bru --env local', 'Run a request with the environment set to local') .example('$0 run folder', 'Run all requests in a folder') - .example('$0 run folder -r', 'Run all requests in a folder recursively'); + .example('$0 run folder -r', 'Run all requests in a folder recursively') + .example( + '$0 run request.bru --env local --env-var secret=xxx', + 'Run a request with the environment set to local and overwrite the variable secret with value xxx' + ); }; const handler = async function (argv) { try { - let { filename, cacert, env, insecure, r: recursive } = argv; + let { filename, cacert, env, envVar, insecure, r: recursive } = argv; const collectionPath = process.cwd(); // todo @@ -175,6 +182,35 @@ const handler = async function (argv) { envVars = getEnvVars(envJson); } + if (envVar) { + if (typeof envVar === 'string') { + let parts = envVar.split('='); + if (parts.length !== 2) { + console.error( + chalk.red(`overridable environment variable not correct: use name=value - presented: `) + + chalk.dim(`${envVar}`) + ); + return; + } + envVars[parts[0]] = parts[1]; + } else if (typeof envVar === 'object' && Array.isArray(envVar)) { + envVar.forEach((value) => { + let parts = value.split('='); + if (parts.length !== 2) { + console.error( + chalk.red(`overridable environment variable not correct: use name=value - presented: `) + + chalk.dim(`${value}`) + ); + return; + } + envVars[parts[0]] = parts[1]; + }); + } else { + console.error(chalk.red(`overridable environment variables not parsable: use name=value`)); + return; + } + } + const options = getOptions(); if (insecure) { options['insecure'] = true; @@ -240,7 +276,7 @@ const handler = async function (argv) { } else { console.log(chalk.yellow('Running Folder Recursively \n')); - bruJsons = await getBruFilesRecursively(filename); + bruJsons = getBruFilesRecursively(filename); } let assertionResults = []; From 6b9e0856962a52a5a376e3ebe7f9251719aa41df Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Fri, 22 Sep 2023 09:22:46 +0200 Subject: [PATCH 09/10] #199 small code refactoring --- packages/bruno-cli/src/commands/run.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 45643ea5f..420eb696d 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -183,18 +183,17 @@ const handler = async function (argv) { } if (envVar) { + let processVars; if (typeof envVar === 'string') { - let parts = envVar.split('='); - if (parts.length !== 2) { - console.error( - chalk.red(`overridable environment variable not correct: use name=value - presented: `) + - chalk.dim(`${envVar}`) - ); - return; - } - envVars[parts[0]] = parts[1]; + processVars = [envVar]; } else if (typeof envVar === 'object' && Array.isArray(envVar)) { - envVar.forEach((value) => { + processVars = envVar; + } else { + console.error(chalk.red(`overridable environment variables not parsable: use name=value`)); + return; + } + if (processVars && Array.isArray(processVars)) { + processVars.forEach((value) => { let parts = value.split('='); if (parts.length !== 2) { console.error( @@ -205,9 +204,6 @@ const handler = async function (argv) { } envVars[parts[0]] = parts[1]; }); - } else { - console.error(chalk.red(`overridable environment variables not parsable: use name=value`)); - return; } } From 8e22aa2fca1fb8cfb37e77bdcd42089dcf235629 Mon Sep 17 00:00:00 2001 From: Mirko Golze Date: Sun, 24 Sep 2023 15:28:33 +0200 Subject: [PATCH 10/10] #199 improve code to check given envvars correctly --- packages/bruno-cli/src/commands/run.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 420eb696d..4b6aa3afa 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -193,17 +193,18 @@ const handler = async function (argv) { return; } if (processVars && Array.isArray(processVars)) { - processVars.forEach((value) => { - let parts = value.split('='); - if (parts.length !== 2) { + for (const value of processVars.values()) { + // split the string at the first equals sign + const match = value.match(/^([^=]+)=(.*)$/); + if (!match) { console.error( - chalk.red(`overridable environment variable not correct: use name=value - presented: `) + + chalk.red(`Overridable environment variable not correct: use name=value - presented: `) + chalk.dim(`${value}`) ); return; } - envVars[parts[0]] = parts[1]; - }); + envVars[match[1]] = match[2]; + } } }