feat: global env var highlight and interpolation

This commit is contained in:
lohxt1 2024-09-30 11:48:30 +05:30
parent 72de78025e
commit a8fce54e97
11 changed files with 86 additions and 12 deletions

View File

@ -20,6 +20,8 @@ import { DocExplorer } from '@usebruno/graphql-docs';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import SecuritySettings from 'components/SecuritySettings'; import SecuritySettings from 'components/SecuritySettings';
import FolderSettings from 'components/FolderSettings'; import FolderSettings from 'components/FolderSettings';
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
import { cloneDeep } from 'lodash';
const MIN_LEFT_PANE_WIDTH = 300; const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 350; const MIN_RIGHT_PANE_WIDTH = 350;
@ -34,6 +36,7 @@ const RequestTabPanel = () => {
const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const collections = useSelector((state) => state.collections.collections); const collections = useSelector((state) => state.collections.collections);
const screenWidth = useSelector((state) => state.app.screenWidth); const screenWidth = useSelector((state) => state.app.screenWidth);
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
let asideWidth = useSelector((state) => state.app.leftSidebarWidth); let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
const focusedTab = find(tabs, (t) => t.uid === activeTabUid); const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
@ -117,7 +120,14 @@ const RequestTabPanel = () => {
return <div className="pb-4 px-4">An error occurred!</div>; return <div className="pb-4 px-4">An error occurred!</div>;
} }
let collection = find(collections, (c) => c.uid === focusedTab.collectionUid); let _collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
let collection = cloneDeep(_collection);
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
collection.globalEnvironmentVariables = globalEnvironmentVariables;
if (!collection || !collection.uid) { if (!collection || !collection.uid) {
return <div className="pb-4 px-4">Collection not found!</div>; return <div className="pb-4 px-4">Collection not found!</div>;
} }

View File

