mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-07 08:34:15 +01:00
Merge branch 'main' into feature/env-secrets
This commit is contained in:
commit
2dadad3af0
@ -13,10 +13,12 @@
|
||||
"packages/bruno-testbench",
|
||||
"packages/bruno-graphql-docs"
|
||||
],
|
||||
"homepage": "https://usebruno.com",
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -100,15 +103,11 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={variable.value || ''}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'value')}
|
||||
<SingleLineEditor
|
||||
value={variable.value}
|
||||
theme={storedTheme}
|
||||
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -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;
|
||||
|
@ -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 <RunnerResults collection={collection} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'variables') {
|
||||
return <VariablesEditor collection={collection} />;
|
||||
}
|
||||
|
||||
const item = findItemInCollection(collection, activeTabUid);
|
||||
if (!item || !item.uid) {
|
||||
return <RequestNotFound itemUid={activeTabUid} />;
|
||||
|
@ -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 (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center p-2">
|
||||
@ -28,7 +39,9 @@ const CollectionToolBar = ({ collection }) => {
|
||||
<span className="mr-2">
|
||||
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
|
||||
</span>
|
||||
<VariablesView collection={collection} />
|
||||
<span className="mr-3">
|
||||
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
|
||||
</span>
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { IconVariable } from '@tabler/icons';
|
||||
|
||||
const SpecialTab = ({ handleCloseClick, text }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center tab-label pl-2">
|
||||
<IconVariable size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">{text}</span>
|
||||
</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecialTab;
|
@ -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 (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
<SpecialTab handleCloseClick={handleCloseClick} text="Variables" />
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const item = findItemInCollection(collection, tab.uid);
|
||||
|
||||
if (!item) {
|
||||
|
@ -114,7 +114,7 @@ const RequestTabs = () => {
|
||||
role="tab"
|
||||
onClick={() => handleClick(tab)}
|
||||
>
|
||||
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab} />
|
||||
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} />
|
||||
</li>
|
||||
);
|
||||
})
|
||||
|
@ -19,6 +19,7 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: hidden !important;
|
||||
padding-bottom: 50px !important;
|
||||
}
|
||||
|
||||
.CodeMirror-hscrollbar {
|
||||
|
@ -31,6 +31,7 @@ class SingleLineEditor extends Component {
|
||||
brunoVarInfo: {
|
||||
variables: getAllVariables(this.props.collection)
|
||||
},
|
||||
scrollbarStyle: null,
|
||||
extraKeys: {
|
||||
Enter: () => {
|
||||
if (this.props.onRun) {
|
||||
|
@ -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;
|
92
packages/bruno-app/src/components/VariablesEditor/index.js
Normal file
92
packages/bruno-app/src/components/VariablesEditor/index.js
Normal file
@ -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 (
|
||||
<div>
|
||||
<table className="border-collapse">
|
||||
<tbody>
|
||||
{Object.entries(data).map(([key, value]) => (
|
||||
<tr key={key}>
|
||||
<td className="px-2 py-1">{key}</td>
|
||||
<td className="px-2 py-1">
|
||||
<Inspector data={value} theme={theme} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EnvVariables = ({ collection, theme }) => {
|
||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||
|
||||
if (!environment) {
|
||||
return (
|
||||
<>
|
||||
<h1 className="font-semibold mt-4 mb-2">Environment Variables</h1>
|
||||
<div className="muted">No environment selected</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="flex items-center mt-4 mb-2">
|
||||
<h1 className="font-semibold">Environment Variables</h1>
|
||||
<span className="muted ml-2">({environment.name})</span>
|
||||
</div>
|
||||
{enabledEnvVars.length > 0 ? (
|
||||
<KeyValueExplorer data={envVarsObj} theme={theme} />
|
||||
) : (
|
||||
<div className="muted">No environment variables found</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CollectionVariables = ({ collection, theme }) => {
|
||||
const collectionVariablesFound = Object.keys(collection.collectionVariables).length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="font-semibold mb-2">Collection Variables</h1>
|
||||
{collectionVariablesFound ? (
|
||||
<KeyValueExplorer data={collection.collectionVariables} theme={theme} />
|
||||
) : (
|
||||
<div className="muted">No collection variables found</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const VariablesEditor = ({ collection }) => {
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const reactInspectorTheme = storedTheme === 'light' ? 'chromeLight' : 'chromeDark';
|
||||
|
||||
return (
|
||||
<StyledWrapper className="px-4 py-4">
|
||||
<CollectionVariables collection={collection} theme={reactInspectorTheme} />
|
||||
<EnvVariables collection={collection} theme={reactInspectorTheme} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default VariablesEditor;
|
@ -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;
|
@ -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 (
|
||||
<StyledWrapper>
|
||||
<div className="popover" ref={popOverRef}>
|
||||
<div className="popover-content">{children}</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopOver;
|
@ -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;
|
@ -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;
|
@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import forOwn from 'lodash/forOwn';
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="mb-2 font-medium">Environment Variables</div>
|
||||
{variables && variables.length ? (
|
||||
variables.map((variable) => {
|
||||
return (
|
||||
<div key={variable.uid} className="flex">
|
||||
<div className="variable-name text-yellow-600 text-right pr-2">{variable.name}</div>
|
||||
<div className="variable-value pl-2 whitespace-normal text-left flex-grow">{variable.value}</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<small>No env variables found</small>
|
||||
)}
|
||||
|
||||
<div className="mt-2 font-medium">Collection Variables</div>
|
||||
{collectionVars && collectionVars.length ? (
|
||||
collectionVars.map((variable) => {
|
||||
return (
|
||||
<div key={variable.uid} className="flex">
|
||||
<div className="variable-name text-yellow-600 text-right pr-2">{variable.name}</div>
|
||||
<div className="variable-value pl-2 whitespace-normal text-left flex-grow">{variable.value}</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<small>No collection variables found</small>
|
||||
)}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default VariablesTable;
|
@ -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 (
|
||||
<StyledWrapper className="mr-2 server-syncstatus-icon" ref={iconRef}>
|
||||
<div
|
||||
className="flex p-1 items-center"
|
||||
onClick={() => setPopOverOpen(true)}
|
||||
onMouseEnter={() => setPopOverOpen(true)}
|
||||
onMouseLeave={() => setPopOverOpen(false)}
|
||||
>
|
||||
<div className="cursor-pointer view-environment">
|
||||
<IconEye size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
{popOverOpen && (
|
||||
<PopOver iconRef={iconRef} handleClose={() => setPopOverOpen(false)}>
|
||||
<div className="px-2 py-1">
|
||||
{showVariablesTable ? (
|
||||
<VariablesTable variables={enabledVariables} collectionVariables={collection.collectionVariables} />
|
||||
) : (
|
||||
'No variables found'
|
||||
)}
|
||||
</div>
|
||||
</PopOver>
|
||||
)}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default VariablesView;
|
@ -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]);
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1 +1 @@
|
||||
@import "buttons";
|
||||
@import 'buttons';
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -542,6 +542,11 @@ export const getAllVariables = (collection) => {
|
||||
|
||||
return {
|
||||
...environmentVariables,
|
||||
...collection.collectionVariables
|
||||
...collection.collectionVariables,
|
||||
process: {
|
||||
env: {
|
||||
...collection.processEnvVariables
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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",
|
||||
@ -28,6 +28,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",
|
||||
|
@ -1,11 +1,13 @@
|
||||
const { ipcMain } = require('electron');
|
||||
const openAboutWindow = require('about-window').default;
|
||||
const { join } = require('path');
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: 'Collection',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Open Local Collection',
|
||||
label: 'Open Collection',
|
||||
click() {
|
||||
ipcMain.emit('main:open-collection');
|
||||
}
|
||||
@ -21,7 +23,8 @@ const template = [
|
||||
{ type: 'separator' },
|
||||
{ role: 'cut' },
|
||||
{ role: 'copy' },
|
||||
{ role: 'paste' }
|
||||
{ role: 'paste' },
|
||||
{ role: 'selectAll' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -42,7 +45,19 @@ const template = [
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: [{ label: 'Learn More' }]
|
||||
submenu: [
|
||||
{
|
||||
label: 'About Bruno',
|
||||
click: () =>
|
||||
openAboutWindow({
|
||||
product_name: 'Bruno',
|
||||
icon_path: join(__dirname, '../../resources/icons/png/256x256.png'),
|
||||
homepage: 'https://www.usebruno.com/',
|
||||
package_json_dir: join(__dirname, '../..')
|
||||
})
|
||||
},
|
||||
{ label: 'Documentation', click: () => ipcMain.emit('main:open-docs') }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -4,12 +4,14 @@ 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 { decryptString } = require('../utils/encryption');
|
||||
const { setDotEnvVars } = require('../store/process-env');
|
||||
const EnvironmentSecretsStore = require('../store/env-secrets');
|
||||
|
||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||
@ -21,6 +23,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');
|
||||
@ -158,6 +167,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);
|
||||
@ -253,6 +281,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, collectionPath);
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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 {
|
||||
@ -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 EnvironmentSecretsStore = require('../store/env-secrets');
|
||||
|
||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||
@ -460,6 +460,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);
|
||||
|
@ -12,7 +12,8 @@ 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');
|
||||
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
|
||||
|
@ -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);
|
||||
|
37
packages/bruno-electron/src/store/process-env.js
Normal file
37
packages/bruno-electron/src/store/process-env.js
Normal file
@ -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
|
||||
};
|
@ -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
|
||||
};
|
||||
|
85
packages/bruno-electron/tests/utils/common.spec.js
Normal file
85
packages/bruno-electron/tests/utils/common.spec.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user