Compare commits

..

7 Commits

Author SHA1 Message Date
Anoop M D
00e98451d4 Merge branch 'main' into fix/json-serialization-issues 2024-08-27 14:09:10 +05:30
lohit
df2e18bedd bru disableParsingResponseJson function rename (#2785)
* disable response json parse flag

* fix: pr review comments

* update bru req function name
2024-08-08 19:07:12 +05:30
Anoop M D
9a88db7e56 fix(#2767): fix failing test 2024-08-08 18:46:35 +05:30
lohit
4a4439f48e disable response json parse flag (#2782)
* disable response json parse flag

* fix: pr review comments
2024-08-08 18:36:00 +05:30
Anoop M D
4710928407 fix(#2767): addressing review comments 2024-08-08 12:21:04 +05:30
Anoop M D
33804f4c7b fix(#2767): addressing review comments 2024-08-08 12:02:00 +05:30
Anoop M D
e0858d1c99 fix(#2767): Fix serilization issues of bigint in json body 2024-08-07 20:00:41 +05:30
74 changed files with 340 additions and 957 deletions

View File

@@ -39,7 +39,7 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
[Download Bruno](https://www.usebruno.com/downloads)
📢 Sieh Dir unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
📢 Sehen Sie sich unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
![bruno](/assets/images/landing-2.png) <br /><br />
@@ -48,13 +48,13 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
Die meisten unserer Funktionen sind kostenlos und quelloffen.
Wir bemühen uns um ein Gleichgewicht zwischen [Open-Source-Prinzipien und Nachhaltigkeit](https://github.com/usebruno/bruno/discussions/269)
Du kannst die [Golden Edition](https://www.usebruno.com/pricing) bestellen **$19**! <br/>
Sie können die [Golden Edition](https://www.usebruno.com/pricing) vorbestellen ~~$19~~ **$9** ! <br/>
### Installation
Bruno ist als Download [auf unserer Website](https://www.usebruno.com/downloads) für Mac, Windows und Linux verfügbar.
Du kannst Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
Sie können Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
```sh
# Auf Mac via Homebrew
@@ -123,11 +123,11 @@ Oder einer Versionskontrolle deiner Wahl
### Unterstützung ❤️
Wuff! Wenn du dieses Projekt magst, klick auf den ⭐ Button !!
Wuff! Wenn du dieses Projekt magst, klick den ⭐ Button !!
### Teile Erfahrungsberichte 📣
Wenn Bruno dir und in deinem Team bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte in unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
Wenn Bruno dir und in deinen Teams bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte auf unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
### Bereitstellung in neuen Paket-Managern

53
package-lock.json generated
View File

@@ -50,7 +50,6 @@
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -671,7 +670,6 @@
},
"node_modules/@babel/compat-data": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -679,7 +677,6 @@
},
"node_modules/@babel/core": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
@@ -743,7 +740,6 @@
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.25.2",
@@ -758,7 +754,6 @@
},
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
"version": "5.1.1",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
@@ -766,7 +761,6 @@
},
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
"version": "3.1.1",
"dev": true,
"license": "ISC"
},
"node_modules/@babel/helper-create-class-features-plugin": {
@@ -845,7 +839,6 @@
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.24.7",
@@ -912,7 +905,6 @@
},
"node_modules/@babel/helper-simple-access": {
"version": "7.24.7",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.24.7",
@@ -950,7 +942,6 @@
},
"node_modules/@babel/helper-validator-option": {
"version": "7.24.8",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -971,7 +962,6 @@
},
"node_modules/@babel/helpers": {
"version": "7.25.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.0",
@@ -3654,6 +3644,37 @@
"node": ">=12"
}
},
"node_modules/@n8n/vm2": {
"version": "3.9.25",
"resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.25.tgz",
"integrity": "sha512-qoGLFzyHBW7HKpwXkl05QKsIh3GkDw6lOiTOWYlUDnOIQ1b7EgM+O5EMjrMGy7r+kz52+Q7o6GLxBIcxVI8rEg==",
"license": "MIT",
"peer": true,
"dependencies": {
"acorn": "^8.7.0",
"acorn-walk": "^8.2.0"
},
"bin": {
"vm2": "bin/vm2"
},
"engines": {
"node": ">=18.10",
"pnpm": ">=9.6"
}
},
"node_modules/@n8n/vm2/node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/@next/env": {
"version": "12.3.3",
"license": "MIT"
@@ -4730,7 +4751,6 @@
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
@@ -4739,7 +4759,6 @@
},
"node_modules/@types/markdown-it": {
"version": "12.2.3",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/linkify-it": "*",
@@ -4748,7 +4767,6 @@
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/@types/minimatch": {
@@ -6222,7 +6240,6 @@
},
"node_modules/browserslist": {
"version": "4.23.3",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -7221,7 +7238,6 @@
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
@@ -8461,7 +8477,6 @@
},
"node_modules/electron-to-chromium": {
"version": "1.5.11",
"dev": true,
"license": "ISC"
},
"node_modules/electron-util": {
@@ -9423,7 +9438,6 @@
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -13172,7 +13186,6 @@
},
"node_modules/node-releases": {
"version": "2.0.18",
"dev": true,
"license": "MIT"
},
"node_modules/node-vault": {
@@ -16188,7 +16201,6 @@
},
"node_modules/semver": {
"version": "6.3.1",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -17651,7 +17663,6 @@
},
"node_modules/update-browserslist-db": {
"version": "1.1.0",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -18622,7 +18633,7 @@
},
"packages/bruno-electron": {
"name": "bruno",
"version": "v1.27.0",
"version": "v1.26.1",
"dependencies": {
"@aws-sdk/credential-providers": "3.525.0",
"@usebruno/common": "0.1.0",

View File

@@ -5,7 +5,6 @@ const StyledWrapper = styled.div`
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
font-family: ${(props) => (props.font ? props.font : 'default')};
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
line-break: anywhere;
flex: 1 1 0;
}

View File

@@ -67,7 +67,7 @@ if (!SERVER_RENDERED) {
'bru.setVar(key,value)',
'bru.deleteVar(key)',
'bru.setNextRequest(requestName)',
'req.disableParsingResponseJson()',
'req.disableParsingResponseJson()'
'bru.getRequestVar(key)',
'bru.sleep(ms)'
];
@@ -333,7 +333,6 @@ export default class CodeEditor extends React.Component {
className="h-full w-full flex flex-col relative"
aria-label="Code Editor"
font={this.props.font}
fontSize={this.props.fontSize}
ref={(node) => {
this._node = node;
}}

View File

@@ -10,9 +10,8 @@ import StyledWrapper from './StyledWrapper';
import { useRef } from 'react';
import path from 'path';
import slash from 'utils/common/slash';
import { isWindowsOS } from 'utils/common/platform';
const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
const certFilePathInputRef = useRef();
const keyFilePathInputRef = useRef();
const pfxFilePathInputRef = useRef();
@@ -68,15 +67,7 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
});
const getFile = (e) => {
if (e.files?.[0]?.path) {
let relativePath;
if (isWindowsOS()) {
relativePath = slash(path.win32.relative(root, e.files[0].path));
} else {
relativePath = path.posix.relative(root, e.files[0].path);
}
formik.setFieldValue(e.name, relativePath);
}
e.files?.[0]?.path && formik.setFieldValue(e.name, e.files?.[0]?.path);
};
const resetFileInputFields = () => {
@@ -111,14 +102,10 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
: clientCertConfig.map((clientCert) => (
<li key={uuid()} className="flex items-center available-certificates p-2 rounded-lg mb-2">
<div className="flex items-center w-full justify-between">
<div className="flex w-full items-center">
<div className="flex items-center">
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
{clientCert.domain}
</div>
<div className="flex w-full items-center">
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
</div>
<button onClick={() => onRemove(clientCert)} className="remove-certificate ml-2">
<IconTrash size={18} strokeWidth={1.5} />
</button>

View File

@@ -46,7 +46,6 @@ const Docs = ({ collection }) => {
onSave={onSave}
mode="application/text"
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
) : (
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
@@ -104,7 +104,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
<div className="mb-3 flex items-center">
<label className="settings-label flex items-center" htmlFor="enabled">
Config
<InfoTip
<Tooltip
text={`
<div>
<ul>
@@ -114,7 +114,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
</ul>
</div>
`}
infotipId="request-var"
tooltipId="request-var"
/>
</label>
<div className="flex items-center">

View File

@@ -52,7 +52,6 @@ const Script = ({ collection }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>
<div className="flex-1 mt-6">
@@ -65,7 +64,6 @@ const Script = ({ collection }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>

View File

@@ -36,7 +36,6 @@ const Tests = ({ collection }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
<div className="mt-6">

View File

@@ -1,9 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.title {
color: var(--color-tab-inactive);
}
`;
export default StyledWrapper;

View File

@@ -1,56 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
table {
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem;
user-select: none;
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}
.btn-add-var {
font-size: 0.8125rem;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;
border: solid 1px transparent;
}
}
input[type='checkbox'] {
cursor: pointer;
position: relative;
top: 1px;
}
`;
export default Wrapper;

View File

@@ -1,162 +0,0 @@
import React from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import InfoTip from 'components/InfoTip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { variableNameRegex } from 'utils/common/regex';
import {
addCollectionVar,
deleteCollectionVar,
updateCollectionVar
} from 'providers/ReduxStore/slices/collections/index';
const VarsTable = ({ collection, vars, varType }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const addVar = () => {
dispatch(
addCollectionVar({
collectionUid: collection.uid,
type: varType
})
);
};
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleVarChange = (e, v, type) => {
const _var = cloneDeep(v);
switch (type) {
case 'name': {
const value = e.target.value;
if (variableNameRegex.test(value) === false) {
toast.error(
'Variable contains invalid characters! Variables must only contain alpha-numeric characters, "-", "_", "."'
);
return;
}
_var.name = value;
break;
}
case 'value': {
_var.value = e.target.value;
break;
}
case 'enabled': {
_var.enabled = e.target.checked;
break;
}
}
dispatch(
updateCollectionVar({
type: varType,
var: _var,
collectionUid: collection.uid
})
);
};
const handleRemoveVar = (_var) => {
dispatch(
deleteCollectionVar({
type: varType,
varUid: _var.uid,
collectionUid: collection.uid
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Name</td>
{varType === 'request' ? (
<td>
<div className="flex items-center">
<span>Value</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
</div>
</td>
)}
<td></td>
</tr>
</thead>
<tbody>
{vars && vars.length
? vars.map((_var) => {
return (
<tr key={_var.uid}>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={_var.name}
className="mousetrap"
onChange={(e) => handleVarChange(e, _var, 'name')}
/>
</td>
<td>
<SingleLineEditor
value={_var.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleVarChange(
{
target: {
value: newValue
}
},
_var,
'value'
)
}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={_var.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, _var, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveVar(_var)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={addVar}>
+ Add
</button>
</StyledWrapper>
);
};
export default VarsTable;

View File

@@ -1,32 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import VarsTable from './VarsTable';
import StyledWrapper from './StyledWrapper';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
const Vars = ({ collection }) => {
const dispatch = useDispatch();
const requestVars = get(collection, 'root.request.vars.req', []);
const responseVars = get(collection, 'root.request.vars.res', []);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<VarsTable collection={collection} vars={requestVars} varType="request" />
</div>
<div className="flex-1">
<div className="mt-1 mb-1 title text-xs">Post Response</div>
<VarsTable collection={collection} vars={responseVars} varType="response" />
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Vars;

View File

@@ -16,7 +16,6 @@ import Docs from './Docs';
import Presets from './Presets';
import Info from './Info';
import StyledWrapper from './StyledWrapper';
import Vars from './Vars/index';
const CollectionSettings = ({ collection }) => {
const dispatch = useDispatch();
@@ -78,9 +77,6 @@ const CollectionSettings = ({ collection }) => {
case 'headers': {
return <Headers collection={collection} />;
}
case 'vars': {
return <Vars collection={collection} />;
}
case 'auth': {
return <Auth collection={collection} />;
}
@@ -99,7 +95,6 @@ const CollectionSettings = ({ collection }) => {
case 'clientCert': {
return (
<ClientCertSettings
root={collection.pathname}
clientCertConfig={clientCertConfig}
onUpdate={onClientCertSettingsUpdate}
onRemove={onClientCertSettingsRemove}
@@ -127,9 +122,6 @@ const CollectionSettings = ({ collection }) => {
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
Headers
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
Vars
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
Auth
</div>

View File

@@ -47,7 +47,6 @@ const Documentation = ({ item, collection }) => {
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={docs || ''}
onEdit={onEdit}
onSave={onSave}

View File

@@ -116,7 +116,6 @@ const Headers = ({ collection, folder }) => {
)
}
collection={collection}
item={folder}
/>
</td>
<td>

View File

@@ -54,7 +54,6 @@ const Script = ({ collection, folder }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>
<div className="flex flex-col flex-1 mt-2 gap-y-2">
@@ -67,7 +66,6 @@ const Script = ({ collection, folder }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>

View File

@@ -37,7 +37,6 @@ const Tests = ({ collection, folder }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
<div className="mt-6">

View File

@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { variableNameRegex } from 'utils/common/regex';
@@ -82,14 +82,14 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
<td>
<div className="flex items-center">
<span>Value</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
</div>
</td>
)}
@@ -130,7 +130,6 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
)
}
collection={collection}
item={folder}
/>
</td>
<td>

View File

@@ -10,8 +10,6 @@ import {
} from 'providers/ReduxStore/slices/notifications';
import { useDispatch, useSelector } from 'react-redux';
import { humanizeDate, relativeDate } from 'utils/common';
import ToolHint from 'components/ToolHint';
import { useTheme } from 'providers/Theme';
const PAGE_SIZE = 5;
@@ -22,7 +20,6 @@ const Notifications = () => {
const [showNotificationsModal, setShowNotificationsModal] = useState(false);
const [selectedNotification, setSelectedNotification] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
const { storedTheme } = useTheme();
const notificationsStartIndex = (pageNumber - 1) * PAGE_SIZE;
const notificationsEndIndex = pageNumber * PAGE_SIZE;
@@ -88,22 +85,21 @@ const Notifications = () => {
return (
<StyledWrapper>
<a
title="Notifications"
className="relative cursor-pointer"
onClick={() => {
dispatch(fetchNotifications());
setShowNotificationsModal(true);
}}
>
<ToolHint text="Notifications" toolhintId="Notifications" offset={8} >
<IconBell
size={18}
strokeWidth={1.5}
className={`mr-2 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
/>
{unreadNotifications.length > 0 && (
<span className="notification-count text-xs">{unreadNotifications.length}</span>
)}
</ToolHint>
<IconBell
size={18}
strokeWidth={1.5}
className={`mr-2 hover:text-gray-700 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
/>
{unreadNotifications.length > 0 && (
<span className="notification-count text-xs">{unreadNotifications.length}</span>
)}
</a>
{showNotificationsModal && (
@@ -133,8 +129,9 @@ const Notifications = () => {
{notifications?.slice(notificationsStartIndex, notificationsEndIndex)?.map((notification) => (
<li
key={notification.id}
className={`p-4 flex flex-col justify-center ${selectedNotification?.id == notification.id ? 'active' : notification.read ? 'read' : ''
}`}
className={`p-4 flex flex-col justify-center ${
selectedNotification?.id == notification.id ? 'active' : notification.read ? 'read' : ''
}`}
onClick={handleNotificationItemClick(notification)}
>
<div className="notification-title w-full">{notification?.title}</div>
@@ -144,8 +141,9 @@ const Notifications = () => {
</ul>
<div className="w-full pagination flex flex-row gap-4 justify-center p-2 items-center text-xs">
<button
className={`pl-2 pr-2 py-3 select-none ${pageNumber <= 1 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
className={`pl-2 pr-2 py-3 select-none ${
pageNumber <= 1 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
onClick={handlePrev}
>
{'Prev'}
@@ -161,8 +159,9 @@ const Notifications = () => {
</div>
</div>
<button
className={`pl-2 pr-2 py-3 select-none ${pageNumber == totalPages ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
className={`pl-2 pr-2 py-3 select-none ${
pageNumber == totalPages ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
onClick={handleNext}
>
{'Next'}

View File

@@ -9,25 +9,17 @@ const Font = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences);
const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default'));
const [codeFontSize, setCodeFontSize] = useState(get(preferences, 'font.codeFontSize', '14'));
const handleCodeFontChange = (event) => {
const handleInputChange = (event) => {
setCodeFont(event.target.value);
};
const handleCodeFontSizeChange = (event) => {
// Restrict to min/max value
const clampedSize = Math.max(1, Math.min(event.target.value, 32));
setCodeFontSize(clampedSize);
};
const handleSave = () => {
dispatch(
savePreferences({
...preferences,
font: {
codeFont,
codeFontSize
codeFont
}
})
).then(() => {
@@ -37,33 +29,17 @@ const Font = ({ close }) => {
return (
<StyledWrapper>
<div className="flex flex-row gap-2 w-full">
<div className="w-4/5">
<label className="block font-medium">Code Editor Font</label>
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleCodeFontChange}
defaultValue={codeFont}
/>
</div>
<div className="w-1/5">
<label className="block font-medium">Font Size</label>
<input
type="number"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
inputMode="numeric"
onChange={handleCodeFontSizeChange}
defaultValue={codeFontSize}
/>
</div>
</div>
<label className="block font-medium">Code Editor Font</label>
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleInputChange}
defaultValue={codeFont}
/>
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>

View File

@@ -191,7 +191,6 @@ const AssertionRow = ({
}
onRun={handleRun}
collection={collection}
item={item}
/>
) : (
<input type="text" className="cursor-default" disabled />

View File

@@ -31,7 +31,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
: get(item, 'request.body.graphql.variables');
const { displayedTheme } = useTheme();
const [schema, setSchema] = useState(null);
const preferences = useSelector((state) => state.app.preferences);
useEffect(() => {
onSchemaLoad(schema);
@@ -72,8 +71,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
onRun={onRun}
onEdit={onQueryChange}
onClickReference={handleGqlClickReference}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
);
}

View File

@@ -33,7 +33,6 @@ const GraphQLVariables = ({ variables, item, collection }) => {
value={variables || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}

View File

@@ -4,8 +4,6 @@ const StyledWrapper = styled.div`
div.CodeMirror {
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
font-family: ${(props) => (props.font ? props.font : 'default')};
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
flex: 1 1 0;
}

View File

@@ -211,8 +211,6 @@ export default class QueryEditor extends React.Component {
<StyledWrapper
className="h-full w-full flex flex-col relative"
aria-label="Query Editor"
font={this.props.font}
fontSize={this.props.fontSize}
ref={(node) => {
this._node = node;
}}

View File

@@ -1,7 +1,7 @@
import React from 'react';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
@@ -175,7 +175,7 @@ const QueryParams = ({ item, collection }) => {
</button>
<div className="mb-2 title text-xs flex items-stretch">
<span>Path</span>
<InfoTip
<Tooltip
text={`
<div>
Path variables are automatically added whenever the
@@ -186,7 +186,7 @@ const QueryParams = ({ item, collection }) => {
</code>
</div>
`}
infotipId="path-param-InfoTip"
tooltipId="path-param-tooltip"
/>
</div>
<table>

View File

@@ -33,18 +33,18 @@ const Wrapper = styled.div`
top: 1px;
}
.infotip {
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
}
.infotip:hover .infotiptext {
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.infotiptext {
.tooltiptext {
visibility: hidden;
width: auto;
background-color: ${(props) => props.theme.requestTabs.active.bg};
@@ -62,7 +62,7 @@ const Wrapper = styled.div`
white-space: nowrap;
}
.infotiptext::after {
.tooltiptext::after {
content: '';
position: absolute;
top: 100%;

View File

@@ -74,7 +74,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
/>
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<div
className="infotip mr-3"
className="tooltip mx-3"
onClick={(e) => {
e.stopPropagation();
if (!item.draft) return;
@@ -87,7 +87,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
size={22}
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
/>
<span className="infotiptext text-xs">
<span className="tooltiptext text-xs">
Save <span className="shortcut">({saveShortcut})</span>
</span>
</div>

View File

@@ -50,7 +50,6 @@ const RequestBody = ({ item, collection }) => {
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={bodyContent[bodyMode] || ''}
onEdit={onEdit}
onRun={onRun}

View File

@@ -47,7 +47,6 @@ const Script = ({ item, collection }) => {
value={requestScript || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onRequestScriptEdit}
mode="javascript"
onRun={onRun}
@@ -61,7 +60,6 @@ const Script = ({ item, collection }) => {
value={responseScript || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onResponseScriptEdit}
mode="javascript"
onRun={onRun}

View File

@@ -34,7 +34,6 @@ const Tests = ({ item, collection }) => {
value={tests || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}

View File

@@ -6,7 +6,7 @@ import { useTheme } from 'providers/Theme';
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { variableNameRegex } from 'utils/common/regex';
@@ -83,14 +83,14 @@ const VarsTable = ({ item, collection, vars, varType }) => {
<td>
<div className="flex items-center">
<span>Value</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
</div>
</td>
)}
@@ -132,7 +132,6 @@ const VarsTable = ({ item, collection, vars, varType }) => {
}
onRun={handleRun}
collection={collection}
item={item}
/>
</td>
<td>

View File

@@ -2,4 +2,4 @@ import styled from 'styled-components';
const StyledWrapper = styled.div``;
export default StyledWrapper;
export default StyledWrapper;

View File

@@ -4,7 +4,6 @@ import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { useDispatch } from 'react-redux';
import ToolHint from 'components/ToolHint';
import StyledWrapper from './StyledWrapper';
import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode';
@@ -52,20 +51,14 @@ const CollectionToolBar = ({ collection }) => {
<span className="mr-2">
<JsSandboxMode collection={collection} />
</span>
<span className="mr-3">
<ToolHint text="Runner" toolhintId="RunnnerToolhintId" place='bottom'>
<IconRun className="cursor-pointer" size={18} strokeWidth={1.5} onClick={handleRun} />
</ToolHint>
<span className="mr-2">
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
</span>
<span className="mr-3">
<ToolHint text="Variables" toolhintId="VariablesToolhintId">
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
</ToolHint>
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
</span>
<span className="mr-3">
<ToolHint text="Collection Settings" toolhintId="CollectionSettingsToolhintId">
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
</ToolHint>
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
</span>
<EnvironmentSelector collection={collection} />
</div>

View File

@@ -2,7 +2,7 @@ import { IconFilter, IconX } from '@tabler/icons';
import React, { useMemo } from 'react';
import { useRef } from 'react';
import { useState } from 'react';
import { Tooltip as ReactInfotip } from 'react-tooltip';
import { Tooltip as ReactTooltip } from 'react-tooltip';
const QueryResultFilter = ({ filter, onChange, mode }) => {
const inputRef = useRef(null);
@@ -19,7 +19,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
}
};
const infotipText = useMemo(() => {
const tooltipText = useMemo(() => {
if (mode.includes('json')) {
return 'Filter with JSONPath';
}
@@ -49,7 +49,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
'response-filter absolute bottom-2 w-full justify-end right-0 flex flex-row items-center gap-2 py-4 px-2 pointer-events-none'
}
>
{infotipText && !isExpanded && <ReactInfotip anchorId={'request-filter-icon'} html={infotipText} />}
{tooltipText && !isExpanded && <ReactTooltip anchorId={'request-filter-icon'} html={tooltipText} />}
<input
ref={inputRef}
type="text"

View File

@@ -81,7 +81,6 @@ const QueryResultPreview = ({
<CodeEditor
collection={collection}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
theme={displayedTheme}
onRun={onRun}
value={formattedData}

View File

@@ -5,7 +5,7 @@ import * as Yup from 'yup';
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
import { cloneCollection } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import Modal from 'components/Modal';
const CloneCollection = ({ onClose, collection }) => {
@@ -126,9 +126,9 @@ const CloneCollection = ({ onClose, collection }) => {
<label htmlFor="collection-folder-name" className="flex items-center mt-3">
<span className="font-semibold">Folder Name</span>
<InfoTip
<Tooltip
text="This folder will be created under the selected location"
infotipId="collection-folder-name-infotip"
tooltipId="collection-folder-name-tooltip"
/>
</label>
<input

View File

@@ -53,7 +53,6 @@ const CodeView = ({ language, item }) => {
collection={collection}
value={snippet}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
theme={displayedTheme}
mode={lang}
/>

View File

@@ -65,7 +65,7 @@ const Wrapper = styled.div`
div.dropdown-item.delete-item {
color: ${(props) => props.theme.colors.danger};
&:hover {
background-color: ${(props) => props.theme.colors.bg.danger} !important;
background-color: ${(props) => props.theme.colors.bg.danger};
color: white;
}
}

View File

@@ -5,7 +5,7 @@ import * as Yup from 'yup';
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import Modal from 'components/Modal';
const CreateCollection = ({ onClose }) => {
@@ -119,9 +119,9 @@ const CreateCollection = ({ onClose }) => {
<label htmlFor="collection-folder-name" className="flex items-center mt-3">
<span className="font-semibold">Folder Name</span>
<InfoTip
<Tooltip
text="This folder will be created under the selected location"
infotipId="collection-folder-name-infotip"
tooltipId="collection-folder-name-tooltip"
/>
</label>
<input

View File

@@ -4,7 +4,6 @@ import StyledWrapper from './StyledWrapper';
import GitHubButton from 'react-github-btn';
import Preferences from 'components/Preferences';
import Cookies from 'components/Cookies';
import ToolHint from 'components/ToolHint';
import GoldenEdition from './GoldenEdition';
import { useState, useEffect } from 'react';
@@ -96,30 +95,28 @@ const Sidebar = () => {
<div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center select-none">
<div className="flex items-center ml-1 text-xs ">
<a className="mr-2 cursor-pointer" onClick={() => dispatch(showPreferences(true))}>
<ToolHint text="Preferences" toolhintId="Preferences" effect='float' place='top-start' offset={8}>
<IconSettings size={18} strokeWidth={1.5} />
</ToolHint>
<a
title="Preferences"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => dispatch(showPreferences(true))}
>
<IconSettings size={18} strokeWidth={1.5} />
</a>
<a
className="mr-2 cursor-pointer"
title="Cookies"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => setCookiesOpen(true)}
>
<ToolHint text="Cookies" toolhintId="Cookies" offset={8}>
<IconCookie size={18} strokeWidth={1.5} />
</ToolHint>
<IconCookie size={18} strokeWidth={1.5} />
</a>
<a
className="mr-2 cursor-pointer"
title="Golden Edition"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => setGoldenEditonOpen(true)}
>
<ToolHint text="Golden Edition" toolhintId="Golden Edition" offset={8} >
<IconHeart size={18} strokeWidth={1.5} />
</ToolHint>
</a>
<a>
<Notifications />
<IconHeart size={18} strokeWidth={1.5} />
</a>
<Notifications />
</div>
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>
{/* This will get moved to home page */}
@@ -129,10 +126,10 @@ const Sidebar = () => {
data-show-count="true"
aria-label="Star usebruno/bruno on GitHub"
>
Star
Star
</GitHubButton> */}
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.27.0</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.26.2</div>
</div>
</div>
</div>
@@ -140,7 +137,7 @@ const Sidebar = () => {
<div className="absolute drag-sidebar h-full" onMouseDown={handleDragbarMouseDown}>
<div className="drag-request-border" />
</div>
</StyledWrapper >
</StyledWrapper>
);
};

View File

@@ -1,8 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
background-color: ${(props) => props.theme.sidebar.badge};
color: ${(props) => props.theme.text};
`;
export default Wrapper;

View File

@@ -1,47 +0,0 @@
import React from 'react';
import { Tooltip as ReactToolHint } from 'react-tooltip';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
const ToolHint = ({
text,
toolhintId,
children,
tooltipStyle = {},
place = 'top',
offset,
theme = null
}) => {
const { theme: contextTheme } = useTheme();
const appliedTheme = theme || contextTheme;
const toolhintBackgroundColor = appliedTheme?.sidebar.badge.bg || 'black';
const toolhintTextColor = appliedTheme?.text || 'white';
const combinedToolhintStyle = {
...tooltipStyle,
fontSize: '0.75rem',
padding: '0.25rem 0.5rem',
backgroundColor: toolhintBackgroundColor,
color: toolhintTextColor
};
return (
<>
<span id={toolhintId}>{children}</span>
<StyledWrapper theme={appliedTheme}>
<ReactToolHint
anchorId={toolhintId}
html={text}
className="toolhint"
offset={offset}
place={place}
noArrow={true}
style={combinedToolhintStyle}
/>
</StyledWrapper>
</>
);
};
export default ToolHint;

View File

@@ -1,12 +1,12 @@
import React from 'react';
import { Tooltip as ReactInfoTip } from 'react-tooltip';
import { Tooltip as ReactTooltip } from 'react-tooltip';
const InfoTip = ({ text, infotipId }) => {
const Tooltip = ({ text, tooltipId }) => {
return (
<>
<svg
tabIndex="-1"
id={infotipId}
id={tooltipId}
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
@@ -17,9 +17,9 @@ const InfoTip = ({ text, infotipId }) => {
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />
</svg>
<ReactInfoTip anchorId={infotipId} html={text} />
<ReactTooltip anchorId={tooltipId} html={text} />
</>
);
};
export default InfoTip;
export default Tooltip;

View File

@@ -60,7 +60,7 @@ const trackStart = () => {
event: 'start',
properties: {
os: platformLib.os.family,
version: '1.27.0'
version: '1.26.2'
}
});
};

View File

@@ -9,7 +9,7 @@ import NetworkError from 'components/ResponsePane/NetworkError';
import NewRequest from 'components/Sidebar/NewRequest';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { findCollectionByUid, findItemInCollection } from 'utils/collections';
import { closeTabs, switchTab } from 'providers/ReduxStore/slices/tabs';
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
export const HotkeysContext = React.createContext();
@@ -154,41 +154,7 @@ export const HotkeysProvider = (props) => {
};
}, [activeTabUid]);
// Switch to the previous tab
useEffect(() => {
Mousetrap.bind(['command+pageup', 'ctrl+pageup'], (e) => {
dispatch(
switchTab({
direction: 'pageup'
})
);
return false; // this stops the event bubbling
});
return () => {
Mousetrap.unbind(['command+pageup', 'ctrl+pageup']);
};
}, [dispatch]);
// Switch to the next tab
useEffect(() => {
Mousetrap.bind(['command+pagedown', 'ctrl+pagedown'], (e) => {
dispatch(
switchTab({
direction: 'pagedown'
})
);
return false; // this stops the event bubbling
});
return () => {
Mousetrap.unbind(['command+pagedown', 'ctrl+pagedown']);
};
}, [dispatch]);
// Close all tabs
// close all tabs
useEffect(() => {
Mousetrap.bind(['command+shift+w', 'ctrl+shift+w'], (e) => {
const activeTab = find(tabs, (t) => t.uid === activeTabUid);

View File

@@ -1302,71 +1302,6 @@ export const collectionsSlice = createSlice({
set(collection, 'root.request.headers', headers);
}
},
addCollectionVar: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
const type = action.payload.type;
if (collection) {
if (type === 'request') {
const vars = get(collection, 'root.request.vars.req', []);
vars.push({
uid: uuid(),
name: '',
value: '',
enabled: true
});
set(collection, 'root.request.vars.req', vars);
} else if (type === 'response') {
const vars = get(collection, 'root.request.vars.res', []);
vars.push({
uid: uuid(),
name: '',
value: '',
enabled: true
});
set(collection, 'root.request.vars.res', vars);
}
}
},
updateCollectionVar: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
const type = action.payload.type;
if (type === 'request') {
let vars = get(collection, 'root.request.vars.req', []);
const _var = find(vars, (h) => h.uid === action.payload.var.uid);
if (_var) {
_var.name = action.payload.var.name;
_var.value = action.payload.var.value;
_var.description = action.payload.var.description;
_var.enabled = action.payload.var.enabled;
}
set(collection, 'root.request.vars.req', vars);
} else if (type === 'response') {
let vars = get(collection, 'root.request.vars.res', []);
const _var = find(vars, (h) => h.uid === action.payload.var.uid);
if (_var) {
_var.name = action.payload.var.name;
_var.value = action.payload.var.value;
_var.description = action.payload.var.description;
_var.enabled = action.payload.var.enabled;
}
set(collection, 'root.request.vars.res', vars);
}
},
deleteCollectionVar: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
const type = action.payload.type;
if (collection) {
if (type === 'request') {
let vars = get(collection, 'root.request.vars.req', []);
vars = filter(vars, (h) => h.uid !== action.payload.varUid);
set(collection, 'root.request.vars.req', vars);
} else if (type === 'response') {
let vars = get(collection, 'root.request.vars.res', []);
vars = filter(vars, (h) => h.uid !== action.payload.varUid);
set(collection, 'root.request.vars.res', vars);
}
}
},
collectionAddFileEvent: (state, action) => {
const file = action.payload.file;
const isCollectionRoot = file.meta.collectionRoot ? true : false;
@@ -1759,9 +1694,6 @@ export const {
addCollectionHeader,
updateCollectionHeader,
deleteCollectionHeader,
addCollectionVar,
updateCollectionVar,
deleteCollectionVar,
updateCollectionAuthMode,
updateCollectionAuth,
updateCollectionRequestScript,

View File

@@ -48,26 +48,6 @@ export const tabsSlice = createSlice({
focusTab: (state, action) => {
state.activeTabUid = action.payload.uid;
},
switchTab: (state, action) => {
if (!state.tabs || !state.tabs.length) {
state.activeTabUid = null;
return;
}
const direction = action.payload.direction;
const activeTabIndex = state.tabs.findIndex((t) => t.uid === state.activeTabUid);
let toBeActivatedTabIndex = 0;
if (direction == 'pageup') {
toBeActivatedTabIndex = (activeTabIndex - 1 + state.tabs.length) % state.tabs.length;
} else if (direction == 'pagedown') {
toBeActivatedTabIndex = (activeTabIndex + 1) % state.tabs.length;
}
state.activeTabUid = state.tabs[toBeActivatedTabIndex].uid;
},
updateRequestPaneTabWidth: (state, action) => {
const tab = find(state.tabs, (t) => t.uid === action.payload.uid);
@@ -131,7 +111,6 @@ export const tabsSlice = createSlice({
export const {
addTab,
focusTab,
switchTab,
updateRequestPaneTabWidth,
updateRequestPaneTab,
updateResponsePaneTab,

View File

@@ -787,25 +787,24 @@ export const getTotalRequestCountInCollection = (collection) => {
};
export const getAllVariables = (collection, item) => {
const envVariables = getEnvironmentVariables(collection);
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
const environmentVariables = getEnvironmentVariables(collection);
let requestVariables = {};
if (item?.request) {
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
requestVariables = mergeFolderLevelVars(item?.request, requestTreePath);
}
const pathParams = getPathParams(item);
const { processEnvVariables = {}, runtimeVariables = {} } = collection;
return {
...collectionVariables,
...envVariables,
...folderVariables,
...environmentVariables,
...requestVariables,
...runtimeVariables,
...collection.runtimeVariables,
pathParams: {
...pathParams
},
process: {
env: {
...processEnvVariables
...collection.processEnvVariables
}
}
};
@@ -832,22 +831,14 @@ const getTreePathFromCollectionToItem = (collection, _item) => {
return path;
};
const mergeVars = (collection, requestTreePath = []) => {
let collectionVariables = {};
let folderVariables = {};
const mergeFolderLevelVars = (request, requestTreePath = []) => {
let requestVariables = {};
let collectionRequestVars = get(collection, 'root.request.vars.req', []);
collectionRequestVars.forEach((_var) => {
if (_var.enabled) {
collectionVariables[_var.name] = _var.value;
}
});
for (let i of requestTreePath) {
if (i.type === 'folder') {
let vars = get(i, 'root.request.vars.req', []);
vars.forEach((_var) => {
if (_var.enabled) {
folderVariables[_var.name] = _var.value;
requestVariables[_var.name] = _var.value;
}
});
} else {
@@ -859,9 +850,6 @@ const mergeVars = (collection, requestTreePath = []) => {
});
}
}
return {
collectionVariables,
folderVariables,
requestVariables
};
return requestVariables;
};

View File

@@ -149,9 +149,7 @@ export const relativeDate = (dateString) => {
};
export const humanizeDate = (dateString) => {
// See this discussion for why .split is necessary
// https://stackoverflow.com/questions/7556591/is-the-javascript-date-object-always-one-day-off
const date = new Date(dateString.split('-'));
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',

View File

@@ -1,6 +1,6 @@
const { describe, it, expect } = require('@jest/globals');
import { normalizeFileName, startsWith, humanizeDate, relativeDate } from './index';
import { normalizeFileName, startsWith } from './index';
describe('common utils', () => {
describe('normalizeFileName', () => {
@@ -49,50 +49,4 @@ describe('common utils', () => {
expect(startsWith('foo', 'foo')).toBe(true);
});
});
describe('humanizeDate', () => {
it('should return a date string in the en-US locale', () => {
expect(humanizeDate('2024-03-17')).toBe('March 17, 2024');
});
it('should return invalid date if the date is invalid', () => {
expect(humanizeDate('9999-99-99')).toBe('Invalid Date');
});
});
describe('relativeDate', () => {
it('should return few seconds ago', () => {
expect(relativeDate(new Date())).toBe('Few seconds ago');
});
it('should return minutes ago', () => {
let date = new Date();
date.setMinutes(date.getMinutes() - 30);
expect(relativeDate(date)).toBe('30 minutes ago');
});
it('should return hours ago', () => {
let date = new Date();
date.setHours(date.getHours() - 10);
expect(relativeDate(date)).toBe('10 hours ago');
});
it('should return days ago', () => {
let date = new Date();
date.setDate(date.getDate() - 5);
expect(relativeDate(date)).toBe('5 days ago');
});
it('should return weeks ago', () => {
let date = new Date();
date.setDate(date.getDate() - 8);
expect(relativeDate(date)).toBe('1 week ago');
});
it('should return months ago', () => {
let date = new Date();
date.setDate(date.getDate() - 60);
expect(relativeDate(date)).toBe('2 months ago');
});
});
});

View File

@@ -18,17 +18,16 @@ const hasLength = (str) => {
};
export const parseQueryParams = (query) => {
try {
if (!query || !query.length) {
return [];
}
return Array.from(new URLSearchParams(query.split('#')[0]).entries())
.map(([name, value]) => ({ name, value }));
} catch (error) {
console.error('Error parsing query params:', error);
if (!query || !query.length) {
return [];
}
let params = query.split('&').map((param) => {
let [name, value = ''] = param.split('=');
return { name, value };
});
return filter(params, (p) => hasLength(p.name));
};
export const parsePathParams = (url) => {

View File

@@ -49,23 +49,6 @@ describe('Url Utils - parseQueryParams', () => {
{ name: 'b', value: '2' }
]);
});
it('should parse query with "=" character - case 9', () => {
const params = parseQueryParams('a=1&b={color=red,size=large}&c=3');
expect(params).toEqual([
{ name: 'a', value: '1' },
{ name: 'b', value: '{color=red,size=large}' },
{ name: 'c', value: '3' }
]);
});
it('should parse query with fragment - case 10', () => {
const params = parseQueryParams('a=1&b=2#I-AM-FRAGMENT');
expect(params).toEqual([
{ name: 'a', value: '1' },
{ name: 'b', value: '2' }
]);
});
});
describe('Url Utils - parsePathParams', () => {

View File

@@ -60,6 +60,20 @@ const runSingleRequest = async function (
request.data = form;
}
// run pre-request vars
const preRequestVars = get(bruJson, 'request.vars.req');
if (preRequestVars?.length) {
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
varsRuntime.runPreRequestVars(
preRequestVars,
request,
envVariables,
runtimeVariables,
collectionPath,
processEnvVars
);
}
// run pre request script
const requestScriptFile = compact([
get(collectionRoot, 'request.script.req'),
@@ -262,7 +276,7 @@ const runSingleRequest = async function (
console.log(
chalk.green(stripExtension(filename)) +
chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
);
// run post-response vars

View File

@@ -1,5 +1,5 @@
{
"version": "v1.27.0",
"version": "v1.26.2",
"name": "bruno",
"description": "Opensource API Client for Exploring and Testing APIs",
"homepage": "https://www.usebruno.com",

View File

@@ -3,7 +3,7 @@ const path = require('path');
const isDev = require('electron-is-dev');
if (isDev) {
if(!fs.existsSync(path.join(__dirname, '../../bruno-js/src/sandbox/bundle-browser-rollup.js'))) {
if (!fs.existsSync('./src/sandbox/bundle-browser-rollup.js')) {
console.log('JS Sandbox libraries have not been bundled yet');
console.log('Please run the below command \nnpm run sandbox:bundle-libraries --workspace=packages/bruno-js');
throw new Error('JS Sandbox libraries have not been bundled yet');

View File

@@ -290,10 +290,10 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) =>
// Filter out ZWNBSP character
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
data = data.replace(/^\uFEFF/, '');
if (!disableParsingResponseJson) {
if(!disableParsingResponseJson) {
data = JSON.parse(data);
}
} catch { }
} catch {}
return { data, dataBuffer };
};
@@ -319,6 +319,20 @@ const registerNetworkIpc = (mainWindow) => {
processEnvVars,
scriptingConfig
) => {
// run pre-request vars
const preRequestVars = get(request, 'vars.req', []);
if (preRequestVars?.length) {
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
varsRuntime.runPreRequestVars(
preRequestVars,
request,
envVars,
runtimeVariables,
collectionPath,
processEnvVars
);
}
// run pre-request script
let scriptResult;
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL);
@@ -1146,7 +1160,7 @@ const registerNetworkIpc = (mainWindow) => {
try {
const disposition = contentDispositionParser.parse(contentDisposition);
return disposition && disposition.parameters['filename'];
} catch (error) { }
} catch (error) {}
};
const getFileNameFromUrlPath = () => {

View File

@@ -12,17 +12,15 @@ const getContentType = (headers = {}) => {
return contentType;
};
const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => {
const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {};
const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEnvVars = {}) => {
const requestVariables = request?.requestVariables || {};
// we clone envVars because we don't want to modify the original object
envVariables = cloneDeep(envVariables);
envVars = cloneDeep(envVars);
// envVars can inturn have values as {{process.env.VAR_NAME}}
// so we need to interpolate envVars first with processEnvVars
forOwn(envVariables, (value, key) => {
envVariables[key] = interpolate(value, {
forOwn(envVars, (value, key) => {
envVars[key] = interpolate(value, {
process: {
env: {
...processEnvVars
@@ -38,9 +36,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
// runtimeVariables take precedence over envVars
const combinedVars = {
...collectionVariables,
...envVariables,
...folderVariables,
...envVars,
...requestVariables,
...runtimeVariables,
process: {

View File

@@ -44,75 +44,73 @@ const mergeFolderLevelHeaders = (request, requestTreePath) => {
request.headers = Array.from(requestHeadersMap, ([name, value]) => ({ name, value, enabled: true }));
};
const mergeVars = (collection, request, requestTreePath) => {
let reqVars = new Map();
let collectionRequestVars = get(collection, 'root.request.vars.req', []);
let collectionVariables = {};
collectionRequestVars.forEach((_var) => {
if (_var.enabled) {
reqVars.set(_var.name, _var.value);
collectionVariables[_var.name] = _var.value;
}
});
let folderVariables = {};
let requestVariables = {};
const mergeFolderLevelVars = (request, requestTreePath) => {
let folderReqVars = new Map();
for (let i of requestTreePath) {
if (i.type === 'folder') {
let vars = get(i, 'root.request.vars.req', []);
vars.forEach((_var) => {
if (_var.enabled) {
reqVars.set(_var.name, _var.value);
folderVariables[_var.name] = _var.value;
folderReqVars.set(_var.name, _var.value);
}
});
} else {
} else if (i.uid === request.uid) {
const vars = i?.draft ? get(i, 'draft.request.vars.req', []) : get(i, 'request.vars.req', []);
vars.forEach((_var) => {
if (_var.enabled) {
reqVars.set(_var.name, _var.value);
requestVariables[_var.name] = _var.value;
folderReqVars.set(_var.name, _var.value);
}
});
}
}
request.collectionVariables = collectionVariables;
request.folderVariables = folderVariables;
request.requestVariables = requestVariables;
request.vars.req = Array.from(reqVars, ([name, value]) => ({
let mergedFolderReqVars = Array.from(folderReqVars, ([name, value]) => ({ name, value, enabled: true }));
let requestReqVars = request?.vars?.req || [];
let requestReqVarsMap = new Map();
for (let _var of requestReqVars) {
if (_var.enabled) {
requestReqVarsMap.set(_var.name, _var.value);
}
}
mergedFolderReqVars.forEach((_var) => {
requestReqVarsMap.set(_var.name, _var.value);
});
request.vars.req = Array.from(requestReqVarsMap, ([name, value]) => ({
name,
value,
enabled: true,
type: 'request'
}));
let resVars = new Map();
let collectionResponseVars = get(collection, 'root.request.vars.res', []);
collectionResponseVars.forEach((_var) => {
if (_var.enabled) {
resVars.set(_var.name, _var.value);
}
});
let folderResVars = new Map();
for (let i of requestTreePath) {
if (i.type === 'folder') {
let vars = get(i, 'root.request.vars.res', []);
vars.forEach((_var) => {
if (_var.enabled) {
resVars.set(_var.name, _var.value);
folderResVars.set(_var.name, _var.value);
}
});
} else {
} else if (i.uid === request.uid) {
const vars = i?.draft ? get(i, 'draft.request.vars.res', []) : get(i, 'request.vars.res', []);
vars.forEach((_var) => {
if (_var.enabled) {
resVars.set(_var.name, _var.value);
folderResVars.set(_var.name, _var.value);
}
});
}
}
request.vars.res = Array.from(resVars, ([name, value]) => ({
let mergedFolderResVars = Array.from(folderResVars, ([name, value]) => ({ name, value, enabled: true }));
let requestResVars = request?.vars?.res || [];
let requestResVarsMap = new Map();
for (let _var of requestResVars) {
if (_var.enabled) {
requestResVarsMap.set(_var.name, _var.value);
}
}
mergedFolderResVars.forEach((_var) => {
requestResVarsMap.set(_var.name, _var.value);
});
request.vars.res = Array.from(requestResVarsMap, ([name, value]) => ({
name,
value,
enabled: true,
@@ -316,7 +314,7 @@ const prepareRequest = (item, collection) => {
if (requestTreePath && requestTreePath.length > 0) {
mergeFolderLevelHeaders(request, requestTreePath);
mergeFolderLevelScripts(request, requestTreePath, scriptFlow);
mergeVars(collection, request, requestTreePath);
mergeFolderLevelVars(request, requestTreePath);
}
each(request.headers, (h) => {
@@ -403,9 +401,6 @@ const prepareRequest = (item, collection) => {
}
axiosRequest.vars = request.vars;
axiosRequest.collectionVariables = request.collectionVariables;
axiosRequest.folderVariables = request.folderVariables;
axiosRequest.requestVariables = request.requestVariables;
axiosRequest.assertions = request.assertions;
return axiosRequest;

View File

@@ -23,8 +23,7 @@ const defaultPreferences = {
timeout: 0
},
font: {
codeFont: 'default',
codeFontSize: 14
codeFont: 'default'
},
proxy: {
enabled: false,
@@ -55,8 +54,7 @@ const preferencesSchema = Yup.object().shape({
timeout: Yup.number()
}),
font: Yup.object().shape({
codeFont: Yup.string().nullable(),
codeFontSize: Yup.number().min(1).max(32).nullable()
codeFont: Yup.string().nullable()
}),
proxy: Yup.object({
enabled: Yup.boolean(),

View File

@@ -4,12 +4,10 @@ const { interpolate } = require('@usebruno/common');
const variableNameRegex = /^[\w-.]*$/;
class Bru {
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables) {
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables) {
this.envVariables = envVariables || {};
this.runtimeVariables = runtimeVariables || {};
this.processEnvVars = cloneDeep(processEnvVars || {});
this.collectionVariables = collectionVariables || {};
this.folderVariables = folderVariables || {};
this.requestVariables = requestVariables || {};
this.collectionPath = collectionPath;
}
@@ -20,9 +18,7 @@ class Bru {
}
const combinedVars = {
...this.collectionVariables,
...this.envVariables,
...this.folderVariables,
...this.requestVariables,
...this.runtimeVariables,
process: {
@@ -75,7 +71,7 @@ class Bru {
if (variableNameRegex.test(key) === false) {
throw new Error(
`Variable name: "${key}" contains invalid characters!` +
' Names must only contain alpha-numeric characters, "-", "_", "."'
' Names must only contain alpha-numeric characters, "-", "_", "."'
);
}
@@ -86,7 +82,7 @@ class Bru {
if (variableNameRegex.test(key) === false) {
throw new Error(
`Variable name: "${key}" contains invalid characters!` +
' Names must only contain alpha-numeric characters, "-", "_", "."'
' Names must only contain alpha-numeric characters, "-", "_", "."'
);
}
@@ -97,14 +93,6 @@ class Bru {
delete this.runtimeVariables[key];
}
getCollectionVar(key) {
return this._interpolate(this.collectionVariables[key]);
}
getFolderVar(key) {
return this._interpolate(this.folderVariables[key]);
}
getRequestVar(key) {
return this._interpolate(this.requestVariables[key]);
}

View File

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

View File

@@ -192,8 +192,6 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
}
const interpolationContext = {
collectionVariables: context.bru.collectionVariables,
folderVariables: context.bru.folderVariables,
requestVariables: context.bru.requestVariables,
runtimeVariables: context.bru.runtimeVariables,
envVariables: context.bru.envVariables,
@@ -240,23 +238,13 @@ class AssertRuntime {
}
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {};
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
if (!enabledAssertions.length) {
return [];
}
const bru = new Bru(
envVariables,
runtimeVariables,
processEnvVars,
undefined,
collectionVariables,
folderVariables,
requestVariables
);
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, undefined, requestVariables);
const req = new BrunoRequest(request);
const res = createResponseParser(response);
@@ -267,9 +255,7 @@ class AssertRuntime {
};
const context = {
...collectionVariables,
...envVariables,
...folderVariables,
...requestVariables,
...runtimeVariables,
...processEnvVars,

View File

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

View File

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

View File

@@ -1,10 +1,22 @@
const _ = require('lodash');
const Bru = require('../bru');
const BrunoRequest = require('../bruno-request');
const { evaluateJsExpression, createResponseParser } = require('../utils');
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
const { executeQuickJsVm } = require('../sandbox/quickjs');
const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => {
if (runtime === 'quickjs') {
return executeQuickJsVm({
script: literal,
context,
scriptType: 'template-literal'
});
}
return evaluateJsTemplateLiteral(literal, context);
};
const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => {
if (runtime === 'quickjs') {
return executeQuickJsVm({
@@ -23,6 +35,35 @@ class VarsRuntime {
this.mode = props?.mode || 'developer';
}
runPreRequestVars(vars, request, envVariables, runtimeVariables, collectionPath, processEnvVars) {
if (!request?.requestVariables) {
request.requestVariables = {};
}
const enabledVars = _.filter(vars, (v) => v.enabled);
if (!enabledVars.length) {
return;
}
const bru = new Bru(envVariables, runtimeVariables, processEnvVars);
const req = new BrunoRequest(request);
const bruContext = {
bru,
req
};
const context = {
...envVariables,
...runtimeVariables,
...bruContext
};
_.each(enabledVars, (v) => {
const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime);
request?.requestVariables && (request.requestVariables[v.name] = value);
});
}
runPostResponseVars(vars, request, response, envVariables, runtimeVariables, collectionPath, processEnvVars) {
const requestVariables = request?.requestVariables || {};
const enabledVars = _.filter(vars, (v) => v.enabled);

View File

@@ -69,18 +69,6 @@ const addBruShimToContext = (vm, bru) => {
vm.setProp(bruObject, 'getRequestVar', getRequestVar);
getRequestVar.dispose();
let getFolderVar = vm.newFunction('getFolderVar', function (key) {
return marshallToVm(bru.getFolderVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getFolderVar', getFolderVar);
getFolderVar.dispose();
let getCollectionVar = vm.newFunction('getCollectionVar', function (key) {
return marshallToVm(bru.getCollectionVar(vm.dump(key)), vm);
});
vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
getCollectionVar.dispose();
const sleep = vm.newFunction('sleep', (timer) => {
const t = vm.getString(timer);
const promise = vm.newPromise();

View File

@@ -1,6 +1,5 @@
headers {
check: again
token: {{collection_pre_var_token}}
}
auth {
@@ -11,11 +10,6 @@ auth:bearer {
token: {{bearer_auth_token}}
}
vars:pre-request {
collection_pre_var: collection_pre_var_value
collection_pre_var_token: {{request_pre_var_token}}
}
docs {
# bruno-testbench 🐶

View File

@@ -25,11 +25,13 @@ auth:bearer {
body:json {
{
"hello": 990531470713421825,
"decimal": 1.0,
"decimal2": 1.00,
"decimal3": 1.00200,
"decimal4": 0.00
"hello": 990531470713421825
}
}
body:text {
{
"hello": 990531470713421825
}
}
@@ -37,6 +39,6 @@ assert {
res.status: eq 200
}
tests {
// todo: add tests once lossless json echo server is ready
script:pre-request {
bru.setVar("foo", "foo-world-2");
}

View File

@@ -25,6 +25,16 @@ body:json {
}
}
vars:pre-request {
boolean: false
undefined: undefined
null: null
string: foo
number_1: 1
number_2: 0
number_3: -1
}
assert {
req.body.boolean: isBoolean false
req.body.number_1: isNumber 1
@@ -41,4 +51,35 @@ assert {
req.body.number_3: eq -1
req.body.number_2: isNumber
req.body.number_3: isNumber
boolean: eq false
undefined: eq undefined
null: eq null
string: eq foo
number_1: eq 1
number_2: eq 0
number_3: eq -1
}
tests {
test("boolean pre var", function() {
expect(bru.getRequestVar('boolean')).to.eql(false);
});
test("number pre var", function() {
expect(bru.getRequestVar('number_1')).to.eql(1);
expect(bru.getRequestVar('number_2')).to.eql(0);
expect(bru.getRequestVar('number_3')).to.eql(-1);
});
test("null pre var", function() {
expect(bru.getRequestVar('null')).to.eql(null);
});
test("undefined pre var", function() {
expect(bru.getRequestVar('undefined')).to.eql(undefined);
});
test("string pre var", function() {
expect(bru.getRequestVar('string')).to.eql('foo');
});
}

View File

@@ -1,8 +0,0 @@
meta {
name: string interpolation
}
vars:pre-request {
folder_pre_var: folder_pre_var_value
folder_pre_var_2: {{env.var1}}
}

View File

@@ -1,48 +0,0 @@
meta {
name: pre-vars
type: http
seq: 5
}
get {
url: {{host}}/ping
body: none
auth: none
}
headers {
request_pre_var: {{request_pre_var}}
}
vars:pre-request {
request_pre_var: {{folder_pre_var}}
request_pre_var_token: request_pre_var_token_value
request_pre_var_1: request_pre_var_1_value
request_pre_var_2: {{request_pre_var_1}}
}
assert {
collection_pre_var: eq collection_pre_var_value
folder_pre_var: eq folder_pre_var_value
}
tests {
test("collection pre var bru function", function() {
expect(bru.getCollectionVar('collection_pre_var')).to.eql('collection_pre_var_value');
});
test("folder pre var bru function", function() {
expect(bru.getFolderVar('folder_pre_var')).to.eql('folder_pre_var_value');
});
test("request pre var bru function", function() {
expect(bru.getRequestVar('request_pre_var')).to.eql('folder_pre_var_value');
});
test("request pre var self-interpoaltion", function() {
expect(bru.getRequestVar('request_pre_var_2')).to.eql('request_pre_var_1_value');
});
}

View File

@@ -43,34 +43,14 @@ Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We v
![bruno](assets/images/landing-2.png) <br /><br />
## Golden Edition ✨
### Golden Edition ✨
Majority of our features are free and open source.
We strive to strike a harmonious balance between [open-source principles and sustainability](https://github.com/usebruno/bruno/discussions/269)
You can buy the [Golden Edition](https://www.usebruno.com/pricing) for a one-time payment of **$19**! <br/>
## Table of Contents
- [Installation](#installation)
- [Features](#features)
- [Run across multiple platforms 🖥️](#run-across-multiple-platforms-%EF%B8%8F)
- [Collaborate via Git 👩‍💻🧑‍💻](#collaborate-via-git-)
- [Sponsors](#sponsors)
- [Gold Sponsors](#gold-sponsors)
- [Silver Sponsors](#silver-sponsors)
- [Bronze Sponsors](#bronze-sponsors)
- [Important Links 📌](#important-links-)
- [Showcase 🎥](#showcase-)
- [Support ❤️](#support-%EF%B8%8F)
- [Share Testimonials 📣](#share-testimonials-)
- [Publishing to New Package Managers](#publishing-to-new-package-managers)
- [Stay in touch 🌐](#stay-in-touch-)
- [Trademark](#trademark)
- [Contribute 👩‍💻🧑‍💻](#contribute-)
- [Authors](#authors)
- [License 📄](#license-)
## Installation
### Installation
Bruno is available as binary download [on our website](https://www.usebruno.com/downloads) for Mac, Windows and Linux.
@@ -106,8 +86,6 @@ sudo apt update
sudo apt install bruno
```
## Features
### Run across multiple platforms 🖥️
![bruno](assets/images/run-anywhere.png) <br /><br />
@@ -118,7 +96,7 @@ Or any version control system of your choice
![bruno](assets/images/version-control.png) <br /><br />
## Sponsors
### Sponsors
#### Gold Sponsors
@@ -134,7 +112,7 @@ Or any version control system of your choice
<img src="assets/images/sponsors/zuplo.png" width="120"/>
</a>
## Important Links 📌
### Important Links 📌
- [Our Long Term Vision](https://github.com/usebruno/bruno/discussions/269)
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
@@ -145,32 +123,32 @@ Or any version control system of your choice
- [Download](https://www.usebruno.com/downloads)
- [GitHub Sponsors](https://github.com/sponsors/helloanoop).
## Showcase 🎥
### Showcase 🎥
- [Testimonials](https://github.com/usebruno/bruno/discussions/343)
- [Knowledge Hub](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
## Support ❤️
### Support ❤️
If you like Bruno and want to support our opensource work, consider sponsoring us via [GitHub Sponsors](https://github.com/sponsors/helloanoop).
## Share Testimonials 📣
### Share Testimonials 📣
If Bruno has helped you at work and your teams, please don't forget to share your [testimonials on our GitHub discussion](https://github.com/usebruno/bruno/discussions/343)
## Publishing to New Package Managers
### Publishing to New Package Managers
Please see [here](publishing.md) for more information.
## Stay in touch 🌐
### Stay in touch 🌐
[𝕏 (Twitter)](https://twitter.com/use_bruno) <br />
[Website](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
## Trademark
### Trademark
**Name**
@@ -180,13 +158,13 @@ Please see [here](publishing.md) for more information.
The logo is sourced from [OpenMoji](https://openmoji.org/library/emoji-1F436/). License: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
## Contribute 👩‍💻🧑‍💻
### Contribute 👩‍💻🧑‍💻
I am happy that you are looking to improve bruno. Please check out the [contributing guide](contributing.md)
Even if you are not able to make contributions via code, please don't hesitate to file bugs and feature requests that needs to be implemented to solve your use case.
## Authors
### Authors
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
@@ -194,6 +172,6 @@ Even if you are not able to make contributions via code, please don't hesitate t
</a>
</div>
## License 📄
### License 📄
[MIT](license.md)