Skip to content

Commit 472965e

Browse files
committed
Fix the server context menu showing database options after the tree state is restored. #7612 (#10580)
1 parent 610cad0 commit 472965e

3 files changed

Lines changed: 163 additions & 7 deletions

File tree

‎web/pgadmin/browser/server_groups/servers/databases/static/js/database.js‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,16 @@ define('pgadmin.node.database', [
474474
pgBrowser.Events.trigger(
475475
'pgadmin:database:connected', _item, _data
476476
);
477-
/* Call enable/disable menu function after database is connected.
478-
To make sure all the menus for database is in the right state */
479-
pgBrowser.enable_disable_menus(_item);
480-
pgBrowser.Nodes['database'].callbacks.selected(_item, _data);
477+
/* Refresh the menus after the database is connected, so that
478+
they are in the right state. The menus always belong to the
479+
selected node, which may not be this database: it can be
480+
connected by expanding it, e.g. whilst the previous tree state
481+
is restored, so only treat it as selected if it really is. */
482+
if (_tree.selected() === _item) {
483+
pgBrowser.Nodes['database'].callbacks.selected(_item, _data);
484+
} else {
485+
pgBrowser.enable_disable_menus();
486+
}
481487

482488
if (!_connected) {
483489
setTimeout(function() {

‎web/pgadmin/browser/server_groups/servers/static/js/server.js‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,9 +698,11 @@ define('pgadmin.node.server', [
698698
// Load dashboard
699699
pgBrowser.Events.trigger('pgadmin-browser:tree:selected', _item, _data, node);
700700

701-
/* Call enable/disable menu function after database is connected.
702-
To make sure all the menus for database is in the right state */
703-
pgBrowser.enable_disable_menus(_item);
701+
/* Refresh the menus after the server is connected, so that they
702+
are in the right state. They always belong to the selected node,
703+
which may not be this server if it was connected by expanding
704+
it, so do not pass the server's item here. */
705+
pgBrowser.enable_disable_menus();
704706

705707
/* Below code is added to show the error message if
706708
connection is successful, but there is an error to
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/////////////////////////////////////////////////////////////
2+
//
3+
// pgAdmin 4 - PostgreSQL Tools
4+
//
5+
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
6+
// This software is released under the PostgreSQL Licence
7+
//
8+
//////////////////////////////////////////////////////////////
9+
10+
const mockPost = jest.fn();
11+
12+
jest.mock('../../../pgadmin/static/js/api_instance', () => ({
13+
__esModule: true,
14+
default: () => ({
15+
post: mockPost, get: jest.fn(() => Promise.resolve({data: {}})),
16+
delete: jest.fn(),
17+
}),
18+
parseApiError: (e) => e,
19+
}));
20+
jest.mock('../../../pgadmin/static/js/Dialogs/index', () => ({
21+
showServerPassword: jest.fn(),
22+
}));
23+
24+
const pgBrowser = {
25+
Nodes: {},
26+
Node: {
27+
extend: (props) => ({...props, generate_url: () => '/connect'}),
28+
callbacks: {selected: jest.fn()},
29+
},
30+
Collection: {extend: (props) => props},
31+
Events: {trigger: jest.fn(), on: jest.fn()},
32+
enable_disable_menus: jest.fn(),
33+
notifier: {success: jest.fn(), info: jest.fn(), warning: jest.fn()},
34+
stdW: {md: 650},
35+
};
36+
const pgAdmin = {Browser: pgBrowser};
37+
38+
// The node modules register themselves through an AMD style define(); load
39+
// them with a stub that hands each factory our fake browser.
40+
global.define = (_name, _deps, factory) => {
41+
factory((s) => s, () => '', pgAdmin, pgBrowser, {id: 1}, {});
42+
};
43+
require('../../../pgadmin/browser/server_groups/servers/databases/static/js/database');
44+
require('../../../pgadmin/browser/server_groups/servers/static/js/server');
45+
delete global.define;
46+
47+
// Let the mocked API calls resolve and the success handlers run.
48+
const flushPromises = async () => {
49+
for (let i = 0; i < 5; i++) {
50+
await Promise.resolve();
51+
}
52+
};
53+
54+
describe('database node connection', () => {
55+
let tree, serverItem, dbItem, dbData;
56+
57+
beforeEach(() => {
58+
jest.useFakeTimers();
59+
serverItem = {id: 'server'};
60+
dbItem = {id: 'db'};
61+
dbData = {_type: 'database', connected: false, allowConn: true, icon: 'x'};
62+
tree = {
63+
selected: jest.fn(),
64+
addIcon: jest.fn(),
65+
removeIcon: jest.fn(),
66+
select: jest.fn(),
67+
open: jest.fn(),
68+
};
69+
pgBrowser.tree = tree;
70+
pgBrowser.enable_disable_menus.mockClear();
71+
pgBrowser.Node.callbacks.selected.mockClear();
72+
mockPost.mockReset();
73+
mockPost.mockResolvedValue({data: {
74+
success: 1, info: 'Connected', data: {connected: true, icon: 'y'},
75+
}});
76+
});
77+
78+
afterEach(() => {
79+
jest.useRealTimers();
80+
});
81+
82+
const connectByExpanding = async () => {
83+
const node = pgBrowser.Nodes['database'];
84+
expect(node.callbacks.beforeopen.call(node, dbItem, dbData)).toBe(false);
85+
await flushPromises();
86+
};
87+
88+
it('keeps the menus on the selected server when a database is connected by expanding it', async () => {
89+
tree.selected.mockReturnValue(serverItem);
90+
91+
await connectByExpanding();
92+
93+
expect(dbData.connected).toBe(true);
94+
expect(pgBrowser.enable_disable_menus).toHaveBeenCalledWith();
95+
expect(pgBrowser.enable_disable_menus).not.toHaveBeenCalledWith(dbItem);
96+
expect(pgBrowser.Node.callbacks.selected).not.toHaveBeenCalled();
97+
});
98+
99+
it('updates the menus for the database when it is the selected node', async () => {
100+
tree.selected.mockReturnValue(dbItem);
101+
102+
await connectByExpanding();
103+
104+
expect(dbData.connected).toBe(true);
105+
expect(pgBrowser.Node.callbacks.selected).toHaveBeenCalledWith(dbItem, dbData);
106+
});
107+
});
108+
109+
describe('server node connection', () => {
110+
let tree, otherItem, serverItem, serverData;
111+
112+
beforeEach(() => {
113+
jest.useFakeTimers();
114+
otherItem = {id: 'other'};
115+
serverItem = {id: 'server'};
116+
serverData = {_type: 'server', _id: 1, connected: false, icon: 'x'};
117+
tree = {
118+
selected: jest.fn(() => otherItem),
119+
addIcon: jest.fn(),
120+
removeIcon: jest.fn(),
121+
setLeaf: jest.fn(),
122+
setInode: jest.fn(),
123+
select: jest.fn(),
124+
open: jest.fn(),
125+
};
126+
pgBrowser.tree = tree;
127+
pgBrowser.enable_disable_menus.mockClear();
128+
mockPost.mockReset();
129+
mockPost.mockResolvedValue({data: {
130+
success: 1, info: 'Connected',
131+
data: {connected: true, icon: 'y', version: 180000},
132+
}});
133+
});
134+
135+
afterEach(() => {
136+
jest.useRealTimers();
137+
});
138+
139+
it('keeps the menus on the selected node when a server is connected by expanding it', async () => {
140+
const node = pgBrowser.Nodes['server'];
141+
expect(node.callbacks.beforeopen.call(node, serverItem, serverData)).toBe(false);
142+
await flushPromises();
143+
144+
expect(serverData.connected).toBe(true);
145+
expect(pgBrowser.enable_disable_menus).toHaveBeenCalledWith();
146+
expect(pgBrowser.enable_disable_menus).not.toHaveBeenCalledWith(serverItem);
147+
});
148+
});

0 commit comments

Comments
 (0)