fix: Show active global environment in config modal (#5698)

* fix: Show active global environment in config modal
* add: delayShow prop in tooltip
This commit is contained in:
Pooja
2025-10-07 12:30:53 +05:30
committed by GitHub
parent 1d0ba135ff
commit 28907a203f
19 changed files with 283 additions and 20 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { IconPlus, IconDownload, IconSettings } from '@tabler/icons';
import ToolHint from 'components/ToolHint';
const EnvironmentListContent = ({
environments,
@@ -18,17 +19,29 @@ const EnvironmentListContent = ({
<div className="dropdown-item no-environment" onClick={() => onEnvironmentSelect(null)}>
<span>No Environment</span>
</div>
<div>
{environments.map((env) => (
<div
key={env.uid}
className={`dropdown-item ${env.uid === activeEnvironmentUid ? 'active' : ''}`}
onClick={() => onEnvironmentSelect(env)}
>
<span className="max-w-32 truncate no-wrap">{env.name}</span>
</div>
))}
</div>
<ToolHint
anchorSelect="[data-tooltip-content]"
place="right"
positionStrategy="fixed"
tooltipStyle={{
maxWidth: '200px',
wordWrap: 'break-word'
}}
delayShow={1000}
>
<div>
{environments.map((env) => (
<div
key={env.uid}
className={`dropdown-item ${env.uid === activeEnvironmentUid ? 'active' : ''}`}
onClick={() => onEnvironmentSelect(env)}
data-tooltip-content={env.name}
>
<span className="max-w-32 truncate no-wrap">{env.name}</span>
</div>
))}
</div>
</ToolHint>
<div className="dropdown-item configure-button">
<button onClick={onSettingsClick} id="configure-env">
<IconSettings size={16} strokeWidth={1.5} />

View File

@@ -14,6 +14,7 @@ import CreateEnvironment from '../EnvironmentSettings/CreateEnvironment';
import ImportEnvironment from '../EnvironmentSettings/ImportEnvironment';
import CreateGlobalEnvironment from 'components/GlobalEnvironments/EnvironmentSettings/CreateEnvironment';
import ImportGlobalEnvironment from 'components/GlobalEnvironments/EnvironmentSettings/ImportEnvironment';
import ToolHint from 'components/ToolHint';
import StyledWrapper from './StyledWrapper';
const EnvironmentSelector = ({ collection }) => {
@@ -123,7 +124,14 @@ const EnvironmentSelector = ({ collection }) => {
<>
<div className="flex items-center">
<IconDatabase size={14} strokeWidth={1.5} className="env-icon" />
<span className="env-text max-w-24 truncate no-wrap">{activeCollectionEnvironment.name}</span>
<ToolHint
text={activeCollectionEnvironment.name}
toolhintId={`collection-env-${activeCollectionEnvironment.uid}`}
place="bottom-start"
delayShow={1000}
>
<span className="env-text max-w-24 truncate overflow-hidden inline-block">{activeCollectionEnvironment.name}</span>
</ToolHint>
</div>
{activeGlobalEnvironment && <span className="env-separator">|</span>}
</>
@@ -131,7 +139,14 @@ const EnvironmentSelector = ({ collection }) => {
{activeGlobalEnvironment && (
<div className="flex items-center">
<IconWorld size={14} strokeWidth={1.5} className="env-icon" />
<span className="env-text max-w-24 truncate no-wrap">{activeGlobalEnvironment.name}</span>
<ToolHint
text={activeGlobalEnvironment.name}
toolhintId={`global-env-${activeGlobalEnvironment.uid}`}
place="bottom-start"
delayShow={1000}
>
<span className="env-text max-w-24 truncate overflow-hidden inline-block">{activeGlobalEnvironment.name}</span>
</ToolHint>
</div>
)}
</>
@@ -193,7 +208,12 @@ const EnvironmentSelector = ({ collection }) => {
{/* Modals - Rendered outside dropdown to avoid conflicts */}
{showGlobalSettings && (
<GlobalEnvironmentSettings globalEnvironments={globalEnvironments} collection={collection} onClose={handleCloseSettings} />
<GlobalEnvironmentSettings
globalEnvironments={globalEnvironments}
collection={collection}
activeGlobalEnvironmentUid={activeGlobalEnvironmentUid}
onClose={handleCloseSettings}
/>
)}
{showCollectionSettings && <EnvironmentSettings collection={collection} onClose={handleCloseSettings} />}

View File

@@ -39,7 +39,7 @@ const DefaultTab = ({ setTab }) => {
);
};
const EnvironmentSettings = ({ globalEnvironments, collection, onClose }) => {
const EnvironmentSettings = ({ globalEnvironments, collection, activeGlobalEnvironmentUid, onClose }) => {
const [isModified, setIsModified] = useState(false);
const environments = globalEnvironments;
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
@@ -64,6 +64,7 @@ const EnvironmentSettings = ({ globalEnvironments, collection, onClose }) => {
<Modal size="lg" title="Global Environments" handleCancel={onClose} hideFooter={true}>
<EnvironmentList
environments={globalEnvironments}
activeEnvironmentUid={activeGlobalEnvironmentUid}
selectedEnvironment={selectedEnvironment}
setSelectedEnvironment={setSelectedEnvironment}
isModified={isModified}

View File

@@ -6,12 +6,15 @@ import { useTheme } from 'providers/Theme';
const ToolHint = ({
text,
toolhintId,
anchorSelect,
children,
tooltipStyle = {},
place = 'top',
offset,
positionStrategy,
theme = null,
className = ''
className = '',
delayShow = 200
}) => {
const { theme: contextTheme } = useTheme();
const appliedTheme = theme || contextTheme;
@@ -28,17 +31,25 @@ const ToolHint = ({
color: toolhintTextColor
};
const toolhintProps_final = anchorSelect
? { anchorSelect }
: { anchorId: toolhintId };
return (
<>
<span id={toolhintId} className={className}>{children}</span>
{!anchorSelect && <span id={toolhintId} className={className}>{children}</span>}
{anchorSelect && children}
<StyledWrapper theme={appliedTheme}>
<ReactToolHint
anchorId={toolhintId}
content={text}
{...toolhintProps_final}
content={anchorSelect ? undefined : text}
className="toolhint"
offset={offset}
place={place}
positionStrategy={positionStrategy}
noArrow={true}
delayShow={delayShow}
style={combinedToolhintStyle}
/>
</StyledWrapper>

View File

@@ -2320,6 +2320,14 @@ export const collectionsSlice = createSlice({
collection.lastAction = null;
if (lastAction.payload === environment.name) {
collection.activeEnvironmentUid = environment.uid;
// Persist the selection to the UI state snapshot
const { ipcRenderer } = window;
if (ipcRenderer) {
ipcRenderer.invoke('renderer:update-ui-state-snapshot', {
type: 'COLLECTION_ENVIRONMENT',
data: { collectionPath: collection?.pathname, environmentName: environment.name }
});
}
}
}
}

View File

@@ -94,7 +94,7 @@ export const addGlobalEnvironment = ({ name, variables = [] }) => (dispatch, get
ipcRenderer
.invoke('renderer:create-global-environment', { name, uid, variables })
.then(() => dispatch(_addGlobalEnvironment({ name, uid, variables })))
.then(() => dispatch(_selectGlobalEnvironment({ environmentUid: uid })))
.then(() => dispatch(selectGlobalEnvironment({ environmentUid: uid })))
.then(resolve)
.catch(reject);
});

View File

@@ -0,0 +1,42 @@
import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Collection Environment Configuration Selection Tests', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('should open collection environment config with currently active environment selected', async ({
pageWithUserData: page
}) => {
// Open the collection from sidebar
await page.locator('#sidebar-collection-name').filter({ hasText: 'collection-env-config-selection' }).click();
// First, select an environment (prod - development)
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
// Select prod (development environment)
await page.locator('.dropdown-item').filter({ hasText: 'prod' }).click();
// Verify environment was selected
await expect(page.locator('.current-environment')).toContainText('prod');
// Now open the dropdown again and go to configuration
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
// Verify the config modal opens with the currently active environment selected
const collectionEnvModal = page.locator('.bruno-modal').filter({ hasText: 'Environments' });
await expect(collectionEnvModal).toBeVisible();
// Check that the active environment in the config matches prod
const activeEnvItem = collectionEnvModal.locator('.environment-item.active');
await expect(activeEnvItem).toContainText('prod');
// Close the collection environment config modal
await page.getByText('×').click();
});
});

View File

@@ -0,0 +1,5 @@
{
"version": "1",
"name": "collection-env-config-selection",
"type": "collection"
}

View File

@@ -0,0 +1,5 @@
vars {
baseUrl: /api/v2
name: staging
host: staging.example.com
}

View File

@@ -0,0 +1,5 @@
vars {
baseUrl: /api/v1
name: development
host: localhost:3000
}

View File

@@ -0,0 +1,17 @@
meta {
name: test-request
type: http
seq: 1
}
get {
url: {{baseUrl}}/echo
body: none
auth: none
}
tests {
test("should get 200 response", function() {
expect(res.getStatus()).to.equal(200);
});
}

View File

@@ -0,0 +1,10 @@
{
"collections": [
{
"path": "{{projectRoot}}/tests/environments/collection-env-config-selection/collection",
"securityConfig": {
"jsSandboxMode": "safe"
}
}
]
}

View File

@@ -0,0 +1,6 @@
{
"maximized": true,
"lastOpenedCollections": [
"{{projectRoot}}/tests/environments/collection-env-config-selection/collection"
]
}

View File

@@ -0,0 +1,5 @@
{
"version": "1",
"name": "global-env-config-selection",
"type": "collection"
}

View File

@@ -0,0 +1,17 @@
meta {
name: test-request
type: http
seq: 1
}
get {
url: {{host}}/api/echo
body: none
auth: none
}
tests {
test("should get 200 response", function() {
expect(res.getStatus()).to.equal(200);
});
}

View File

@@ -0,0 +1,35 @@
import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Global Environment Configuration Selection Tests', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('should open global environment config with currently active environment selected', async ({
pageWithUserData: page
}) => {
// Open the collection from sidebar
await page.locator('#sidebar-collection-name').filter({ hasText: 'global-env-config-selection' }).click();
// Get the currently active environment name
const currentEnvName = await page.locator('.current-environment').textContent() as string;
// Open global environment configuration
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
// Verify the config modal opens with the currently active environment selected
const globalEnvModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
await expect(globalEnvModal).toBeVisible();
// Check that the active environment in the config matches the current environment
const activeEnvItem = globalEnvModal.locator('.environment-item.active');
await expect(activeEnvItem).toContainText(currentEnvName);
// Close the global environment config modal and go to welcome page
await page.getByText('×').click();
});
});

View File

@@ -0,0 +1,10 @@
{
"collections": [
{
"path": "{{projectRoot}}/tests/environments/global-env-config-selection/collection",
"securityConfig": {
"jsSandboxMode": "safe"
}
}
]
}

View File

@@ -0,0 +1,47 @@
{
"environments": [
{
"uid": "FlaexlO7lcH7UtEpWsVyz",
"name": "Development Environment",
"variables": [
{
"uid": "dev-var-1",
"name": "env_type",
"value": "development",
"type": "text",
"secret": false,
"enabled": true
}
]
},
{
"uid": "MsHcnAIonZ3455OfvpTUT",
"name": "Production Environment",
"variables": [
{
"uid": "prod-var-1",
"name": "env_type",
"value": "production",
"type": "text",
"secret": false,
"enabled": true
}
]
},
{
"uid": "VdUAdMPcfapMCqjKAeUiI",
"name": "Staging Environment",
"variables": [
{
"uid": "staging-var-1",
"name": "env_type",
"value": "staging",
"type": "text",
"secret": false,
"enabled": true
}
]
}
],
"activeGlobalEnvironmentUid": "MsHcnAIonZ3455OfvpTUT"
}

View File

@@ -0,0 +1,6 @@
{
"maximized": true,
"lastOpenedCollections": [
"{{projectRoot}}/tests/environments/global-env-config-selection/collection"
]
}