From 7b55d52c11e8a89bd53cf09deedfa05f66e78de2 Mon Sep 17 00:00:00 2001 From: bplatta Date: Thu, 12 Oct 2023 21:53:14 -0500 Subject: [PATCH 001/835] firstpass at adding openapi v3 import json --- .../Sidebar/ImportCollection/index.js | 12 + .../src/utils/importers/openapi-collection.js | 346 ++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 packages/bruno-app/src/utils/importers/openapi-collection.js diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js index 185e3c5cb..c119963db 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js @@ -2,6 +2,7 @@ import React from 'react'; import importBrunoCollection from 'utils/importers/bruno-collection'; import importPostmanCollection from 'utils/importers/postman-collection'; import importInsomniaCollection from 'utils/importers/insomnia-collection'; +import importOpenapiCollection from 'utils/importers/openapi-collection'; import { toastError } from 'utils/common/error'; import Modal from 'components/Modal'; @@ -30,6 +31,14 @@ const ImportCollection = ({ onClose, handleSubmit }) => { .catch((err) => toastError(err, 'Insomnia Import collection failed')); }; + const handleImportOpenapiCollection = () => { + importOpenapiCollection() + .then((collection) => { + handleSubmit(collection); + }) + .catch((err) => toastError(err, 'OpenAPI v3 Import collection failed')); + }; + return (
@@ -42,6 +51,9 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
Insomnia Collection
+
+ OpenAPI Collection +
); diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js new file mode 100644 index 000000000..a43c01e3d --- /dev/null +++ b/packages/bruno-app/src/utils/importers/openapi-collection.js @@ -0,0 +1,346 @@ +import each from 'lodash/each'; +import get from 'lodash/get'; +import fileDialog from 'file-dialog'; +import { uuid } from 'utils/common'; +import { BrunoError } from 'utils/common/error'; +import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } from './common'; + +const readFile = (files) => { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (e) => resolve(e.target.result); + fileReader.onerror = (err) => reject(err); + fileReader.readAsText(files[0]); + }); +}; + +const ensureUrl = (url) => { + let protUrl = url.startsWith('http') ? url : `http://${url}`; + // replace any double or triple slashes + return protUrl.replace(/([^:]\/)\/+/g, '$1'); +}; + +const transformOpenapiRequestItem = (request) => { + console.log(request); + let _operationObject = request.operationObject; + const brunoRequestItem = { + uid: uuid(), + name: _operationObject.operationId, + type: 'http-request', + request: { + url: ensureUrl(request.global.server + '/' + request.path), + method: request.method.toUpperCase(), + auth: { + mode: 'none', + basic: null, + bearer: null + }, + headers: [], + params: [], + body: { + mode: 'none', + json: null, + text: null, + xml: null, + formUrlEncoded: [], + multipartForm: [] + } + } + }; + + each(_operationObject.parameters || [], (param) => { + if (param.in === 'query') { + brunoRequestItem.request.params.push({ + uid: uuid(), + name: param.name, + value: '', + description: param.description || '', + enabled: param.required + }); + } else if (param.in === 'header') { + brunoRequestItem.request.headers.push({ + uid: uuid(), + name: param.name, + value: '', + description: param.description || '', + enabled: param.required + }); + } + }); + + let auth; + // allow operation override + if (_operationObject.security) { + let schemeName = Object.keys(_operationObject.security[0])[0]; + auth = request.global.security.getScheme(schemeName); + } else if (request.global.security.supported.length > 0) { + auth = request.global.security.supported[0]; + } + + if (auth) { + if (auth.type === 'http' && auth.scheme === 'basic') { + brunoRequestItem.request.auth.mode = 'basic'; + brunoRequestItem.request.auth.basic = { + username: '{{username}}', + password: '{{password}}' + }; + } else if (auth.type === 'http' && auth.scheme === 'bearer') { + brunoRequestItem.request.auth.mode = 'bearer'; + brunoRequestItem.request.auth.bearer = { + token: '{{token}}' + }; + } else if (auth.type === 'apiKey' && auth.in === 'header') { + brunoRequestItem.request.headers.push({ + uid: uuid(), + name: auth.name, + value: '{{apiKey}}', + description: 'Authentication header', + enabled: true + }); + } + } + + // TODO: handle allOf/anyOf/oneOf + if (_operationObject.requestBody) { + let content = get(_operationObject, 'requestBody.content', {}); + Object.entries(content).forEach(([mimeType, body]) => { + if (mimeType === 'application/json') { + brunoRequestItem.request.body.mode = 'json'; + if (body.schema && body.schema.type === 'object') { + let _jsonBody = {}; + each(body.schema.properties || {}, (prop, name) => { + _jsonBody[name] = ''; + }); + brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); + } + } else if (mimeType === 'application/x-www-form-urlencoded') { + brunoRequestItem.request.body.mode = 'formUrlEncoded'; + if (body.schema && body.schema.type === 'object') { + each(body.schema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.formUrlEncoded.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'multipart/form-data') { + brunoRequestItem.request.body.mode = 'multipartForm'; + if (body.schema && body.schema.type === 'object') { + each(body.schema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.multipartForm.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'text/plain') { + brunoRequestItem.request.body.mode = 'text'; + brunoRequestItem.request.body.text = ''; + } else if (mimeType === 'text/xml') { + brunoRequestItem.request.body.mode = 'xml'; + brunoRequestItem.request.body.xml = ''; + } + }); + } + + return brunoRequestItem; +}; + +const resolveRefs = (spec, components = spec.components) => { + if (!spec || typeof spec !== 'object') { + return spec; + } + + if (Array.isArray(spec)) { + return spec.map((item) => resolveRefs(item, components)); + } + + if ('$ref' in spec) { + const refPath = spec.$ref; + + if (refPath.startsWith('#/components/')) { + // Local reference within components + const refKeys = refPath.replace('#/components/', '').split('/'); + let ref = components; + + for (const key of refKeys) { + if (ref[key]) { + ref = ref[key]; + } else { + // Handle invalid references gracefully? + return spec; + } + } + + return resolveRefs(ref, components); + } else { + // Handle external references (not implemented here) + // You would need to fetch the external reference and resolve it. + // Example: Fetch and resolve an external reference from a URL. + } + } + + // Recursively resolve references in nested objects + for (const prop in spec) { + spec[prop] = resolveRefs(spec[prop], components); + } + + return spec; +}; + +const groupRequestsByTags = (requests) => { + let _groups = {}; + let ungrouped = []; + each(requests, (request) => { + let tags = request.operationObject.tags || []; + if (tags.length > 0) { + let tag = tags[0]; // take first tag + if (!_groups[tag]) { + _groups[tag] = []; + } + _groups[tag].push(request); + } else { + ungrouped.push(request); + } + }); + + let groups = Object.keys(_groups).map((groupName) => { + return { + name: groupName, + requests: _groups[groupName] + }; + }); + + return [groups, ungrouped]; +}; + +const getDefaultUrl = (serverObject) => { + let url = serverObject.url; + if (serverObject.variables) { + each(serverObject.variables, (variable, variableName) => { + let sub = variable.default || (variable.enum ? variable.enum[0] : `{{${variableName}}}`); + url = url.replace(`{${variableName}}`, sub); + }); + } + return url; +}; + +const getSecurity = (apiSpec) => { + let supportedSchemes = apiSpec.security || []; + if (supportedSchemes.length === 0) { + return { + supported: [] + }; + } + + let securitySchemes = get(apiSpec, 'components.securitySchemes', {}); + if (Object.keys(securitySchemes) === 0) { + return { + supported: [] + }; + } + + return { + supported: supportedSchemes.map((scheme) => { + var schemeName = Object.keys(scheme)[0]; + return securitySchemes[schemeName]; + }), + schemes: securitySchemes, + getScheme: (schemeName) => { + return securitySchemes[schemeName]; + } + }; +}; + +const parseOpenapiCollection = (data) => { + const brunoCollection = { + name: '', + uid: uuid(), + version: '1', + items: [], + environments: [] + }; + + return new Promise((resolve, reject) => { + try { + const collectionData = resolveRefs(JSON.parse(data)); + if (!collectionData) { + reject(new BrunoError('Invalid OpenAPI collection. Failed to resolve refs.')); + return; + } + + // Currently parsing of openapi spec is "do your best", that is + // allows "invalid" openapi spec + + // assumes v3 if not defined. v2 no supported yet + if (collectionData.openapi && !collectionData.openapi.startsWith('3')) { + reject(new BrunoError('Only OpenAPI v3 is supported currently.')); + return; + } + + // TODO what if info.title not defined? + brunoCollection.name = collectionData.info.title; + let servers = collectionData.servers || []; + let baseUrl = servers[0] ? getDefaultUrl(servers[0]) : ''; + let securityConfig = getSecurity(collectionData); + + let allRequests = Object.entries(collectionData.paths) + .map(([path, methods]) => { + return Object.entries(methods).map(([method, operationObject]) => { + return { + method: method, + path: path, + operationObject: operationObject, + global: { + server: baseUrl, + security: securityConfig + } + }; + }); + }) + .reduce((acc, val) => acc.concat(val), []); // flatten + + let [groups, ungroupedRequests] = groupRequestsByTags(allRequests); + let brunoFolders = groups.map((group) => { + return { + uid: uuid(), + name: group.name, + type: 'folder', + items: group.requests.map(transformOpenapiRequestItem) + }; + }); + + let ungroupedItems = ungroupedRequests.map(transformOpenapiRequestItem); + let brunoCollectionItems = brunoFolders.concat(ungroupedItems); + brunoCollection.items = brunoCollectionItems; + resolve(brunoCollection); + } catch (err) { + console.error(err); + reject(new BrunoError('An error occurred while parsing the OpenAPI collection')); + } + }); +}; + +const importCollection = () => { + return new Promise((resolve, reject) => { + fileDialog({ accept: 'application/json' }) + .then(readFile) + .then(parseOpenapiCollection) + .then(transformItemsInCollection) + .then(hydrateSeqInCollection) + .then(validateSchema) + .then((collection) => resolve(collection)) + .catch((err) => { + console.log(err); + reject(new BrunoError('Import collection failed: ' + err.message)); + }); + }); +}; + +export default importCollection; From 35db296c1f19564c3c0c67ca60cbb92d1b62fe65 Mon Sep 17 00:00:00 2001 From: bplatta Date: Fri, 13 Oct 2023 10:50:47 -0500 Subject: [PATCH 002/835] Update console logs --- packages/bruno-app/src/utils/importers/openapi-collection.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js index a43c01e3d..3c9178219 100644 --- a/packages/bruno-app/src/utils/importers/openapi-collection.js +++ b/packages/bruno-app/src/utils/importers/openapi-collection.js @@ -21,7 +21,6 @@ const ensureUrl = (url) => { }; const transformOpenapiRequestItem = (request) => { - console.log(request); let _operationObject = request.operationObject; const brunoRequestItem = { uid: uuid(), @@ -337,7 +336,7 @@ const importCollection = () => { .then(validateSchema) .then((collection) => resolve(collection)) .catch((err) => { - console.log(err); + console.error(err); reject(new BrunoError('Import collection failed: ' + err.message)); }); }); From 36ee1f542a4a902355c55d9290f756c3304df1dc Mon Sep 17 00:00:00 2001 From: bplatta Date: Fri, 13 Oct 2023 11:30:27 -0500 Subject: [PATCH 003/835] build entire json object for request and only support first requestBody content type --- .../src/utils/importers/openapi-collection.js | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js index 3c9178219..440f3d521 100644 --- a/packages/bruno-app/src/utils/importers/openapi-collection.js +++ b/packages/bruno-app/src/utils/importers/openapi-collection.js @@ -20,6 +20,21 @@ const ensureUrl = (url) => { return protUrl.replace(/([^:]\/)\/+/g, '$1'); }; +const buildEmptyJsonBody = (bodySchema) => { + let _jsonBody = {}; + each(bodySchema.properties || {}, (prop, name) => { + if (prop.type === 'object') { + _jsonBody[name] = buildEmptyJsonBody(prop); + // handle arrays + } else if (prop.type === 'array') { + _jsonBody[name] = []; + } else { + _jsonBody[name] = ''; + } + }); + return _jsonBody; +}; + const transformOpenapiRequestItem = (request) => { let _operationObject = request.operationObject; const brunoRequestItem = { @@ -102,50 +117,48 @@ const transformOpenapiRequestItem = (request) => { // TODO: handle allOf/anyOf/oneOf if (_operationObject.requestBody) { let content = get(_operationObject, 'requestBody.content', {}); - Object.entries(content).forEach(([mimeType, body]) => { - if (mimeType === 'application/json') { - brunoRequestItem.request.body.mode = 'json'; - if (body.schema && body.schema.type === 'object') { - let _jsonBody = {}; - each(body.schema.properties || {}, (prop, name) => { - _jsonBody[name] = ''; - }); - brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); - } - } else if (mimeType === 'application/x-www-form-urlencoded') { - brunoRequestItem.request.body.mode = 'formUrlEncoded'; - if (body.schema && body.schema.type === 'object') { - each(body.schema.properties || {}, (prop, name) => { - brunoRequestItem.request.body.formUrlEncoded.push({ - uid: uuid(), - name: name, - value: '', - description: prop.description || '', - enabled: true - }); - }); - } - } else if (mimeType === 'multipart/form-data') { - brunoRequestItem.request.body.mode = 'multipartForm'; - if (body.schema && body.schema.type === 'object') { - each(body.schema.properties || {}, (prop, name) => { - brunoRequestItem.request.body.multipartForm.push({ - uid: uuid(), - name: name, - value: '', - description: prop.description || '', - enabled: true - }); - }); - } - } else if (mimeType === 'text/plain') { - brunoRequestItem.request.body.mode = 'text'; - brunoRequestItem.request.body.text = ''; - } else if (mimeType === 'text/xml') { - brunoRequestItem.request.body.mode = 'xml'; - brunoRequestItem.request.body.xml = ''; + let mimeType = Object.keys(content)[0]; + let body = content[mimeType] || {}; + let bodySchema = body.schema; + if (mimeType === 'application/json') { + brunoRequestItem.request.body.mode = 'json'; + if (bodySchema && bodySchema.type === 'object') { + let _jsonBody = buildEmptyJsonBody(bodySchema); + brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); } - }); + } else if (mimeType === 'application/x-www-form-urlencoded') { + brunoRequestItem.request.body.mode = 'formUrlEncoded'; + if (bodySchema && bodySchema.type === 'object') { + each(bodySchema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.formUrlEncoded.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'multipart/form-data') { + brunoRequestItem.request.body.mode = 'multipartForm'; + if (bodySchema && bodySchema.type === 'object') { + each(bodySchema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.multipartForm.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'text/plain') { + brunoRequestItem.request.body.mode = 'text'; + brunoRequestItem.request.body.text = ''; + } else if (mimeType === 'text/xml') { + brunoRequestItem.request.body.mode = 'xml'; + brunoRequestItem.request.body.xml = ''; + } } return brunoRequestItem; From 4258e031c66ee6591c0cfacadada0a2fd6c33199 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Mon, 16 Oct 2023 14:05:44 +0200 Subject: [PATCH 004/835] docs(readme): add simple installation instructions Many people just want to know how to get it directly from the Github readme --- readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/readme.md b/readme.md index 8021a5b7a..006191f5b 100644 --- a/readme.md +++ b/readme.md @@ -22,6 +22,14 @@ Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We v ![bruno](assets/images/landing-2.png)

+### Installation + +Bruno is available as binary download [on our website](https://www.usebruno.com/downloads) or via homebrew + +```sh +brew install bruno +``` + ### Run across multiple platforms 🖥️ ![bruno](assets/images/run-anywhere.png)

From d4c0207545ab111dabe6e37d190b26f9b14088ec Mon Sep 17 00:00:00 2001 From: Martin Hoecker Date: Mon, 16 Oct 2023 13:28:58 +0200 Subject: [PATCH 005/835] feat: bru.setNextRequest() --- packages/bruno-cli/src/commands/run.js | 17 ++++++++++++- .../src/runner/run-single-request.js | 17 +++++++++---- .../bruno-electron/src/ipc/network/index.js | 24 ++++++++++++++++++- packages/bruno-js/src/bru.js | 4 ++++ .../bruno-js/src/runtime/script-runtime.js | 6 +++-- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 78b7226ca..e9b98e9e5 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -357,7 +357,9 @@ const handler = async function (argv) { } } - for (const iter of bruJsons) { + let currentRequestIndex = 0; + while (currentRequestIndex < bruJsons.length) { + const iter = bruJsons[currentRequestIndex]; const { bruFilepath, bruJson } = iter; const result = await runSingleRequest( bruFilepath, @@ -370,6 +372,19 @@ const handler = async function (argv) { collectionRoot ); + const nextRequestName = result?.nextRequestName; + if (nextRequestName) { + const nextRequestIdx = bruJsons.findIndex((iter) => iter.bruJson.name === nextRequestName); + if (nextRequestIdx > 0) { + currentRequestIndex = nextRequestIdx; + } else { + console.error("Could not find request with name '" + nextRequestName + "'"); + currentRequestIndex++; + } + } else { + currentRequestIndex++; + } + results.push(result); } diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index ee67f60b3..d6f3107a7 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -29,6 +29,7 @@ const runSingleRequest = async function ( ) { try { let request; + let nextRequestName; request = prepareRequest(bruJson.request, collectionRoot); @@ -66,7 +67,7 @@ const runSingleRequest = async function ( ]).join(os.EOL); if (requestScriptFile && requestScriptFile.length) { const scriptRuntime = new ScriptRuntime(); - await scriptRuntime.runRequestScript( + const result = await scriptRuntime.runRequestScript( decomment(requestScriptFile), request, envVariables, @@ -76,6 +77,9 @@ const runSingleRequest = async function ( processEnvVars, scriptingConfig ); + if (result?.nextRequestName) { + nextRequestName = result.nextRequestName; + } } // interpolate variables inside request @@ -187,7 +191,8 @@ const runSingleRequest = async function ( }, error: err.message, assertionResults: [], - testResults: [] + testResults: [], + nextRequestName: nextRequestName }; } } @@ -219,7 +224,7 @@ const runSingleRequest = async function ( ]).join(os.EOL); if (responseScriptFile && responseScriptFile.length) { const scriptRuntime = new ScriptRuntime(); - await scriptRuntime.runResponseScript( + const result = await scriptRuntime.runResponseScript( decomment(responseScriptFile), request, response, @@ -230,6 +235,9 @@ const runSingleRequest = async function ( processEnvVars, scriptingConfig ); + if (result?.nextRequestName) { + nextRequestName = result.nextRequestName; + } } // run assertions @@ -301,7 +309,8 @@ const runSingleRequest = async function ( }, error: null, assertionResults, - testResults + testResults, + nextRequestName: nextRequestName }; } catch (err) { console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`)); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index ebd780ece..2995e5742 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -605,7 +605,10 @@ const registerNetworkIpc = (mainWindow) => { }); } - for (let item of folderRequests) { + let currentRequestIndex = 0; + while (currentRequestIndex < folderRequests.length) { + item = folderRequests[currentRequestIndex]; + let nextRequestName = undefined; const itemUid = item.uid; const eventData = { collectionUid, @@ -685,6 +688,10 @@ const registerNetworkIpc = (mainWindow) => { collectionVariables: result.collectionVariables, collectionUid }); + + if (result?.nextRequestName) { + nextRequestName = result.nextRequestName; + } } // interpolate variables inside request @@ -807,6 +814,10 @@ const registerNetworkIpc = (mainWindow) => { collectionVariables: result.collectionVariables, collectionUid }); + + if (result?.nextRequestName) { + nextRequestName = result.nextRequestName; + } } // run assertions @@ -963,6 +974,17 @@ const registerNetworkIpc = (mainWindow) => { ...eventData }); } + if (nextRequestName) { + const nextRequestIdx = folderRequests.findIndex((request) => request.name === nextRequestName); + if (nextRequestIdx > 0) { + currentRequestIndex = nextRequestIdx; + } else { + console.error("Could not find request with name '" + nextRequestName + "'"); + currentRequestIndex++; + } + } else { + currentRequestIndex++; + } } mainWindow.webContents.send('main:run-folder-event', { diff --git a/packages/bruno-js/src/bru.js b/packages/bruno-js/src/bru.js index 3cd9e8f5f..10af84a50 100644 --- a/packages/bruno-js/src/bru.js +++ b/packages/bruno-js/src/bru.js @@ -65,6 +65,10 @@ class Bru { getVar(key) { return this.collectionVariables[key]; } + + setNextRequest(nextRequest) { + this.nextRequest = nextRequest; + } } module.exports = Bru; diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index 391d047d5..c395bc7dd 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -116,7 +116,8 @@ class ScriptRuntime { return { request, envVariables: cleanJson(envVariables), - collectionVariables: cleanJson(collectionVariables) + collectionVariables: cleanJson(collectionVariables), + nextRequestName: bru.nextRequest }; } @@ -207,7 +208,8 @@ class ScriptRuntime { return { response, envVariables: cleanJson(envVariables), - collectionVariables: cleanJson(collectionVariables) + collectionVariables: cleanJson(collectionVariables), + nextRequestName: bru.nextRequest }; } } From 232027ff4ed6a3a2536de25638eb8b50f7fcfadb Mon Sep 17 00:00:00 2001 From: cardonator Date: Wed, 18 Oct 2023 12:35:48 -0600 Subject: [PATCH 006/835] adjust styles on environment dropdown and modal --- packages/bruno-app/src/components/Dropdown/StyledWrapper.js | 2 ++ .../EnvironmentSettings/EnvironmentList/StyledWrapper.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js index ceb2fb1d7..6ad94e289 100644 --- a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js +++ b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js @@ -14,6 +14,8 @@ const Wrapper = styled.div` background-color: ${(props) => props.theme.dropdown.bg}; box-shadow: ${(props) => props.theme.dropdown.shadow}; border-radius: 3px; + max-height: 90vh; + overflow-y: auto; .tippy-content { padding-left: 0; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js index 687cde46c..d6639b7db 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js @@ -10,7 +10,8 @@ const StyledWrapper = styled.div` background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg}; border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight}; min-height: 400px; - height: 100%; + max-height: 85vh; + overflow-y: auto; } .environment-item { From f2c32a881246e9d50f207dd6ac5ecc6199af9111 Mon Sep 17 00:00:00 2001 From: Waldemar Heinze Date: Wed, 18 Oct 2023 21:35:43 +0200 Subject: [PATCH 007/835] docs(readme): add missing linebreaks --- readme.md | 6 +++--- readme_ua.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index cf3d2dc61..b696821c7 100644 --- a/readme.md +++ b/readme.md @@ -52,11 +52,11 @@ Woof! If you like project, hit that ⭐ button !! ### 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) +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) ### Contribute 👩‍💻🧑‍💻 -I am happy that you are looking to improve bruno. Please checkout the [contributing guide](contributing.md) +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. @@ -72,7 +72,7 @@ Even if you are not able to make contributions via code, please don't hesitate t [Twitter](https://twitter.com/use_bruno)
[Website](https://www.usebruno.com)
-[Discord](https://discord.com/invite/KgcZUncpjq) +[Discord](https://discord.com/invite/KgcZUncpjq)
[LinkedIn](https://www.linkedin.com/company/usebruno) ### License 📄 diff --git a/readme_ua.md b/readme_ua.md index 793e11a8e..fa6700e0a 100644 --- a/readme_ua.md +++ b/readme_ua.md @@ -72,7 +72,7 @@ Bruno є повністю автономним. Немає жодних план [Twitter](https://twitter.com/use_bruno)
[Сайт](https://www.usebruno.com)
-[Discord](https://discord.com/invite/KgcZUncpjq) +[Discord](https://discord.com/invite/KgcZUncpjq)
[LinkedIn](https://www.linkedin.com/company/usebruno) ### Ліцензія 📄 From 21c49093a5caaa4d64a67c243fb45957cf53f2da Mon Sep 17 00:00:00 2001 From: jcari-dev <582119jc@gmail.com> Date: Wed, 18 Oct 2023 18:00:45 -0400 Subject: [PATCH 008/835] fixed typo in contributing.md --- contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 966a6134b..23501f4ef 100644 --- a/contributing.md +++ b/contributing.md @@ -33,5 +33,5 @@ Please reference [development.md](docs/development.md) for instructions on runni - Please follow the format of creating branches - feature/[feature name]: This branch should contain changes for a specific feature - Example: feature/dark-mode - - bugfix/[bug name]: This branch should container only bug fixes for a specific bug + - bugfix/[bug name]: This branch should contain only bug fixes for a specific bug - Example bugfix/bug-1 From ddddab811290c82e15a7008c4027792fe7772bf7 Mon Sep 17 00:00:00 2001 From: Louis Wilke Date: Thu, 19 Oct 2023 21:28:21 +0200 Subject: [PATCH 009/835] 571 - allow and encode multiline uri form data --- .../components/RequestPane/FormUrlEncodedParams/index.js | 1 + packages/bruno-app/src/components/SingleLineEditor/index.js | 6 +++++- packages/bruno-lang/v2/src/bruToJson.js | 2 +- packages/bruno-lang/v2/src/jsonToBru.js | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js index 3cf5842e8..6d1d5be25 100644 --- a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js @@ -107,6 +107,7 @@ const FormUrlEncodedParams = ({ item, collection }) => { 'value' ) } + allowNewlines={true} onRun={handleRun} collection={collection} /> diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index bee59b2d9..fb3bff212 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -57,6 +57,7 @@ class SingleLineEditor extends Component { } componentDidMount() { // Initialize CodeMirror as a single line editor + /** @type {import("codemirror").Editor} */ this.editor = CodeMirror(this.editorRef.current, { lineWrapping: false, lineNumbers: false, @@ -84,7 +85,10 @@ class SingleLineEditor extends Component { } }, 'Alt-Enter': () => { - if (this.props.onRun) { + if (this.props.allowNewlines) { + this.editor.setValue(this.editor.getValue() + '\n'); + this.editor.setCursor({ line: this.editor.lineCount(), ch: 0 }); + } else if (this.props.onRun) { this.props.onRun(); } }, diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 5ce65eba0..d8d51d3b1 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -104,7 +104,7 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => { } return _.map(pairList[0], (pair) => { let name = _.keys(pair)[0]; - let value = pair[name]; + let value = decodeURIComponent(pair[name]); if (!parseEnabled) { return { diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 757406b70..56b5ec478 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -154,7 +154,7 @@ ${indentString(body.sparql)} if (enabled(body.formUrlEncoded).length) { bru += `\n${indentString( enabled(body.formUrlEncoded) - .map((item) => `${item.name}: ${item.value}`) + .map((item) => `${item.name}: ${encodeURIComponent(item.value)}`) .join('\n') )}`; } @@ -162,7 +162,7 @@ ${indentString(body.sparql)} if (disabled(body.formUrlEncoded).length) { bru += `\n${indentString( disabled(body.formUrlEncoded) - .map((item) => `~${item.name}: ${item.value}`) + .map((item) => `~${item.name}: ${encodeURIComponent(item.value)}`) .join('\n') )}`; } From b00d35f007243be0f4da84e5b31b0708dadeebc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fukan=20=C3=9Crker?= Date: Fri, 20 Oct 2023 00:22:19 +0300 Subject: [PATCH 010/835] Turkish translation for ReadMe page. --- readme.md | 2 +- readme_ru.md | 2 +- readme_tr.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ readme_ua.md | 2 +- 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 readme_tr.md diff --git a/readme.md b/readme.md index cf3d2dc61..33755c2c0 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,7 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) -**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) +**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there. diff --git a/readme_ru.md b/readme_ru.md index d8b8255f7..207c399bb 100644 --- a/readme_ru.md +++ b/readme_ru.md @@ -10,7 +10,7 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) -[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** +[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** | [Türkçe](/readme_tr.md) Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами. diff --git a/readme_tr.md b/readme_tr.md new file mode 100644 index 000000000..7e1a046fa --- /dev/null +++ b/readme_tr.md @@ -0,0 +1,80 @@ +
+ + +### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE. + +[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno) +[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml) +[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse) +[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno) +[![Web Sitesi](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) +[![İndir](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) + +[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | **Türkçe** + +Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir. + +Bruno koleksiyonlarınızı doğrudan dosya sisteminizdeki bir klasörde saklar. API istekleri hakkındaki bilgileri kaydetmek için düz bir metin biçimlendirme dili olan Bru kullanıyoruz. + +API koleksiyonlarınız üzerinde işbirliği yapmak için git veya seçtiğiniz herhangi bir sürüm kontrolünü kullanabilirsiniz. + +Bruno yalnızca çevrimdışıdır. Bruno'ya bulut senkronizasyonu eklemek gibi bir planımız yok. Veri gizliliğinize değer veriyoruz ve cihazınızda kalması gerektiğine inanıyoruz. Uzun vadeli vizyonumuzu okuyun [burada](https://github.com/usebruno/bruno/discussions/269) + +![bruno](assets/images/landing-2.png)

+ +### Birden fazla platformda çalıştırın 🖥️ + +![bruno](assets/images/run-anywhere.png)

+ +### Git üzerinden işbirliği yapın 👩‍💻🧑‍💻 + +Veya seçtiğiniz herhangi bir sürüm kontrol sistemi + +![bruno](assets/images/version-control.png)

+ +### Önemli Bağlantılar 📌 + +- [Uzun Vadeli Vizyonumuz](https://github.com/usebruno/bruno/discussions/269) +- [Yol Haritası](https://github.com/usebruno/bruno/discussions/384) +- [Dokümantasyon](https://docs.usebruno.com) +- [Web sitesi](https://www.usebruno.com) +- [İndir](https://www.usebruno.com/downloads) + +### Vitrin 🎥 + +- [Görüşler](https://github.com/usebruno/bruno/discussions/343) +- [Bilgi Merkezi](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Destek ❤️ + +Woof! Projeyi beğendiyseniz, şu ⭐ düğmesine basın! + +### Referansları Paylaşın 📣 + +Bruno işinizde ve ekiplerinizde size yardımcı olduysa, lütfen [github tartışmamızdaki referanslarınızı](https://github.com/usebruno/bruno/discussions/343) paylaşmayı unutmayın + +### Katkıda Bulunun 👩‍💻🧑‍💻 + +Bruno'yu geliştirmek istemenize sevindim. Lütfen [katkıda bulunma kılavuzu](contributing.md)'na göz atın + +Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek için uygulanması gereken hataları ve özellik isteklerini bildirmekten çekinmeyin. + +### Katkıda Bulunanlar + + + +### İletişimde Kalın 🌐 + +[Twitter](https://twitter.com/use_bruno)
+[Website](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq) +[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Lisans 📄 + +[MIT](license.md) diff --git a/readme_ua.md b/readme_ua.md index 793e11a8e..77a280543 100644 --- a/readme_ua.md +++ b/readme_ua.md @@ -10,7 +10,7 @@ [![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) [![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) -[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) +[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman. From 89fbb42512bb5e0bdc64b4ffa152d2ba82b9a92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fukan=20=C3=9Crker?= Date: Fri, 20 Oct 2023 00:23:13 +0300 Subject: [PATCH 011/835] Turkish translation for contributing.md. --- contributing.md | 2 +- contributing_ru.md | 2 +- contributing_tr.md | 37 +++++++++++++++++++++++++++++++++++++ contributing_ua.md | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 contributing_tr.md diff --git a/contributing.md b/contributing.md index 966a6134b..6020d4c32 100644 --- a/contributing.md +++ b/contributing.md @@ -1,4 +1,4 @@ -**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) +**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) ## Lets make bruno better, together !! diff --git a/contributing_ru.md b/contributing_ru.md index 6636004ba..fd310a244 100644 --- a/contributing_ru.md +++ b/contributing_ru.md @@ -1,4 +1,4 @@ -[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** +[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** | [Türkçe](/contributing_tr.md) ## Давайте вместе сделаем Бруно лучше!!! diff --git a/contributing_tr.md b/contributing_tr.md new file mode 100644 index 000000000..f67e1a27b --- /dev/null +++ b/contributing_tr.md @@ -0,0 +1,37 @@ +[English](/readme.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | **Türkçe** + +## Bruno'yu birlikte daha iyi hale getirelim !! + +Bruno'yu geliştirmek istemenizden mutluluk duyuyorum. Aşağıda, bruno'yu bilgisayarınıza getirmeye başlamak için yönergeler bulunmaktadır. + +### Kullanılan Teknolojiler + +Bruno, NextJs ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz + +Kullandığımız kütüphaneler + +- CSS - Tailwind +- Kod Düzenleyiciler - Codemirror +- Durum Yönetimi - Redux +- Iconlar - Tabler Simgeleri +- Formlar - formik +- Şema Doğrulama - Yup +- İstek İstemcisi - axios +- Dosya Sistemi İzleyicisi - chokidar + +### Bağımlılıklar + +[Node v18.x veya en son LTS sürümüne](https://nodejs.org/en/) ve npm 8.x'e ihtiyacınız olacaktır. Projede npm çalışma alanlarını kullanıyoruz + +### Kodlamaya başlayalım + +Yerel geliştirme ortamının çalıştırılmasına ilişkin talimatlar için lütfen [development.md](docs/development.md) adresine başvurun. + +### Pull Request Oluşturma + +- Lütfen PR'ları küçük tutun ve tek bir şeye odaklanın +- Lütfen şube oluşturma formatını takip edin + - feature/[özellik adı]: Bu dal belirli bir özellik için değişiklikler içermelidir + - Örnek: feature/dark-mode + - bugfix/[hata adı]: Bu dal yalnızca belirli bir hata için hata düzeltmelerini içermelidir + - Örnek bugfix/bug-1 diff --git a/contributing_ua.md b/contributing_ua.md index 75760f565..a7fff3cfe 100644 --- a/contributing_ua.md +++ b/contributing_ua.md @@ -1,4 +1,4 @@ -[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) +[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) ## Давайте зробимо Bruno краще, разом !! From 16cf654e57f17f17955c1bce49937006fc5cab59 Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Fri, 20 Oct 2023 10:59:00 +0200 Subject: [PATCH 012/835] fix: overflowing code editor --- packages/bruno-app/src/components/CodeEditor/StyledWrapper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js index fe0f4cc19..3623d406d 100644 --- a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js +++ b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js @@ -5,6 +5,7 @@ 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')}; + line-break: anywhere; } .CodeMirror-overlayscroll-horizontal div, From 3ed7dd3893db302888d6f6eca790dfa3c5091b6f Mon Sep 17 00:00:00 2001 From: Ali Asgar Date: Fri, 20 Oct 2023 15:55:36 +0530 Subject: [PATCH 013/835] fixed: #686 --- .../src/providers/ReduxStore/slices/collections/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index ce129d801..20a68b80e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -393,8 +393,8 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { }); }; -export const sortCollections = () => (dispatch) => { - dispatch(_sortCollections()); +export const sortCollections = (payload) => (dispatch) => { + dispatch(_sortCollections(payload)); }; export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => { const state = getState(); From 55c52971d6c34fc6abd7839ac4f5fc036916a04a Mon Sep 17 00:00:00 2001 From: Martin Sefcik Date: Fri, 20 Oct 2023 14:34:04 +0200 Subject: [PATCH 014/835] bug(#699): fixed crashing app on clicking on request result in runner --- .../src/components/ResponsePane/QueryResult/index.js | 4 ++-- packages/bruno-app/src/components/ResponsePane/index.js | 1 + .../src/components/RunnerResults/ResponsePane/index.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index ba8696a14..18b9ec2c6 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -35,7 +35,7 @@ const formatResponse = (data, mode) => { return safeStringifyJSON(data); }; -const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers, error }) => { +const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => { const contentType = getContentType(headers); const mode = getCodeMirrorModeBasedOnContentType(contentType); const formattedData = formatResponse(data, mode); @@ -90,7 +90,7 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h { collection={collection} width={rightPaneWidth} data={response.data} + dataBuffer={response.dataBuffer} headers={response.headers} error={response.error} key={item.filename} diff --git a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js index 6526c7454..aeba867f4 100644 --- a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js +++ b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js @@ -34,6 +34,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { width={rightPaneWidth} disableRunEventListener={true} data={responseReceived.data} + dataBuffer={responseReceived.dataBuffer} headers={responseReceived.headers} key={item.filename} /> From c2bdddaa5934ba25a1cdf5d23293cdbe5194f325 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 09:51:40 -0400 Subject: [PATCH 015/835] chore: consolidate prerequest-related code --- .../bruno-electron/src/ipc/network/index.js | 235 +++++++----------- .../src/ipc/network/prepare-request.js | 9 + 2 files changed, 105 insertions(+), 139 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index f092e9ee9..5b0ba6582 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -189,6 +189,81 @@ const parseDataFromResponse = (response) => { }; const registerNetworkIpc = (mainWindow) => { + const onConsoleLog = (type, args) => { + console[type](...args); + + mainWindow.webContents.send('main:console-log', { + type, + args + }); + }; + + const runPreRequest = async ( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ) => { + // run pre-request vars + const preRequestVars = get(request, 'vars.req', []); + if (preRequestVars?.length) { + const varsRuntime = new VarsRuntime(); + const result = varsRuntime.runPreRequestVars( + preRequestVars, + request, + envVars, + collectionVariables, + collectionPath, + processEnvVars + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + } + + // run pre-request script + const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL); + if (requestScript?.length) { + const scriptRuntime = new ScriptRuntime(); + const result = await scriptRuntime.runRequestScript( + decomment(requestScript), + request, + envVars, + collectionVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig + ); + + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + + // interpolate variables inside request + interpolateVars(request, envVars, collectionVariables, processEnvVars); + + // stringify the request url encoded params + if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { + request.data = qs.stringify(request.data); + } + }; + // handler for sending http request ipcMain.handle('send-http-request', async (event, item, collection, environment, collectionVariables) => { const collectionUid = collection.uid; @@ -196,15 +271,6 @@ const registerNetworkIpc = (mainWindow) => { const cancelTokenUid = uuid(); const requestUid = uuid(); - const onConsoleLog = (type, args) => { - console[type](...args); - - mainWindow.webContents.send('main:console-log', { - type, - args - }); - }; - mainWindow.webContents.send('main:run-request-event', { type: 'request-queued', requestUid, @@ -222,75 +288,21 @@ const registerNetworkIpc = (mainWindow) => { const scriptingConfig = get(brunoConfig, 'scripts', {}); try { - // make axios work in node using form data - // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 - if (request.headers && request.headers['content-type'] === 'multipart/form-data') { - const form = new FormData(); - forOwn(request.data, (value, key) => { - form.append(key, value); - }); - extend(request.headers, form.getHeaders()); - request.data = form; - } - const cancelToken = axios.CancelToken.source(); request.cancelToken = cancelToken.token; saveCancelToken(cancelTokenUid, cancelToken); - // run pre-request vars - const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPreRequestVars( - preRequestVars, - request, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - } - - // run pre-request script - const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( - os.EOL + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (requestScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runRequestScript( - decomment(requestScript), - request, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - - interpolateVars(request, envVars, collectionVariables, processEnvVars); - - // stringify the request url encoded params - if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { - request.data = qs.stringify(request.data); - } // todo: // i have no clue why electron can't send the request object @@ -544,15 +556,6 @@ const registerNetworkIpc = (mainWindow) => { const scriptingConfig = get(brunoConfig, 'scripts', {}); const collectionRoot = get(collection, 'root', {}); - const onConsoleLog = (type, args) => { - console[type](...args); - - mainWindow.webContents.send('main:console-log', { - type, - args - }); - }; - if (!folder) { folder = collection; } @@ -602,67 +605,21 @@ const registerNetworkIpc = (mainWindow) => { const _request = item.draft ? item.draft.request : item.request; const request = prepareRequest(_request, collectionRoot); + const requestUid = uuid(); const processEnvVars = getProcessEnvVars(collectionUid); try { - // make axios work in node using form data - // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 - if (request.headers && request.headers['content-type'] === 'multipart/form-data') { - const form = new FormData(); - forOwn(request.data, (value, key) => { - form.append(key, value); - }); - extend(request.headers, form.getHeaders()); - request.data = form; - } - - // run pre-request vars - const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars && preRequestVars.length) { - const varsRuntime = new VarsRuntime(); - 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 - const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( - os.EOL + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (requestScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runRequestScript( - decomment(requestScript), - request, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - - // interpolate variables inside request - interpolateVars(request, envVars, collectionVariables, processEnvVars); // todo: // i have no clue why electron can't send the request object diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 6c2d7d4b3..6e3f123c1 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -137,6 +137,15 @@ const prepareRequest = (request, collectionRoot) => { each(enabledParams, (p) => (params[p.name] = p.value)); axiosRequest.headers['content-type'] = 'multipart/form-data'; axiosRequest.data = params; + + // make axios work in node using form data + // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 + const form = new FormData(); + forOwn(axiosRequest.data, (value, key) => { + form.append(key, value); + }); + extend(axiosRequest.headers, form.getHeaders()); + axiosRequest.data = form; } if (request.body.mode === 'graphql') { From 846ac66ca365b4d8a4b4ae4d7dcc9c3660872572 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 10:56:50 -0400 Subject: [PATCH 016/835] feat: run prerequest code before gql schema fetch --- .../bruno-electron/src/ipc/network/index.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 5b0ba6582..01d1658e4 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -514,8 +514,25 @@ const registerNetworkIpc = (mainWindow) => { }); } - const processEnvVars = getProcessEnvVars(collection.uid); - interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars); + const requestUid = uuid(); + const collectionPath = collection.pathname; + const collectionUid = collection.uid; + const collectionVariables = collection.collectionVariables; + const processEnvVars = getProcessEnvVars(collectionUid); + const brunoConfig = getBrunoConfig(collection.uid); + const scriptingConfig = get(brunoConfig, 'scripts', {}); + + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); const axiosInstance = await configureRequest( collection.uid, From 7e8073cbdbf4ccdb806b5dedd55334103bde7a31 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 11:49:12 -0400 Subject: [PATCH 017/835] chore: consolidate postresponse-related code --- .../bruno-electron/src/ipc/network/index.js | 182 +++++++++--------- 1 file changed, 86 insertions(+), 96 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 01d1658e4..4dca5ae01 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -264,6 +264,69 @@ const registerNetworkIpc = (mainWindow) => { } }; + const runPostResponse = async ( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ) => { + // run post-response vars + const postResponseVars = get(request, 'vars.res', []); + if (postResponseVars?.length) { + const varsRuntime = new VarsRuntime(); + const result = varsRuntime.runPostResponseVars( + postResponseVars, + request, + response, + envVars, + collectionVariables, + collectionPath, + processEnvVars + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + } + + // run post-response script + const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( + os.EOL + ); + if (responseScript?.length) { + const scriptRuntime = new ScriptRuntime(); + const result = await scriptRuntime.runResponseScript( + decomment(responseScript), + request, + response, + envVars, + collectionVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig + ); + + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + }; + // handler for sending http request ipcMain.handle('send-http-request', async (event, item, collection, environment, collectionVariables) => { const collectionUid = collection.uid; @@ -365,55 +428,18 @@ const registerNetworkIpc = (mainWindow) => { const { data, dataBuffer } = parseDataFromResponse(response); response.data = data; - // run post-response vars - const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPostResponseVars( - postResponseVars, - request, - response, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - } - - // run post-response script - const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( - os.EOL + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (responseScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runResponseScript( - decomment(responseScript), - request, - response, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } // run assertions const assertions = get(request, 'assertions'); @@ -712,54 +738,18 @@ const registerNetworkIpc = (mainWindow) => { } } - // run post-response vars - const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPostResponseVars( - postResponseVars, - request, - response, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - } - - // run response script - const responseScript = compact([ - get(collectionRoot, 'request.script.res'), - get(request, 'script.res') - ]).join(os.EOL); - if (responseScript && responseScript.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runResponseScript( - decomment(responseScript), - request, - response, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); // run assertions const assertions = get(item, 'request.assertions'); From a6ddf12ee117376b7f6019951750a5ff648caef3 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Fri, 20 Oct 2023 12:13:35 -0400 Subject: [PATCH 018/835] feat: run postrequest code after gql schema fetch --- packages/bruno-electron/src/ipc/network/index.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 4dca5ae01..52922516b 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -569,6 +569,19 @@ const registerNetworkIpc = (mainWindow) => { ); const response = await axiosInstance(preparedRequest); + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); + return { status: response.status, statusText: response.statusText, From fe59cd3223567adc588147846eaef874f32873a9 Mon Sep 17 00:00:00 2001 From: Sebastien Dionne Date: Fri, 20 Oct 2023 19:02:04 -0400 Subject: [PATCH 019/835] Added pre-request and tests scripts support from Postman Collection --- .../src/utils/importers/postman-collection.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js index 7fd36dad2..acb018d9d 100644 --- a/packages/bruno-app/src/utils/importers/postman-collection.js +++ b/packages/bruno-app/src/utils/importers/postman-collection.js @@ -62,6 +62,23 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { } }; + if (i.event) { + i.event.forEach((event) => { + if (event.listen === 'prerequest' && event.script && event.script.exec) { + if (!brunoRequestItem.request.script) { + brunoRequestItem.request.script = {}; + } + brunoRequestItem.request.script.req = event.script.exec[0]; + } + if (event.listen === 'test' && event.script && event.script.exec) { + if (!brunoRequestItem.request.tests) { + brunoRequestItem.request.tests = {}; + } + brunoRequestItem.request.tests = event.script.exec[0]; + } + }); + } + const bodyMode = get(i, 'request.body.mode'); if (bodyMode) { if (bodyMode === 'formdata') { From 36d6d115d4bcc699275d61f82ee8007bb59db7df Mon Sep 17 00:00:00 2001 From: Amr osama Date: Sat, 21 Oct 2023 20:51:13 +0300 Subject: [PATCH 020/835] Added properties to collections --- .../CollectionProperties/StyledWrapper.js | 40 +++++ .../Collection/CollectionProperties/index.js | 138 +++++++++++++++--- .../components/Sidebar/NewRequest/index.js | 7 +- .../src/providers/App/useIpcEvents.js | 6 + .../ReduxStore/slices/collections/actions.js | 16 ++ .../ReduxStore/slices/collections/index.js | 9 ++ packages/bruno-electron/src/ipc/collection.js | 22 +++ 7 files changed, 214 insertions(+), 24 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/StyledWrapper.js diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/StyledWrapper.js new file mode 100644 index 000000000..72d1d8a90 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/StyledWrapper.js @@ -0,0 +1,40 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + div.method-selector-container { + border: solid 1px ${(props) => props.theme.modal.input.border}; + border-right: none; + background-color: ${(props) => props.theme.modal.input.bg}; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + + .method-selector { + min-width: 80px; + } + } + + div.method-selector-container, + div.input-container { + background-color: ${(props) => props.theme.modal.input.bg}; + height: 2.3rem; + } + + div.input-container { + border: solid 1px ${(props) => props.theme.modal.input.border}; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + + input { + background-color: ${(props) => props.theme.modal.input.bg}; + outline: none; + box-shadow: none; + + &:focus { + outline: none !important; + box-shadow: none !important; + } + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js index 376a88db4..0a72eb490 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js @@ -1,5 +1,12 @@ import React from 'react'; import Modal from 'components/Modal'; +import { useState } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import StyledWrapper from './StyledWrapper'; +import { useDispatch } from 'react-redux'; +import toast from 'react-hot-toast'; +import { updateCollectionProperties } from 'providers/ReduxStore/slices/collections/actions'; function countRequests(items) { let count = 0; @@ -21,29 +28,116 @@ function countRequests(items) { } const CollectionProperties = ({ collection, onClose }) => { + const dispatch = useDispatch(); + const { + brunoConfig: { properties: defaultProperties = {} } + } = collection; + const formik = useFormik({ + enableReinitialize: true, + initialValues: { + defaultType: defaultProperties.defaultType || 'http-request', + defaultUrl: defaultProperties.defaultUrl || '' + }, + onSubmit: (newProperties) => { + dispatch(updateCollectionProperties(newProperties, collection.uid)); + toast.success('Collection properties updated'); + onClose(); + } + }); + + const onSubmit = () => formik.handleSubmit(); + return ( - - - - - - - - - - - - - - - - - - - - -
Name :{collection.name}
Location :{collection.pathname}
Environments :{collection.environments?.length || 0}
Requests :{countRequests(collection.items)}
-
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name :{collection.name}
Location :{collection.pathname}
Environments :{collection.environments?.length || 0}
Requests :{countRequests(collection.items)}
Default Request Type : +
+ + + + + +
+
Default Base URL : +
+
+ +
+
+
+
+ {formik.touched.defaultUrl && formik.errors.defaultUrl ? ( +
{formik.errors.defaultUrl}
+ ) : null} +
+
+
+
); }; diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 2e54b56ad..99a7fe3bb 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -15,12 +15,15 @@ import StyledWrapper from './StyledWrapper'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); const inputRef = useRef(); + const { + brunoConfig: { properties: collectionProperties = {} } + } = collection; const formik = useFormik({ enableReinitialize: true, initialValues: { requestName: '', - requestType: 'http-request', - requestUrl: '', + requestType: collectionProperties.defaultType || 'http-request', + requestUrl: collectionProperties.defaultUrl || '', requestMethod: 'GET' }, validationSchema: Yup.object({ diff --git a/packages/bruno-app/src/providers/App/useIpcEvents.js b/packages/bruno-app/src/providers/App/useIpcEvents.js index 8e87b1cf9..1c8787486 100644 --- a/packages/bruno-app/src/providers/App/useIpcEvents.js +++ b/packages/bruno-app/src/providers/App/useIpcEvents.js @@ -18,6 +18,7 @@ import { updatePreferences } from 'providers/ReduxStore/slices/app'; import toast from 'react-hot-toast'; import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions'; import { isElectron } from 'utils/common/platform'; +import { collectionPropertiesUpdatedEvent } from 'providers/ReduxStore/slices/collections/index'; const useIpcEvents = () => { const dispatch = useDispatch(); @@ -107,6 +108,10 @@ const useIpcEvents = () => { dispatch(collectionRenamedEvent(val)); }); + const removeCollectionPropertiesUpdatedListener = ipcRenderer.on('main:collection-properties-updated', (val) => { + dispatch(collectionPropertiesUpdatedEvent(val)); + }); + const removeRunFolderEventListener = ipcRenderer.on('main:run-folder-event', (val) => { dispatch(runFolderEvent(val)); }); @@ -138,6 +143,7 @@ const useIpcEvents = () => { removeDisplayErrorListener(); removeScriptEnvUpdateListener(); removeCollectionRenamedListener(); + removeCollectionPropertiesUpdatedListener(); removeRunFolderEventListener(); removeRunRequestEventListener(); removeProcessEnvUpdatesListener(); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index ce129d801..62839b9c9 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -47,6 +47,22 @@ import { resolveRequestFilename } from 'utils/common/platform'; import { parseQueryParams, splitOnFirst } from 'utils/url/index'; import { each } from 'lodash'; +export const updateCollectionProperties = (newProperties, collectionUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + return new Promise((resolve, reject) => { + if (!collection) { + return reject(new Error('Collection not found')); + } + + ipcRenderer + .invoke('renderer:update-collection-properties', newProperties, collection.pathname) + .then(resolve) + .catch(reject); + }); +}; + export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 4ad30d204..aef4d179e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1224,6 +1224,14 @@ export const collectionsSlice = createSlice({ collection.name = newName; } }, + collectionPropertiesUpdatedEvent: (state, action) => { + const { collectionPathname, newProperties } = action.payload; + const collection = findCollectionByPathname(state.collections, collectionPathname); + + if (collection) { + collection.properties = newProperties; + } + }, resetRunResults: (state, action) => { const { collectionUid } = action.payload; const collection = findCollectionByUid(state.collections, collectionUid); @@ -1427,6 +1435,7 @@ export const { collectionUnlinkDirectoryEvent, collectionAddEnvFileEvent, collectionRenamedEvent, + collectionPropertiesUpdatedEvent, resetRunResults, runRequestEvent, runFolderEvent, diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index ab92d50bd..c0a2e004b 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -94,6 +94,28 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); + // update collection properties + ipcMain.handle('renderer:update-collection-properties', async (event, newProperties, collectionPathname) => { + try { + const brunoJsonFilePath = path.join(collectionPathname, 'bruno.json'); + const content = fs.readFileSync(brunoJsonFilePath, 'utf8'); + const json = JSON.parse(content); + + json.properties = newProperties; + + const newContent = await stringifyJson(json); + await writeFile(brunoJsonFilePath, newContent); + + // fire an event in renderer to change the collection properties + mainWindow.webContents.send('main:collection-properties-updated', { + collectionPathname, + newProperties + }); + } catch (error) { + return Promise.reject(error); + } + }); + ipcMain.handle('renderer:save-collection-root', async (event, collectionPathname, collectionRoot) => { try { const collectionBruFilePath = path.join(collectionPathname, 'collection.bru'); From bbb904437fa74bf5b2cf6e9e78b6755705f0e0a6 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Sat, 21 Oct 2023 14:32:41 -0400 Subject: [PATCH 021/835] chore: re-sync proxy-util.js copies --- packages/bruno-cli/src/utils/proxy-util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-cli/src/utils/proxy-util.js b/packages/bruno-cli/src/utils/proxy-util.js index dd526d0d7..a9fdc1b9a 100644 --- a/packages/bruno-cli/src/utils/proxy-util.js +++ b/packages/bruno-cli/src/utils/proxy-util.js @@ -1,4 +1,5 @@ const parseUrl = require('url').parse; +const { isEmpty } = require('lodash'); const DEFAULT_PORTS = { ftp: 21, @@ -9,7 +10,7 @@ const DEFAULT_PORTS = { wss: 443 }; /** - * check for proxy bypass, Copied form 'proxy-from-env' + * check for proxy bypass, copied form 'proxy-from-env' */ const shouldUseProxy = (url, proxyBypass) => { if (proxyBypass === '*') { @@ -39,7 +40,6 @@ const shouldUseProxy = (url, proxyBypass) => { if (!dontProxyFor) { return true; // Skip zero-length hosts. } - const parsedProxy = dontProxyFor.match(/^(.+):(\d+)$/); let parsedProxyHostname = parsedProxy ? parsedProxy[1] : dontProxyFor; const parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0; From 9fd937bfbc1b4cd3a6240ad61a28e809ae678b21 Mon Sep 17 00:00:00 2001 From: Ali Asgar Date: Sun, 22 Oct 2023 02:49:10 +0530 Subject: [PATCH 022/835] fix: #692 --- packages/bruno-electron/src/store/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index fcf514f53..56db5d9a0 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -20,7 +20,7 @@ const defaultPreferences = { enabled: false, protocol: 'http', hostname: '', - port: '', + port: null, auth: { enabled: false, username: '', From cbfd7fa5f4c6dfa023c4f4fe7c0a929f77218c08 Mon Sep 17 00:00:00 2001 From: Amr osama Date: Sun, 22 Oct 2023 15:32:27 +0300 Subject: [PATCH 023/835] Added presets to collection settings --- .../Presets/StyledWrapper.js | 27 ++++ .../CollectionSettings/Presets/index.js | 93 ++++++++++++ .../components/CollectionSettings/index.js | 7 + .../Collection/CollectionProperties/index.js | 138 +++--------------- .../components/Sidebar/NewRequest/index.js | 6 +- .../src/providers/App/useIpcEvents.js | 10 +- .../ReduxStore/slices/collections/actions.js | 4 +- .../ReduxStore/slices/collections/index.js | 10 +- packages/bruno-electron/src/ipc/collection.js | 8 +- 9 files changed, 168 insertions(+), 135 deletions(-) create mode 100644 packages/bruno-app/src/components/CollectionSettings/Presets/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Presets/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/Presets/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Presets/StyledWrapper.js new file mode 100644 index 000000000..c8f1241c5 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Presets/StyledWrapper.js @@ -0,0 +1,27 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .settings-label { + width: 80px; + } + + .textbox { + border: 1px solid #ccc; + padding: 0.15rem 0.45rem; + box-shadow: none; + border-radius: 0px; + outline: none; + box-shadow: none; + transition: border-color ease-in-out 0.1s; + border-radius: 3px; + background-color: ${(props) => props.theme.modal.input.bg}; + border: 1px solid ${(props) => props.theme.modal.input.border}; + + &:focus { + border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important; + outline: none !important; + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Presets/index.js b/packages/bruno-app/src/components/CollectionSettings/Presets/index.js new file mode 100644 index 000000000..e6b4bcec1 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Presets/index.js @@ -0,0 +1,93 @@ +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import { useDispatch } from 'react-redux'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import { updateCollectionPresets } from 'providers/ReduxStore/slices/collections/actions'; + +const PresetsSettings = ({ collection }) => { + const dispatch = useDispatch(); + const { + brunoConfig: { presets: defaultPresets = {} } + } = collection; + + const formik = useFormik({ + enableReinitialize: true, + initialValues: { + defaultType: defaultPresets.defaultType || 'http-request', + defaultRequestUrl: defaultPresets.defaultRequestUrl || '' + }, + onSubmit: (newPresets) => { + dispatch(updateCollectionPresets(newPresets, collection.uid)); + toast.success('Collection presets updated'); + } + }); + + return ( + +

Collection Presets

+
+
+ +
+ + + + + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ ); +}; + +export default PresetsSettings; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index 62d774e2b..46c4b2e9d 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -13,6 +13,7 @@ import Auth from './Auth'; import Script from './Script'; import Test from './Tests'; import Docs from './Docs'; +import Presets from './Presets'; import StyledWrapper from './StyledWrapper'; const CollectionSettings = ({ collection }) => { @@ -75,6 +76,9 @@ const CollectionSettings = ({ collection }) => { case 'headers': { return ; } + case 'presets': { + return ; + } case 'auth': { return ; } @@ -114,6 +118,9 @@ const CollectionSettings = ({ collection }) => {
setTab('headers')}> Headers
+
setTab('presets')}> + Presets +
setTab('auth')}> Auth
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js index 0a72eb490..376a88db4 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionProperties/index.js @@ -1,12 +1,5 @@ import React from 'react'; import Modal from 'components/Modal'; -import { useState } from 'react'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; -import StyledWrapper from './StyledWrapper'; -import { useDispatch } from 'react-redux'; -import toast from 'react-hot-toast'; -import { updateCollectionProperties } from 'providers/ReduxStore/slices/collections/actions'; function countRequests(items) { let count = 0; @@ -28,116 +21,29 @@ function countRequests(items) { } const CollectionProperties = ({ collection, onClose }) => { - const dispatch = useDispatch(); - const { - brunoConfig: { properties: defaultProperties = {} } - } = collection; - const formik = useFormik({ - enableReinitialize: true, - initialValues: { - defaultType: defaultProperties.defaultType || 'http-request', - defaultUrl: defaultProperties.defaultUrl || '' - }, - onSubmit: (newProperties) => { - dispatch(updateCollectionProperties(newProperties, collection.uid)); - toast.success('Collection properties updated'); - onClose(); - } - }); - - const onSubmit = () => formik.handleSubmit(); - return ( - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Name :{collection.name}
Location :{collection.pathname}
Environments :{collection.environments?.length || 0}
Requests :{countRequests(collection.items)}
Default Request Type : -
- - - - - -
-
Default Base URL : -
-
- -
-
-
-
- {formik.touched.defaultUrl && formik.errors.defaultUrl ? ( -
{formik.errors.defaultUrl}
- ) : null} -
-
-
-
+ + + + + + + + + + + + + + + + + + + + +
Name :{collection.name}
Location :{collection.pathname}
Environments :{collection.environments?.length || 0}
Requests :{countRequests(collection.items)}
+
); }; diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 99a7fe3bb..0389e574b 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -16,14 +16,14 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); const inputRef = useRef(); const { - brunoConfig: { properties: collectionProperties = {} } + brunoConfig: { presets: collectionPresets = {} } } = collection; const formik = useFormik({ enableReinitialize: true, initialValues: { requestName: '', - requestType: collectionProperties.defaultType || 'http-request', - requestUrl: collectionProperties.defaultUrl || '', + requestType: collectionPresets.defaultType || 'http-request', + requestUrl: collectionPresets.defaultRequestUrl || '', requestMethod: 'GET' }, validationSchema: Yup.object({ diff --git a/packages/bruno-app/src/providers/App/useIpcEvents.js b/packages/bruno-app/src/providers/App/useIpcEvents.js index 1c8787486..6cef0bfa2 100644 --- a/packages/bruno-app/src/providers/App/useIpcEvents.js +++ b/packages/bruno-app/src/providers/App/useIpcEvents.js @@ -12,13 +12,13 @@ import { collectionRenamedEvent, runRequestEvent, runFolderEvent, - brunoConfigUpdateEvent + brunoConfigUpdateEvent, + collectionPresetsUpdatedEvent } from 'providers/ReduxStore/slices/collections'; import { updatePreferences } from 'providers/ReduxStore/slices/app'; import toast from 'react-hot-toast'; import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions'; import { isElectron } from 'utils/common/platform'; -import { collectionPropertiesUpdatedEvent } from 'providers/ReduxStore/slices/collections/index'; const useIpcEvents = () => { const dispatch = useDispatch(); @@ -108,8 +108,8 @@ const useIpcEvents = () => { dispatch(collectionRenamedEvent(val)); }); - const removeCollectionPropertiesUpdatedListener = ipcRenderer.on('main:collection-properties-updated', (val) => { - dispatch(collectionPropertiesUpdatedEvent(val)); + const removeCollectionPresetsUpdatedListener = ipcRenderer.on('main:collection-presets-updated', (val) => { + dispatch(collectionPresetsUpdatedEvent(val)); }); const removeRunFolderEventListener = ipcRenderer.on('main:run-folder-event', (val) => { @@ -143,7 +143,7 @@ const useIpcEvents = () => { removeDisplayErrorListener(); removeScriptEnvUpdateListener(); removeCollectionRenamedListener(); - removeCollectionPropertiesUpdatedListener(); + removeCollectionPresetsUpdatedListener(); removeRunFolderEventListener(); removeRunRequestEventListener(); removeProcessEnvUpdatesListener(); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 62839b9c9..0d6167d85 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -47,7 +47,7 @@ import { resolveRequestFilename } from 'utils/common/platform'; import { parseQueryParams, splitOnFirst } from 'utils/url/index'; import { each } from 'lodash'; -export const updateCollectionProperties = (newProperties, collectionUid) => (dispatch, getState) => { +export const updateCollectionPresets = (newPresets, collectionUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -57,7 +57,7 @@ export const updateCollectionProperties = (newProperties, collectionUid) => (dis } ipcRenderer - .invoke('renderer:update-collection-properties', newProperties, collection.pathname) + .invoke('renderer:update-collection-presets', newPresets, collection.pathname) .then(resolve) .catch(reject); }); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index aef4d179e..eb3b620ab 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1224,12 +1224,12 @@ export const collectionsSlice = createSlice({ collection.name = newName; } }, - collectionPropertiesUpdatedEvent: (state, action) => { - const { collectionPathname, newProperties } = action.payload; + collectionPresetsUpdatedEvent: (state, action) => { + const { collectionPathname, newPresets } = action.payload; const collection = findCollectionByPathname(state.collections, collectionPathname); - if (collection) { - collection.properties = newProperties; + if (collection.brunoConfig) { + collection.brunoConfig.presets = newPresets; } }, resetRunResults: (state, action) => { @@ -1435,7 +1435,7 @@ export const { collectionUnlinkDirectoryEvent, collectionAddEnvFileEvent, collectionRenamedEvent, - collectionPropertiesUpdatedEvent, + collectionPresetsUpdatedEvent, resetRunResults, runRequestEvent, runFolderEvent, diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index c0a2e004b..f5d5357ab 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -95,21 +95,21 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection }); // update collection properties - ipcMain.handle('renderer:update-collection-properties', async (event, newProperties, collectionPathname) => { + ipcMain.handle('renderer:update-collection-presets', async (event, newPresets, collectionPathname) => { try { const brunoJsonFilePath = path.join(collectionPathname, 'bruno.json'); const content = fs.readFileSync(brunoJsonFilePath, 'utf8'); const json = JSON.parse(content); - json.properties = newProperties; + json.presets = newPresets; const newContent = await stringifyJson(json); await writeFile(brunoJsonFilePath, newContent); // fire an event in renderer to change the collection properties - mainWindow.webContents.send('main:collection-properties-updated', { + mainWindow.webContents.send('main:collection-presets-updated', { collectionPathname, - newProperties + newPresets }); } catch (error) { return Promise.reject(error); From 1d58bdab59148d24734f0e73d8849a159528b3ba Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Sun, 22 Oct 2023 11:02:35 -0400 Subject: [PATCH 024/835] fix: respect rejectUnauthorized and ca opts when proxying https --- .../src/runner/run-single-request.js | 5 ++--- packages/bruno-cli/src/utils/proxy-util.js | 21 ++++++++++++++++++- .../bruno-electron/src/ipc/network/index.js | 5 ++--- .../bruno-electron/src/utils/proxy-util.js | 21 ++++++++++++++++++- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index bd440d62e..e047969f6 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -12,11 +12,10 @@ const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@use const { stripExtension } = require('../utils/filesystem'); const { getOptions } = require('../utils/bru'); const https = require('https'); -const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('../utils/axios-instance'); -const { shouldUseProxy } = require('../utils/proxy-util'); +const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util'); const runSingleRequest = async function ( filename, @@ -152,7 +151,7 @@ const runSingleRequest = async function ( request.httpsAgent = socksProxyAgent; request.httpAgent = socksProxyAgent; } else { - request.httpsAgent = new HttpsProxyAgent( + request.httpsAgent = new PatchedHttpsProxyAgent( proxyUri, Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined ); diff --git a/packages/bruno-cli/src/utils/proxy-util.js b/packages/bruno-cli/src/utils/proxy-util.js index a9fdc1b9a..729e03356 100644 --- a/packages/bruno-cli/src/utils/proxy-util.js +++ b/packages/bruno-cli/src/utils/proxy-util.js @@ -1,5 +1,6 @@ const parseUrl = require('url').parse; const { isEmpty } = require('lodash'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const DEFAULT_PORTS = { ftp: 21, @@ -61,6 +62,24 @@ const shouldUseProxy = (url, proxyBypass) => { }); }; +/** + * Patched version of HttpsProxyAgent to get around a bug that ignores + * options like ca and rejectUnauthorized when upgrading the socket to TLS: + * https://github.com/TooTallNate/proxy-agents/issues/194 + */ +class PatchedHttpsProxyAgent extends HttpsProxyAgent { + constructor(proxy, opts) { + super(proxy, opts); + this.constructorOpts = opts; + } + + async connect(req, opts) { + const combinedOpts = { ...this.constructorOpts, ...opts }; + return super.connect(req, combinedOpts); + } +} + module.exports = { - shouldUseProxy + shouldUseProxy, + PatchedHttpsProxyAgent }; diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index f092e9ee9..7e24887e6 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -19,12 +19,11 @@ const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); const { preferencesUtil } = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); -const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('./axios-instance'); const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper'); -const { shouldUseProxy } = require('../../utils/proxy-util'); +const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -149,7 +148,7 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria request.httpsAgent = socksProxyAgent; request.httpAgent = socksProxyAgent; } else { - request.httpsAgent = new HttpsProxyAgent( + request.httpsAgent = new PatchedHttpsProxyAgent( proxyUri, Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined ); diff --git a/packages/bruno-electron/src/utils/proxy-util.js b/packages/bruno-electron/src/utils/proxy-util.js index a9fdc1b9a..ef64d37ad 100644 --- a/packages/bruno-electron/src/utils/proxy-util.js +++ b/packages/bruno-electron/src/utils/proxy-util.js @@ -1,5 +1,6 @@ const parseUrl = require('url').parse; const { isEmpty } = require('lodash'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const DEFAULT_PORTS = { ftp: 21, @@ -61,6 +62,24 @@ const shouldUseProxy = (url, proxyBypass) => { }); }; +/** + * Patched version of HttpsProxyAgent to get around a bug that ignores options + * such as ca and rejectUnauthorized when upgrading the proxied socket to TLS: + * https://github.com/TooTallNate/proxy-agents/issues/194 + */ +class PatchedHttpsProxyAgent extends HttpsProxyAgent { + constructor(proxy, opts) { + super(proxy, opts); + this.constructorOpts = opts; + } + + async connect(req, opts) { + const combinedOpts = { ...this.constructorOpts, ...opts }; + return super.connect(req, combinedOpts); + } +} + module.exports = { - shouldUseProxy + shouldUseProxy, + PatchedHttpsProxyAgent }; From 7267ba64851ef2714e1b1c72b0a5d5de3218a431 Mon Sep 17 00:00:00 2001 From: Its-treason <39559178+Its-treason@users.noreply.github.com> Date: Sun, 22 Oct 2023 21:54:17 +0200 Subject: [PATCH 025/835] feat(#682): Enforce environment variable strictness --- .../EnvironmentVariables/StyledWrapper.js | 6 +- .../EnvironmentVariables/index.js | 210 +++++++++--------- .../EnvironmentVariables/reducer.js | 52 ----- 3 files changed, 113 insertions(+), 155 deletions(-) delete mode 100644 packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/reducer.js diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js index 64b11b25c..7eec1394c 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js @@ -13,10 +13,12 @@ const Wrapper = styled.div` padding: 4px 10px; &:nth-child(1), - &:nth-child(4), - &:nth-child(5) { + &:nth-child(4) { width: 70px; } + &:nth-child(5) { + width: 40px; + } &:nth-child(2) { width: 25%; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js index 748626768..6ac7f8e4c 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js @@ -1,68 +1,79 @@ -import React, { useReducer } from 'react'; +import React 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'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import { uuid } from 'utils/common'; const EnvironmentVariables = ({ environment, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] }); - const { variables, hasChanges } = state; - const saveChanges = () => { - dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid)) - .then(() => { - toast.success('Changes saved successfully'); - reducerDispatch({ - type: 'CHANGES_SAVED' - }); + const formik = useFormik({ + enableReinitialize: true, + initialValues: environment.variables || [], + validationSchema: Yup.array().of( + Yup.object({ + enabled: Yup.boolean(), + name: Yup.string() + .required('Name cannot be empty') + .matches(/^[^\d]\w*$/, 'Name contains invalid characters') + .trim(), + secret: Yup.boolean(), + type: Yup.string(), + uid: Yup.string(), + value: Yup.string().trim() }) - .catch(() => toast.error('An error occurred while saving the changes')); + ), + onSubmit: (values) => { + if (!formik.dirty) { + toast.error('Nothing to save'); + return; + } + + dispatch(saveEnvironment(cloneDeep(values), environment.uid, collection.uid)) + .then(() => { + toast.success('Changes saved successfully'); + formik.resetForm({ values }); + }) + .catch(() => toast.error('An error occurred while saving the changes')); + } + }); + + const ErrorMessage = ({ name }) => { + const meta = formik.getFieldMeta(name); + console.log(name, meta); + if (!meta.error) { + return null; + } + + return ( + + ); }; const addVariable = () => { - reducerDispatch({ - type: 'ADD_VAR' - }); + const newVariable = { + uid: uuid(), + name: '', + value: '', + type: 'text', + secret: false, + enabled: true + }; + formik.setFieldValue(formik.values.length, newVariable, false); }; - const handleVarChange = (e, _variable, type) => { - const variable = cloneDeep(_variable); - switch (type) { - case 'name': { - variable.name = e.target.value; - break; - } - case 'value': { - variable.value = e.target.value; - break; - } - case 'enabled': { - variable.enabled = e.target.checked; - break; - } - case 'secret': { - variable.secret = e.target.checked; - break; - } - } - reducerDispatch({ - type: 'UPDATE_VAR', - variable - }); - }; - - const handleRemoveVars = (variable) => { - reducerDispatch({ - type: 'DELETE_VAR', - variable - }); + const handleRemoveVar = (id) => { + formik.setValues(formik.values.filter((variable) => variable.uid !== id)); }; return ( @@ -78,55 +89,57 @@ const EnvironmentVariables = ({ environment, collection }) => { - {variables && variables.length - ? variables.map((variable, index) => { - return ( - - - handleVarChange(e, variable, 'enabled')} - /> - - - handleVarChange(e, variable, 'name')} - /> - - - handleVarChange({ target: { value: newValue } }, variable, 'value')} - collection={collection} - /> - - - handleVarChange(e, variable, 'secret')} - /> - - - - - - ); - }) - : null} + {formik.values.map((variable, index) => ( + + + + + + + + + + formik.setFieldValue(`${index}.value`, newValue, true)} + /> + + + + + + + + + ))} @@ -137,12 +150,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
-
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/reducer.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/reducer.js deleted file mode 100644 index a5aa3e0c1..000000000 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/reducer.js +++ /dev/null @@ -1,52 +0,0 @@ -import produce from 'immer'; -import find from 'lodash/find'; -import filter from 'lodash/filter'; -import { uuid } from 'utils/common'; - -const reducer = (state, action) => { - switch (action.type) { - case 'ADD_VAR': { - return produce(state, (draft) => { - draft.variables.push({ - uid: uuid(), - name: '', - value: '', - type: 'text', - secret: false, - enabled: true - }); - draft.hasChanges = true; - }); - } - - case 'UPDATE_VAR': { - return produce(state, (draft) => { - const variable = find(draft.variables, (v) => v.uid === action.variable.uid); - variable.name = action.variable.name; - variable.value = action.variable.value; - variable.enabled = action.variable.enabled; - variable.secret = action.variable.secret; - draft.hasChanges = true; - }); - } - - case 'DELETE_VAR': { - return produce(state, (draft) => { - draft.variables = filter(draft.variables, (v) => v.uid !== action.variable.uid); - draft.hasChanges = true; - }); - } - - case 'CHANGES_SAVED': { - return produce(state, (draft) => { - draft.hasChanges = false; - }); - } - - default: { - return state; - } - } -}; - -export default reducer; From 296e067222bd7c5add5ba5b35a20cd2e983b9a07 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 23 Oct 2023 01:37:30 +0530 Subject: [PATCH 026/835] chore(#684): renamed use to enabled in collection proxy config --- .../CollectionSettings/ProxySettings/index.js | 32 +++++++++---------- .../Sidebar/ImportCollection/index.js | 2 +- .../bruno-electron/src/ipc/network/index.js | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index fd3cc8986..2ed25b1e5 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -7,17 +7,17 @@ import toast from 'react-hot-toast'; const ProxySettings = ({ proxyConfig, onUpdate }) => { const proxySchema = Yup.object({ - use: Yup.string().oneOf(['global', 'true', 'false']), + enabled: Yup.string().oneOf(['global', 'true', 'false']), protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), hostname: Yup.string() - .when('use', { + .when('enabled', { is: true, then: (hostname) => hostname.required('Specify the hostname for your proxy.'), otherwise: (hostname) => hostname.nullable() }) .max(1024), port: Yup.number() - .when('use', { + .when('enabled', { is: true, then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'), otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null)) @@ -49,7 +49,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { const formik = useFormik({ initialValues: { - use: proxyConfig.use || 'global', + enabled: proxyConfig.enabled || 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -65,11 +65,11 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { proxySchema .validate(values, { abortEarly: true }) .then((validatedProxy) => { - // serialize 'use' to boolean - if (validatedProxy.use === 'true') { - validatedProxy.use = true; - } else if (validatedProxy.use === 'false') { - validatedProxy.use = false; + // serialize 'enabled' to boolean + if (validatedProxy.enabled === 'true') { + validatedProxy.enabled = true; + } else if (validatedProxy.enabled === 'false') { + validatedProxy.enabled = false; } onUpdate(validatedProxy); @@ -83,7 +83,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { useEffect(() => { formik.setValues({ - use: proxyConfig.use === true ? 'true' : proxyConfig.use === false ? 'false' : 'global', + enabled: proxyConfig.enabled === true ? 'true' : proxyConfig.enabled === false ? 'false' : 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -120,9 +120,9 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {