From 123fe7d5420a176a9213e05c0fe166049607cbb5 Mon Sep 17 00:00:00 2001 From: Pragadesh-45 <54320162+Pragadesh-45@users.noreply.github.com> Date: Thu, 25 Sep 2025 22:53:08 +0530 Subject: [PATCH 1/7] Merge pull request #5557 from Pragadesh-45/feat/default-location feat: default location for collections --- .../components/Preferences/General/index.js | 52 +++++++++++- .../Collection/CloneCollection/index.js | 5 +- .../Sidebar/Collections/Collection/index.js | 3 +- .../Sidebar/CreateCollection/index.js | 7 +- .../src/components/StatusBar/index.js | 2 +- .../bruno-app/src/components/Welcome/index.js | 2 +- .../src/providers/ReduxStore/slices/app.js | 3 + .../bruno-electron/src/store/preferences.js | 6 ++ .../collection/bruno.json | 5 ++ .../collection/collection.bru | 5 ++ .../collection/environments/Test.bru | 3 + .../collection/request.bru | 11 +++ .../default-collection-location.spec.js | 84 +++++++++++++++++++ .../init-user-data/collection-security.json | 10 +++ .../init-user-data/preferences.json | 9 ++ 15 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 tests/preferences/default-collection-location/collection/bruno.json create mode 100644 tests/preferences/default-collection-location/collection/collection.bru create mode 100644 tests/preferences/default-collection-location/collection/environments/Test.bru create mode 100644 tests/preferences/default-collection-location/collection/request.bru create mode 100644 tests/preferences/default-collection-location/default-collection-location.spec.js create mode 100644 tests/preferences/default-collection-location/init-user-data/collection-security.json create mode 100644 tests/preferences/default-collection-location/init-user-data/preferences.json diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 0f72e6b07..14eff0709 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -3,6 +3,7 @@ import get from 'lodash/get'; import { useFormik } from 'formik'; import { useSelector, useDispatch } from 'react-redux'; import { savePreferences } from 'providers/ReduxStore/slices/app'; +import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; import * as Yup from 'yup'; import toast from 'react-hot-toast'; @@ -35,7 +36,8 @@ const General = ({ close }) => { }) .test('isValidTimeout', 'Request Timeout must be equal or greater than 0', (value) => { return value === undefined || Number(value) >= 0; - }) + }), + defaultCollectionLocation: Yup.string().max(1024) }); const formik = useFormik({ @@ -50,7 +52,8 @@ const General = ({ close }) => { }, timeout: preferences.request.timeout, storeCookies: get(preferences, 'request.storeCookies', true), - sendCookies: get(preferences, 'request.sendCookies', true) + sendCookies: get(preferences, 'request.sendCookies', true), + defaultCollectionLocation: get(preferences, 'general.defaultCollectionLocation', '') }, validationSchema: preferencesSchema, onSubmit: async (values) => { @@ -79,6 +82,9 @@ const General = ({ close }) => { timeout: newPreferences.timeout, storeCookies: newPreferences.storeCookies, sendCookies: newPreferences.sendCookies + }, + general: { + defaultCollectionLocation: newPreferences.defaultCollectionLocation } })) .then(() => { @@ -99,6 +105,19 @@ const General = ({ close }) => { formik.setFieldValue('customCaCertificate.filePath', null); }; + const browseDefaultLocation = () => { + dispatch(browseDirectory()) + .then((dirPath) => { + if (typeof dirPath === 'string') { + formik.setFieldValue('defaultCollectionLocation', dirPath); + } + }) + .catch((error) => { + formik.setFieldValue('defaultCollectionLocation', ''); + console.error(error); + }); + }; + return (
@@ -231,6 +250,35 @@ const General = ({ close }) => { {formik.touched.timeout && formik.errors.timeout ? (
{formik.errors.timeout}
) : null} +
+ + +
+ + Browse + +
+
+ {formik.touched.defaultCollectionLocation && formik.errors.defaultCollectionLocation ? ( +
{formik.errors.defaultCollectionLocation}
+ ) : null}
{isLoading ? : null} -
+
} placement="bottom-start">
{
{ menuDropdownTippyRef.current.hide(); setShowCloneCollectionModalOpen(true); diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 3eb6707e0..642cf1ca9 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -1,5 +1,5 @@ import React, { useRef, useEffect } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions'; @@ -14,18 +14,21 @@ import Help from 'components/Help'; import { multiLineMsg } from "utils/common"; import { formatIpcError } from "utils/common/error"; import { toggleSidebarCollapse } from 'providers/ReduxStore/slices/app'; +import get from 'lodash/get'; const CreateCollection = ({ onClose }) => { const inputRef = useRef(); const dispatch = useDispatch(); const [isEditing, toggleEditing] = useState(false); + const preferences = useSelector((state) => state.app.preferences); + const defaultLocation = get(preferences, 'general.defaultCollectionLocation', ''); const formik = useFormik({ enableReinitialize: true, initialValues: { collectionName: '', collectionFolderName: '', - collectionLocation: '' + collectionLocation: defaultLocation }, validationSchema: Yup.object({ collectionName: Yup.string() diff --git a/packages/bruno-app/src/components/StatusBar/index.js b/packages/bruno-app/src/components/StatusBar/index.js index 25b984ffe..9ce6de680 100644 --- a/packages/bruno-app/src/components/StatusBar/index.js +++ b/packages/bruno-app/src/components/StatusBar/index.js @@ -82,7 +82,7 @@ const StatusBar = () => { diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index 59495aec8..fa2c6fd3e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -25,6 +25,9 @@ const initialState = { font: { codeFont: 'default' }, + general: { + defaultCollectionLocation: '' + }, beta: { grpc: false } diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 004d0e860..586d9bf95 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -47,6 +47,9 @@ const defaultPreferences = { }, onboarding: { hasLaunchedBefore: false + }, + general: { + defaultCollectionLocation: '' } }; @@ -89,6 +92,9 @@ const preferencesSchema = Yup.object().shape({ }), onboarding: Yup.object({ hasLaunchedBefore: Yup.boolean() + }), + general: Yup.object({ + defaultCollectionLocation: Yup.string().max(1024).nullable() }) }); diff --git a/tests/preferences/default-collection-location/collection/bruno.json b/tests/preferences/default-collection-location/collection/bruno.json new file mode 100644 index 000000000..03a26e1f7 --- /dev/null +++ b/tests/preferences/default-collection-location/collection/bruno.json @@ -0,0 +1,5 @@ +{ + "version": "1", + "name": "collection", + "type": "collection" +} diff --git a/tests/preferences/default-collection-location/collection/collection.bru b/tests/preferences/default-collection-location/collection/collection.bru new file mode 100644 index 000000000..408d3bd10 --- /dev/null +++ b/tests/preferences/default-collection-location/collection/collection.bru @@ -0,0 +1,5 @@ +meta { + name: collection + type: collection + version: 1.0.0 +} \ No newline at end of file diff --git a/tests/preferences/default-collection-location/collection/environments/Test.bru b/tests/preferences/default-collection-location/collection/environments/Test.bru new file mode 100644 index 000000000..e597f9c24 --- /dev/null +++ b/tests/preferences/default-collection-location/collection/environments/Test.bru @@ -0,0 +1,3 @@ +vars { + host: https://www.httpfaker.org +} diff --git a/tests/preferences/default-collection-location/collection/request.bru b/tests/preferences/default-collection-location/collection/request.bru new file mode 100644 index 000000000..baa0764c4 --- /dev/null +++ b/tests/preferences/default-collection-location/collection/request.bru @@ -0,0 +1,11 @@ +meta { + name: request + type: http + seq: 1 +} + +post { + url: {{host}}/api/echo + body: text + auth: none +} \ No newline at end of file diff --git a/tests/preferences/default-collection-location/default-collection-location.spec.js b/tests/preferences/default-collection-location/default-collection-location.spec.js new file mode 100644 index 000000000..8194d854c --- /dev/null +++ b/tests/preferences/default-collection-location/default-collection-location.spec.js @@ -0,0 +1,84 @@ +import { test, expect } from '../../../playwright'; + +test.describe('Default Collection Location Feature', () => { + test('Should hydrate the default location from preferences', async ({ pageWithUserData: page }) => { + // open preferences + await page.locator('.preferences-button').click(); + + // verify the default location is pre-filled + const defaultLocationInput = page.locator('.default-collection-location-input'); + await expect(defaultLocationInput).toHaveValue('/tmp/bruno-collections'); + + // close the preferences + await page.locator('[data-test-id="modal-close-button"]').click(); + + // wait for 2 seconds + await page.waitForTimeout(2000); + }); + + test('Should save empty default location', async ({ pageWithUserData: page }) => { + // open preferences + await page.locator('.preferences-button').click(); + + // clear the default location field + const defaultLocationInput = page.locator('.default-collection-location-input'); + await defaultLocationInput.clear(); + + // save preferences + await page.getByRole('button', { name: 'Save' }).click(); + + // verify success message + await expect(page.locator('text=Preferences saved successfully')).toBeVisible(); + + // wait for 2 seconds + await page.waitForTimeout(2000); + }); + + test('Should save a valid default location', async ({ pageWithUserData: page }) => { + // open preferences + await page.locator('.preferences-button').click(); + + // set a default location + const defaultLocationInput = page.locator('.default-collection-location-input'); + + // fill the default location input + await defaultLocationInput.fill('/tmp/bruno-collections'); + + // save preferences + await page.getByRole('button', { name: 'Save' }).click(); + + // verify success message + await expect(page.locator('text=Preferences saved successfully')).toBeVisible(); + + // wait for 2 seconds + await page.waitForTimeout(2000); + }); + + test('Should use default location in Create Collection modal', async ({ pageWithUserData: page }) => { + // test Create Collection modal + await page.locator('[data-testid="create-collection"]').click(); + + // verify the default location is pre-filled + const collectionLocationInput = page.getByLabel('Location'); + await expect(collectionLocationInput).toHaveValue('/tmp/bruno-collections'); + + // cancel the collection creation + await page.getByRole('button', { name: 'Cancel' }).click(); + + // wait for 2 seconds + await page.waitForTimeout(2000); + }); + + test('Should use default location in Clone Collection modal', async ({ pageWithUserData: page }) => { + // open the clone collection modal + await page.locator('[data-testid="collection-actions"]').click(); + await page.getByTestId('clone-collection').click(); + + // verify the default location is pre-filled + const cloneLocationInput = page.getByLabel('Location'); + await expect(cloneLocationInput).toHaveValue('/tmp/bruno-collections'); + + // wait for 2 seconds + await page.waitForTimeout(2000); + }); +}); diff --git a/tests/preferences/default-collection-location/init-user-data/collection-security.json b/tests/preferences/default-collection-location/init-user-data/collection-security.json new file mode 100644 index 000000000..e60afe806 --- /dev/null +++ b/tests/preferences/default-collection-location/init-user-data/collection-security.json @@ -0,0 +1,10 @@ +{ + "collections": [ + { + "path": "{{projectRoot}}/tests/preferences/default-collection-location/collection", + "securityConfig": { + "jsSandboxMode": "developer" + } + } + ] +} \ No newline at end of file diff --git a/tests/preferences/default-collection-location/init-user-data/preferences.json b/tests/preferences/default-collection-location/init-user-data/preferences.json new file mode 100644 index 000000000..fa1553037 --- /dev/null +++ b/tests/preferences/default-collection-location/init-user-data/preferences.json @@ -0,0 +1,9 @@ +{ + "maximized": true, + "lastOpenedCollections": ["{{projectRoot}}/tests/preferences/default-collection-location/collection"], + "preferences": { + "general": { + "defaultCollectionLocation": "/tmp/bruno-collections" + } + } +} From 191a997b057d481525af9dd8a00645ba6870def7 Mon Sep 17 00:00:00 2001 From: Rudra Patel <85089368+RudraPatel2003@users.noreply.github.com> Date: Mon, 29 Sep 2025 03:30:42 -0400 Subject: [PATCH 2/7] feat: Add button to copy environment variable from popover (#5416) * feat: Add copy button to environment variable hover * feat: Add success state * feat: Clean up code * feat: Add DOM test for popover and copy button functionality * feat: Add more robust tests * chore: reformat --------- Co-authored-by: Siddharth Gelera --- .../src/utils/codemirror/brunoVarInfo.js | 133 +++++++++++++++--- .../src/utils/codemirror/brunoVarInfo.spec.js | 119 +++++++++++++++- 2 files changed, 234 insertions(+), 18 deletions(-) diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js index 52614703f..a91fa706e 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js @@ -12,31 +12,130 @@ let CodeMirror; const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; const { get } = require('lodash'); -if (!SERVER_RENDERED) { - CodeMirror = require('codemirror'); +const COPY_ICON_SVG_TEXT = ` + + + + +`; - const renderVarInfo = (token, options, cm, pos) => { - // Extract variable name and value based on token - const { variableName, variableValue } = extractVariableInfo(token.string, options.variables); +const CHECKMARK_ICON_SVG_TEXT = ` + + + +`; - if (variableValue === undefined) { +const COPY_SUCCESS_COLOR = '#22c55e'; + +export const COPY_SUCCESS_TIMEOUT = 1000; + +const getCopyButton = variableValue => { + const copyButton = document.createElement('button'); + + copyButton.className = 'copy-button'; + copyButton.style.backgroundColor = 'transparent'; + copyButton.style.border = 'none'; + copyButton.style.color = 'inherit'; + copyButton.style.cursor = 'pointer'; + copyButton.style.padding = '2px'; + copyButton.style.opacity = '0.7'; + copyButton.style.transition = 'opacity 0.2s ease'; + copyButton.style.display = 'flex'; + copyButton.style.alignItems = 'center'; + copyButton.style.justifyContent = 'center'; + + copyButton.innerHTML = COPY_ICON_SVG_TEXT; + + let isCopied = false; + + copyButton.addEventListener('mouseenter', () => { + if (isCopied) { return; } - const into = document.createElement('div'); - const descriptionDiv = document.createElement('div'); - descriptionDiv.className = 'info-description'; + copyButton.style.opacity = '1'; + }); - if (options?.variables?.maskedEnvVariables?.includes(variableName)) { - descriptionDiv.appendChild(document.createTextNode('*****')); - } else { - descriptionDiv.appendChild(document.createTextNode(variableValue)); + copyButton.addEventListener('mouseleave', () => { + if (isCopied) { + return; } - into.appendChild(descriptionDiv); + copyButton.style.opacity = '0.7'; + }); - return into; - }; + copyButton.addEventListener('click', e => { + e.stopPropagation(); + + // Prevent clicking if showing success checkmark + if (isCopied) { + return; + } + + navigator.clipboard + .writeText(variableValue) + .then(() => { + isCopied = true; + copyButton.innerHTML = CHECKMARK_ICON_SVG_TEXT; + copyButton.style.opacity = '1'; + copyButton.style.color = COPY_SUCCESS_COLOR; + copyButton.style.cursor = 'default'; + copyButton.classList.add('copy-success'); + + setTimeout(() => { + isCopied = false; + copyButton.innerHTML = COPY_ICON_SVG_TEXT; + copyButton.style.opacity = '0.7'; + copyButton.style.color = 'inherit'; + copyButton.style.cursor = 'pointer'; + copyButton.classList.remove('copy-success'); + }, COPY_SUCCESS_TIMEOUT); + }) + .catch(err => { + console.error('Failed to copy to clipboard:', err.message); + }); + }); + + return copyButton; +}; + +export const renderVarInfo = (token, options, cm, pos) => { + // Extract variable name and value based on token + const { variableName, variableValue } = extractVariableInfo(token.string, options.variables); + + if (variableValue === undefined) { + return; + } + + const into = document.createElement('div'); + + const contentDiv = document.createElement('div'); + contentDiv.style.display = 'flex'; + contentDiv.style.alignItems = 'center'; + contentDiv.style.gap = '8px'; + contentDiv.className = 'info-content'; + + const descriptionDiv = document.createElement('div'); + descriptionDiv.className = 'info-description'; + descriptionDiv.style.flex = '1'; + + if (options?.variables?.maskedEnvVariables?.includes(variableName)) { + descriptionDiv.appendChild(document.createTextNode('*****')); + } else { + descriptionDiv.appendChild(document.createTextNode(variableValue)); + } + + const copyButton = getCopyButton(variableValue); + + contentDiv.appendChild(descriptionDiv); + contentDiv.appendChild(copyButton); + into.appendChild(contentDiv); + + return into; +}; + +if (!SERVER_RENDERED) { + CodeMirror = require('codemirror'); CodeMirror.defineOption('brunoVarInfo', false, function (cm, options, old) { if (old && old !== CodeMirror.Init) { @@ -214,4 +313,4 @@ export const extractVariableInfo = (str, variables) => { } return { variableName, variableValue }; -}; \ No newline at end of file +}; diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js index 5002097c2..0a2a161dc 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js @@ -1,5 +1,5 @@ import { interpolate } from '@usebruno/common'; -import { extractVariableInfo } from './brunoVarInfo'; +import { COPY_SUCCESS_TIMEOUT, extractVariableInfo, renderVarInfo } from './brunoVarInfo'; // Mock the dependencies jest.mock('@usebruno/common', () => ({ @@ -225,3 +225,120 @@ describe('extractVariableInfo', () => { }); }); }); + +describe('renderVarInfo', () => { + let clipboardText = ''; + + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers(); + + // setup mock clipboard + clipboardText = ''; + Object.defineProperty(navigator, 'clipboard', { + value: { + writeText: jest.fn(text => { + if (text === 'cause-clipboard-error') { + return Promise.reject(new Error('Clipboard error')); + } + + clipboardText = text; + + return Promise.resolve(); + }), + }, + configurable: true, + }); + + // mock console.error + console.error = jest.fn(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + function setupRender(variables) { + const result = renderVarInfo({ string: '{{apiKey}}' }, { variables }); + const contentDiv = result.querySelector('.info-content'); + const descriptionDiv = contentDiv.querySelector('.info-description'); + const copyButton = contentDiv.querySelector('.copy-button'); + + return { result, contentDiv, descriptionDiv, copyButton }; + } + + describe('popup functionality', () => { + it('should create a popup', () => { + const { result } = setupRender({ apiKey: 'test-value' }); + + expect(result).toBeDefined(); + }); + + it('should create a popup with the correct variable name and value', () => { + const { descriptionDiv } = setupRender({ apiKey: 'test-value' }); + + expect(descriptionDiv.textContent).toBe('test-value'); + }); + + it('should correctly mask the variable value in the popup', () => { + const { descriptionDiv } = setupRender({ + apiKey: 'test-value', + maskedEnvVariables: ['apiKey'], + }); + + expect(descriptionDiv.textContent).toBe('*****'); + }); + }); + + describe('copy button functionality', () => { + it('should create a copy button', () => { + const { copyButton } = setupRender({ apiKey: 'test-value' }); + + expect(copyButton).toBeDefined(); + }); + + it('should copy the variable value to the clipboard', async () => { + const { copyButton } = setupRender({ apiKey: 'test-value' }); + + await copyButton.click(); + + expect(clipboardText).toBe('test-value'); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('test-value'); + }); + + it('should copy the variable value of masked variables to the clipboard', async () => { + const { copyButton } = setupRender({ apiKey: 'test-value', maskedEnvVariables: ['apiKey'] }); + + await copyButton.click(); + + expect(clipboardText).toBe('test-value'); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('test-value'); + }); + + it('should show a success checkmark when the variable value is copied', async () => { + const { copyButton } = setupRender({ apiKey: 'test-value' }); + + expect(copyButton.classList.contains('copy-success')).toBe(false); + + await copyButton.click(); + + expect(copyButton.classList.contains('copy-success')).toBe(true); + + jest.advanceTimersByTime(COPY_SUCCESS_TIMEOUT); + + expect(copyButton.classList.contains('copy-success')).toBe(false); + }); + + it('should log to the console when the variable value is not copied', async () => { + const { copyButton } = setupRender({ apiKey: 'cause-clipboard-error' }); + + await copyButton.click(); + + // wait for .catch() microtask to run + await Promise.resolve(); + + expect(clipboardText).toBe(''); + expect(console.error).toHaveBeenCalledWith('Failed to copy to clipboard:', 'Clipboard error'); + }); + }); +}); From 41e0615f77b5e2025adaa48c9be7fb903edbc31c Mon Sep 17 00:00:00 2001 From: Mauricio Sanabria Date: Mon, 29 Sep 2025 01:37:10 -0600 Subject: [PATCH 3/7] Feature: Add collapse full collection feature (#4492) * Add collapse collection feature --------- Co-authored-by: Anoop M D --- .../Sidebar/Collections/Collection/index.js | 15 ++++++++++++++- .../ReduxStore/slices/collections/index.js | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index 3c19da4f6..93d0daca1 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -6,7 +6,7 @@ import filter from 'lodash/filter'; import { useDrop, useDrag } from 'react-dnd'; import { IconChevronRight, IconDots, IconLoader2 } from '@tabler/icons'; import Dropdown from 'components/Dropdown'; -import { toggleCollection } from 'providers/ReduxStore/slices/collections'; +import { toggleCollection, collapseFullCollection } from 'providers/ReduxStore/slices/collections'; import { mountCollection, moveCollectionAndPersist, handleCollectionItemDrop } from 'providers/ReduxStore/slices/collections/actions'; import { useDispatch, useSelector } from 'react-redux'; import { hideHomePage } from 'providers/ReduxStore/slices/app'; @@ -132,6 +132,10 @@ const Collection = ({ collection, searchText }) => { } }; + const handleCollapseFullCollection = () => { + dispatch(collapseFullCollection({ collectionUid: collection.uid })); + }; + const viewCollectionSettings = () => { dispatch( addTab({ @@ -311,6 +315,15 @@ const Collection = ({ collection, searchText }) => { > Share
+
{ + menuDropdownTippyRef.current.hide(); + handleCollapseFullCollection(); + }} + > + Collapse +
{ 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 909726a2a..236c4c69c 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -96,6 +96,13 @@ export const collectionsSlice = createSlice({ state.collections.push(collection); } }, + collapseFullCollection: (state, action) => { + const { collectionUid } = action.payload; + const collection = findCollectionByUid(state.collections, collectionUid); + if (collection) { + collapseAllItemsInCollection(collection); + } + }, updateCollectionMountStatus: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { @@ -2748,6 +2755,7 @@ export const { saveRequest, deleteRequestDraft, newEphemeralHttpRequest, + collapseFullCollection, toggleCollection, toggleCollectionItem, requestUrlChanged, From 2a00add966ff1a18e7c1e758ea26abbbf21504ef Mon Sep 17 00:00:00 2001 From: Siddharth Gelera Date: Mon, 29 Sep 2025 14:54:56 +0530 Subject: [PATCH 4/7] fix: update stylistic rules in ESLint configuration - Added 'comma-dangle' rule to disallow trailing commas. - Changed 'arrow-parens' rule to require parentheses for arrow functions. --- eslint.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 4eaaf583b..70c15f819 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -37,11 +37,11 @@ module.exports = runESMImports().then(() => defineConfig([ indent: 2, quotes: 'single', semi: true, - arrowParens: false, jsx: true, }).rules, + '@stylistic/comma-dangle': ['error', 'never'], '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }], - '@stylistic/arrow-parens': ['error', 'as-needed'], + '@stylistic/arrow-parens': ['error', 'always'], '@stylistic/curly-newline': ['error', { multiline: true, minElements: 2, From aacb1e0b8ef8b3ac44abfc6a4850c651763335f5 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Mon, 29 Sep 2025 19:37:56 +0530 Subject: [PATCH 5/7] Merge pull request #5635 from naman-bruno/feat/performance-monitor add: system monitor --- package-lock.json | 13 ++ .../src/components/Devtools/Console/index.js | 15 ++- .../Devtools/Performance/StyledWrapper.js | 120 ++++++++++++++++++ .../components/Devtools/Performance/index.js | 100 +++++++++++++++ .../src/providers/App/useIpcEvents.js | 6 + .../src/providers/ReduxStore/index.js | 4 +- .../ReduxStore/slices/performance.js | 28 ++++ packages/bruno-electron/package.json | 1 + .../bruno-electron/src/app/system-monitor.js | 71 +++++++++++ packages/bruno-electron/src/index.js | 8 ++ 10 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 packages/bruno-app/src/components/Devtools/Performance/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/Devtools/Performance/index.js create mode 100644 packages/bruno-app/src/providers/ReduxStore/slices/performance.js create mode 100644 packages/bruno-electron/src/app/system-monitor.js diff --git a/package-lock.json b/package-lock.json index 8205bb5b2..52fd51301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20235,6 +20235,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidusage": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-4.0.1.tgz", + "integrity": "sha512-yCH2dtLHfEBnzlHUJymR/Z1nN2ePG3m392Mv8TFlTP1B0xkpMQNHAnfkY0n2tAi6ceKO6YWhxYfZ96V4vVkh/g==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", @@ -30164,6 +30176,7 @@ "lodash": "^4.17.21", "mime-types": "^2.1.35", "nanoid": "3.3.8", + "pidusage": "^4.0.1", "qs": "^6.11.0", "socks-proxy-agent": "^8.0.2", "tough-cookie": "^6.0.0", diff --git a/packages/bruno-app/src/components/Devtools/Console/index.js b/packages/bruno-app/src/components/Devtools/Console/index.js index e87e38d37..5705eecb4 100644 --- a/packages/bruno-app/src/components/Devtools/Console/index.js +++ b/packages/bruno-app/src/components/Devtools/Console/index.js @@ -12,7 +12,8 @@ import { IconCode, IconChevronDown, IconTerminal2, - IconNetwork + IconNetwork, + IconDashboard, } from '@tabler/icons'; import { closeConsole, @@ -24,10 +25,12 @@ import { updateNetworkFilter, toggleAllNetworkFilters } from 'providers/ReduxStore/slices/logs'; + import NetworkTab from './NetworkTab'; import RequestDetailsPanel from './RequestDetailsPanel'; // import DebugTab from './DebugTab'; import ErrorDetailsPanel from './ErrorDetailsPanel'; +import Performance from '../Performance'; import StyledWrapper from './StyledWrapper'; const LogIcon = ({ type }) => { @@ -384,6 +387,8 @@ const Console = () => { ); case 'network': return ; + case 'performance': + return ; // case 'debug': // return ; default: @@ -484,6 +489,14 @@ const Console = () => { Network + + {/*