@ -8,19 +8,24 @@ import { useSelector } from 'react-redux';
import { CopyToClipboard } from 'react-copy-to-clipboard'; import { CopyToClipboard } from 'react-copy-to-clipboard';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { IconCopy } from '@tabler/icons'; import { IconCopy } from '@tabler/icons';
import { findCollectionByItemUid } from '../../../../../../../utils/collections/index'; import { findCollectionByItemUid, getGlobalEnvironmentVariables } from '../../../../../../../utils/collections/index';
import { getAuthHeaders } from '../../../../../../../utils/codegenerator/auth'; import { getAuthHeaders } from '../../../../../../../utils/codegenerator/auth';
const CodeView = ({ language, item }) => { const CodeView = ({ language, item }) => {
const { displayedTheme } = useTheme(); const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences); const preferences = useSelector((state) => state.app.preferences);
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
const { target, client, language: lang } = language; const { target, client, language: lang } = language;
const requestHeaders = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers'); const requestHeaders = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
const collection = findCollectionByItemUid( let collection = findCollectionByItemUid(
useSelector((state) => state.collections.collections), useSelector((state) => state.collections.collections),
item.uid item.uid
); );
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
collection.globalEnvironmentVariables = globalEnvironmentVariables;
const collectionRootAuth = collection?.root?.request?.auth; const collectionRootAuth = collection?.root?.request?.auth;
const requestAuth = item.draft ? get(item, 'draft.request.auth') : get(item, 'request.auth'); const requestAuth = item.draft ? get(item, 'draft.request.auth') : get(item, 'request.auth');

View File

@ -44,6 +44,7 @@ import { parsePathParams, 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'; import { name } from 'file-loader';
import slash from 'utils/common/slash'; import slash from 'utils/common/slash';
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
const state = getState(); const state = getState();
@ -183,6 +184,7 @@ export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState)
export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch, getState) => { export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch, getState) => {
const state = getState(); const state = getState();
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const collection = findCollectionByUid(state.collections.collections, collectionUid); const collection = findCollectionByUid(state.collections.collections, collectionUid);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -190,7 +192,11 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch
return reject(new Error('Collection not found')); return reject(new Error('Collection not found'));
} }
const collectionCopy = cloneDeep(collection); let collectionCopy = cloneDeep(collection);
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid); const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
@ -212,6 +218,7 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch
export const sendRequest = (item, collectionUid) => (dispatch, getState) => { export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
const state = getState(); const state = getState();
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const collection = findCollectionByUid(state.collections.collections, collectionUid); const collection = findCollectionByUid(state.collections.collections, collectionUid);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -220,7 +227,11 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
} }
const itemCopy = cloneDeep(item || {}); const itemCopy = cloneDeep(item || {});
const collectionCopy = cloneDeep(collection); let collectionCopy = cloneDeep(collection);
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;
const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid); const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid);
sendNetworkRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables) sendNetworkRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
@ -285,6 +296,7 @@ export const cancelRunnerExecution = (cancelTokenUid) => (dispatch) => {
export const runCollectionFolder = (collectionUid, folderUid, recursive, delay) => (dispatch, getState) => { export const runCollectionFolder = (collectionUid, folderUid, recursive, delay) => (dispatch, getState) => {
const state = getState(); const state = getState();
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const collection = findCollectionByUid(state.collections.collections, collectionUid); const collection = findCollectionByUid(state.collections.collections, collectionUid);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -292,7 +304,12 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive, delay)
return reject(new Error('Collection not found')); return reject(new Error('Collection not found'));
} }
const collectionCopy = cloneDeep(collection); let collectionCopy = cloneDeep(collection);
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;
const folder = findItemInCollection(collectionCopy, folderUid); const folder = findItemInCollection(collectionCopy, folderUid);
if (folderUid && !folder) { if (folderUid && !folder) {

View File

@ -782,6 +782,19 @@ export const getDefaultRequestPaneTab = (item) => {
} }
}; };
export const getGlobalEnvironmentVariables = ({ globalEnvironments, activeGlobalEnvironmentUid }) => {
let variables = {};
const environment = globalEnvironments?.find(env => env?.uid === activeGlobalEnvironmentUid);
if (environment) {
each(environment.variables, (variable) => {
if (variable.name && variable.value && variable.enabled) {
variables[variable.name] = variable.value;
}
});
}
return variables;
};
export const getEnvironmentVariables = (collection) => { export const getEnvironmentVariables = (collection) => {
let variables = {}; let variables = {};
if (collection) { if (collection) {
@ -798,6 +811,7 @@ export const getEnvironmentVariables = (collection) => {
return variables; return variables;
}; };
const getPathParams = (item) => { const getPathParams = (item) => {
let pathParams = {}; let pathParams = {};
if (item && item.request && item.request.params) { if (item && item.request && item.request.params) {
@ -829,10 +843,12 @@ export const getAllVariables = (collection, item) => {
const requestTreePath = getTreePathFromCollectionToItem(collection, item); const requestTreePath = getTreePathFromCollectionToItem(collection, item);
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath); let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
const pathParams = getPathParams(item); const pathParams = getPathParams(item);
const { globalEnvironmentVariables = {} } = collection;
const { processEnvVariables = {}, runtimeVariables = {} } = collection; const { processEnvVariables = {}, runtimeVariables = {} } = collection;
return { return {
...globalEnvironmentVariables,
...collectionVariables, ...collectionVariables,
...envVariables, ...envVariables,
...folderVariables, ...folderVariables,

View File

@ -14,6 +14,7 @@ const getContentType = (headers = {}) => {
}; };
const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => { const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {}; const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {}; const requestVariables = request?.requestVariables || {};
@ -39,6 +40,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
// runtimeVariables take precedence over envVars // runtimeVariables take precedence over envVars
const combinedVars = { const combinedVars = {
...globalEnvironmentVariables,
...collectionVariables, ...collectionVariables,
...envVariables, ...envVariables,
...folderVariables, ...folderVariables,

View File

@ -370,6 +370,7 @@ const prepareRequest = (item, collection) => {
mergeFolderLevelHeaders(request, requestTreePath); mergeFolderLevelHeaders(request, requestTreePath);
mergeFolderLevelScripts(request, requestTreePath, scriptFlow); mergeFolderLevelScripts(request, requestTreePath, scriptFlow);
mergeVars(collection, request, requestTreePath); mergeVars(collection, request, requestTreePath);
request.globalEnvironmentVariables = collection?.globalEnvironmentVariables;
} }
// Request level headers // Request level headers
@ -461,6 +462,7 @@ const prepareRequest = (item, collection) => {
axiosRequest.collectionVariables = request.collectionVariables; axiosRequest.collectionVariables = request.collectionVariables;
axiosRequest.folderVariables = request.folderVariables; axiosRequest.folderVariables = request.folderVariables;
axiosRequest.requestVariables = request.requestVariables; axiosRequest.requestVariables = request.requestVariables;
axiosRequest.globalEnvironmentVariables = request.globalEnvironmentVariables;
axiosRequest.assertions = request.assertions; axiosRequest.assertions = request.assertions;
return axiosRequest; return axiosRequest;

View File

@ -4,13 +4,14 @@ const { interpolate } = require('@usebruno/common');
const variableNameRegex = /^[\w-.]*$/; const variableNameRegex = /^[\w-.]*$/;
class Bru { class Bru {
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables) { constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables) {
this.envVariables = envVariables || {}; this.envVariables = envVariables || {};
this.runtimeVariables = runtimeVariables || {}; this.runtimeVariables = runtimeVariables || {};
this.processEnvVars = cloneDeep(processEnvVars || {}); this.processEnvVars = cloneDeep(processEnvVars || {});
this.collectionVariables = collectionVariables || {}; this.collectionVariables = collectionVariables || {};
this.folderVariables = folderVariables || {}; this.folderVariables = folderVariables || {};
this.requestVariables = requestVariables || {}; this.requestVariables = requestVariables || {};
this.globalEnvironmentVariables = globalEnvironmentVariables || {};
this.collectionPath = collectionPath; this.collectionPath = collectionPath;
} }
@ -20,6 +21,7 @@ class Bru {
} }
const combinedVars = { const combinedVars = {
...this.globalEnvironmentVariables,
...this.collectionVariables, ...this.collectionVariables,
...this.envVariables, ...this.envVariables,
...this.folderVariables, ...this.folderVariables,
@ -63,6 +65,18 @@ class Bru {
this.envVariables[key] = value; this.envVariables[key] = value;
} }
getGlobalEnvVar(key) {
return this._interpolate(this.globalEnvironmentVariables[key]);
}
setGlobalEnvVar(key, value) {
if (!key) {
throw new Error('Creating a env variable without specifying a name is not allowed.');
}
this.globalEnvironmentVariables[key] = value;
}
hasVar(key) { hasVar(key) {
return Object.hasOwn(this.runtimeVariables, key); return Object.hasOwn(this.runtimeVariables, key);
} }

View File

@ -2,13 +2,14 @@ const { interpolate } = require('@usebruno/common');
const interpolateString = ( const interpolateString = (
str, str,
{ envVariables = {}, runtimeVariables = {}, processEnvVars = {}, collectionVariables = {}, folderVariables = {}, requestVariables = {} } { envVariables = {}, runtimeVariables = {}, processEnvVars = {}, collectionVariables = {}, folderVariables = {}, requestVariables = {}, globalEnvironmentVariables = {} }
) => { ) => {
if (!str || !str.length || typeof str !== 'string') { if (!str || !str.length || typeof str !== 'string') {
return str; return str;
} }
const combinedVars = { const combinedVars = {
...globalEnvironmentVariables,
...collectionVariables, ...collectionVariables,
...envVariables, ...envVariables,
...folderVariables, ...folderVariables,

View File

@ -192,6 +192,7 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
} }
const interpolationContext = { const interpolationContext = {
globalEnvironmentVariables: context.bru.globalEnvironmentVariables,
collectionVariables: context.bru.collectionVariables, collectionVariables: context.bru.collectionVariables,
folderVariables: context.bru.folderVariables, folderVariables: context.bru.folderVariables,
requestVariables: context.bru.requestVariables, requestVariables: context.bru.requestVariables,
@ -240,6 +241,7 @@ class AssertRuntime {
} }
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) { runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {}; const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {}; const requestVariables = request?.requestVariables || {};
@ -255,7 +257,8 @@ class AssertRuntime {
undefined, undefined,
collectionVariables, collectionVariables,
folderVariables, folderVariables,
requestVariables requestVariables,
globalEnvironmentVariables
); );
const req = new BrunoRequest(request); const req = new BrunoRequest(request);
const res = createResponseParser(response); const res = createResponseParser(response);
@ -267,6 +270,7 @@ class AssertRuntime {
}; };
const context = { const context = {
...globalEnvironmentVariables,
...collectionVariables, ...collectionVariables,
...envVariables, ...envVariables,
...folderVariables, ...folderVariables,

View File

@ -47,10 +47,11 @@ class ScriptRuntime {
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig
) { ) {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {}; const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {}; const requestVariables = request?.requestVariables || {};
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables); const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
const req = new BrunoRequest(request); const req = new BrunoRequest(request);
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []); const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
@ -164,10 +165,11 @@ class ScriptRuntime {
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig
) { ) {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {}; const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {}; const requestVariables = request?.requestVariables || {};
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables); const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
const req = new BrunoRequest(request); const req = new BrunoRequest(request);
const res = new BrunoResponse(response); const res = new BrunoResponse(response);
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);

View File

@ -48,10 +48,11 @@ class TestRuntime {
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig
) { ) {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {}; const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {}; const requestVariables = request?.requestVariables || {};
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables); const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
const req = new BrunoRequest(request); const req = new BrunoRequest(request);
const res = new BrunoResponse(response); const res = new BrunoResponse(response);
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);