From b0f27d01b95673c9e70b2ca36bb34ed43a8badb8 Mon Sep 17 00:00:00 2001 From: shubh-bruno Date: Wed, 14 Jan 2026 12:24:07 +0530 Subject: [PATCH] fix: env vars loading and switching using react-virtuoso (#6790) * fix: environment variables fetching and switching * chore: formatting * fix: support active, inactive, secret vars popup * fix: variable highlight styles * chore: codemirror styles * fix: show variable highlighting when editor is inactive * fix: tab press for switching columns * fix: environment variables loading with react-virtuoso * fix: refactor EnvironmentVariables component for improved table rendering * fix: update react-virtuoso to version 4.18.1 --------- Co-authored-by: shubh-bruno Co-authored-by: Sid --- package-lock.json | 21 +- packages/bruno-app/package.json | 2 +- .../EnvironmentVariables/index.js | 226 ++++++++++-------- .../EnvironmentVariables/index.js | 217 +++++++++-------- 4 files changed, 249 insertions(+), 217 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9fb736dff..63d03fb7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24819,15 +24819,6 @@ "react-dom": ">=16.14.0" } }, - "node_modules/react-virtuoso": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.17.0.tgz", - "integrity": "sha512-od3pi2v13v31uzn5zPXC2u3ouISFCVhjFVFch2VvS2Cx7pWA2F1aJa3XhNTN2F07M3lhfnMnsmGeH+7wZICr7w==", - "peerDependencies": { - "react": ">=16 || >=17 || >= 18 || >= 19", - "react-dom": ">=16 || >=17 || >= 18 || >=19" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -30084,7 +30075,7 @@ "react-player": "^2.16.0", "react-redux": "^7.2.9", "react-tooltip": "^5.5.2", - "react-virtuoso": "^4.17.0", + "react-virtuoso": "^4.18.1", "sass": "^1.46.0", "semver": "^7.7.1", "shell-quote": "^1.8.3", @@ -31525,6 +31516,16 @@ "url": "https://opencollective.com/express" } }, + "packages/bruno-app/node_modules/react-virtuoso": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.1.tgz", + "integrity": "sha512-KF474cDwaSb9+SJ380xruBB4P+yGWcVkcu26HtMqYNMTYlYbrNy8vqMkE+GpAApPPufJqgOLMoWMFG/3pJMXUA==", + "license": "MIT", + "peerDependencies": { + "react": ">=16 || >=17 || >= 18 || >= 19", + "react-dom": ">=16 || >=17 || >= 18 || >=19" + } + }, "packages/bruno-app/node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index e8341a0f7..528812fa2 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -84,7 +84,7 @@ "react-player": "^2.16.0", "react-redux": "^7.2.9", "react-tooltip": "^5.5.2", - "react-virtuoso": "^4.17.0", + "react-virtuoso": "^4.18.1", "sass": "^1.46.0", "semver": "^7.7.1", "shell-quote": "^1.8.3", 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 0e10fe9b3..dee8e1e87 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,4 +1,5 @@ import React, { useCallback, useRef, useMemo, useEffect } from 'react'; +import { TableVirtuoso } from 'react-virtuoso'; import cloneDeep from 'lodash/cloneDeep'; import { get } from 'lodash'; import { IconTrash, IconAlertCircle, IconInfoCircle } from '@tabler/icons'; @@ -18,14 +19,28 @@ import { getGlobalEnvironmentVariables, flattenItems, isItemARequest } from 'uti import SensitiveFieldWarning from 'components/SensitiveFieldWarning'; import { sensitiveFields } from './constants'; +const TableRow = React.memo(({ children, item }) => {children}, (prevProps, nextProps) => { + const prevUid = prevProps?.item?.uid; + const nextUid = nextProps?.item?.uid; + return prevUid === nextUid && prevProps.children === nextProps.children; +}); + +const MIN_H = 35 * 2; // 2 rows worth of height + const EnvironmentVariables = ({ environment, setIsModified, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments); + const [tableHeight, setTableHeight] = React.useState(MIN_H); + const environmentsDraft = collection?.environmentsDraft; const hasDraftForThisEnv = environmentsDraft?.environmentUid === environment.uid; + const handleTotalHeightChanged = React.useCallback((h) => { + setTableHeight(h); + }, []); + // Track environment changes for draft restoration const prevEnvUidRef = React.useRef(null); const mountedRef = React.useRef(false); @@ -384,111 +399,114 @@ const EnvironmentVariables = ({ environment, setIsModified, collection }) => { return ( -
- - - - - - - - - - - - {formik.values.map((variable, index) => { - const isLastRow = index === formik.values.length - 1; - const isEmptyRow = !variable.name || variable.name.trim() === ''; - const isLastEmptyRow = isLastRow && isEmptyRow; + ( + + + + + + + + )} + fixedItemHeight={35} + computeItemKey={(index, variable) => variable.uid} + itemContent={(index, variable) => { + const isLastRow = index === formik.values.length - 1; + const isEmptyRow = !variable.name || variable.name.trim() === ''; + const isLastEmptyRow = isLastRow && isEmptyRow; - return ( - - - - - - - - ); - })} - -
NameValueSecret
NameValueSecret
- {!isLastEmptyRow && ( - - )} - -
- handleNameChange(index, e)} - onBlur={() => handleNameBlur(index)} - onKeyDown={(e) => handleNameKeyDown(index, e)} - /> - -
-
-
- formik.setFieldValue(`${index}.value`, newValue, true)} - onSave={handleSave} - /> -
- {typeof variable.value !== 'string' && ( - - - - - )} - {!variable.secret && hasSensitiveUsage(variable.name) && ( - - )} -
- {!isLastEmptyRow && ( - - )} - - {!isLastEmptyRow && ( - - )} -
-
+ return ( + <> + + {!isLastEmptyRow && ( + + )} + + +
+ handleNameChange(index, e)} + onBlur={() => handleNameBlur(index)} + onKeyDown={(e) => handleNameKeyDown(index, e)} + /> + +
+ + +
+ formik.setFieldValue(`${index}.value`, newValue, true)} + onSave={handleSave} + /> +
+ {typeof variable.value !== 'string' && ( + + + + + )} + {!variable.secret && hasSensitiveUsage(variable.name) && ( + + )} + + + {!isLastEmptyRow && ( + + )} + + + {!isLastEmptyRow && ( + + )} + + + ); + }} + />
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js index 95ea0bae4..6e96967dc 100644 --- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js +++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js @@ -1,4 +1,5 @@ import React, { useCallback, useRef } from 'react'; +import { TableVirtuoso } from 'react-virtuoso'; import cloneDeep from 'lodash/cloneDeep'; import { IconTrash, IconAlertCircle, IconInfoCircle } from '@tabler/icons'; import { useTheme } from 'providers/Theme'; @@ -19,6 +20,14 @@ import { Tooltip } from 'react-tooltip'; import { getGlobalEnvironmentVariables } from 'utils/collections'; import Button from 'ui/Button'; +const MIN_H = 35 * 2; + +const TableRow = React.memo(({ children, item }) => {children}, (prevProps, nextProps) => { + const prevUid = prevProps?.item?.uid; + const nextUid = nextProps?.item?.uid; + return prevUid === nextUid && prevProps.children === nextProps.children; +}); + const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); @@ -28,6 +37,12 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV const hasDraftForThisEnv = globalEnvironmentDraft?.environmentUid === environment.uid; + const [tableHeight, setTableHeight] = React.useState(MIN_H); + + const handleTotalHeightChanged = React.useCallback((h) => { + setTableHeight(h); + }, []); + // Track environment changes for draft restoration const prevEnvUidRef = React.useRef(null); const mountedRef = React.useRef(false); @@ -322,109 +337,107 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV return ( -
- - - - - - - - - - - - {formik.values.map((variable, index) => { - const isLastRow = index === formik.values.length - 1; - const isEmptyRow = !variable.name || variable.name.trim() === ''; - const isLastEmptyRow = isLastRow && isEmptyRow; + ( + + + + + + + + )} + itemContent={(index, variable) => { + const isLastRow = index === formik.values.length - 1; + const isEmptyRow = !variable.name || variable.name.trim() === ''; + const isLastEmptyRow = isLastRow && isEmptyRow; - return ( - - - - - - - - ); - })} - -
NameValueSecret
NameValueSecret
- {!isLastEmptyRow && ( - - )} - -
- handleNameChange(index, e)} - onBlur={() => handleNameBlur(index)} - onKeyDown={(e) => handleNameKeyDown(index, e)} - /> - -
-
-
- formik.setFieldValue(`${index}.value`, newValue, true)} - onSave={handleSave} - /> -
- {typeof variable.value !== 'string' && ( - - - - - )} -
- {!isLastEmptyRow && ( - - )} - - {!isLastEmptyRow && ( - - )} -
-
+ return ( + <> + + {!isLastEmptyRow && ( + + )} + + +
+ handleNameChange(index, e)} + onBlur={() => handleNameBlur(index)} + onKeyDown={(e) => handleNameKeyDown(index, e)} + /> + +
+ + +
+ formik.setFieldValue(`${index}.value`, newValue, true)} + onSave={handleSave} + /> +
+ {typeof variable.value !== 'string' && ( + + + + + )} + + + {!isLastEmptyRow && ( + + )} + + + {!isLastEmptyRow && ( + + )} + + + ); + }} + />