From 3135edf4343b17ece15fbe42d240c4289ae35095 Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Tue, 14 Jul 2026 12:04:21 +0200 Subject: [PATCH 1/7] dbeaver/pro#9638 fix: update project filtering logic to use project IDs for active projects Changed all four spots from reference equality to id comparison, matching the rest of the codebase, see https://github.com/dbeaver/cloudbeaver/issues/4430 --- .../src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts | 4 ++-- .../navigationTreeProjectsExpandStateGetter.ts | 4 ++-- .../ProjectsRenderer/navigationTreeProjectFilter.ts | 2 +- .../navigationTreeProjectsExpandStateGetter.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts index 5177c901691..af3c9432f0e 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -63,7 +63,7 @@ export function navigationTreeProjectFilter( if (isRMProjectNode(node)) { const project = projectsNavNodeService.getProject(node.uri); - if (!project || !projectsService.activeProjects.includes(project)) { + if (!project || !projectsService.activeProjects.some(({ id }) => id === project.id)) { return false; } diff --git a/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts index 64aaad0c470..b36a51f4a8d 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ export function navigationTreeProjectsExpandStateGetter( let active = false; const project = projectsNavNodeService.getByNodeId(nodeId); if (project) { - active = projectsService.activeProjects.includes(project); + active = projectsService.activeProjects.some(({ id }) => id === project.id); } return { diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectFilter.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectFilter.ts index 93f49119c9d..676fbdce147 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectFilter.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectFilter.ts @@ -37,7 +37,7 @@ export function navigationTreeProjectFilter( if (isProjectNode(node)) { const project = projectsNavNodeService.getProject(node.uri); - if (!project || !projectsService.activeProjects.includes(project)) { + if (!project || !projectsService.activeProjects.some(({ id }) => id === project.id)) { return false; } diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts index 9c5efeb8c26..097b1ea6ef7 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ export function navigationTreeProjectsExpandStateGetter( let active = false; const project = projectsNavNodeService.getByNodeId(nodeId); if (project) { - active = projectsService.activeProjects.includes(project); + active = projectsService.activeProjects.some(({ id }) => id === project.id); } return { From eb2ba7871e5de3155144183e9be6d08053e2460d Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Wed, 15 Jul 2026 09:47:33 +0200 Subject: [PATCH 2/7] dbeaver/pro#9638 refactor(connectionUpdateHandler): move node to a new parent on change --- .../src/NavNodes/ConnectionNavNodeService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts index c058f45ac88..6933ec2f0d2 100644 --- a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts +++ b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts @@ -162,6 +162,10 @@ export class ConnectionNavNodeService { const folderId = node?.parentId; // current parent + if (folderId && folderId !== parentId && this.navTreeResource.has(parentId)) { + this.navTreeResource.moveToNode(connectionInfo.nodePath, parentId); + } + if (folderId && !outdatedTrees.includes(folderId)) { outdatedTrees.push(folderId); } From 28392396042ceedc272ec5455f7039d52f98f87f Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Wed, 15 Jul 2026 16:03:03 +0200 Subject: [PATCH 3/7] dbeaver/pro#9638 revert commit eb2ba7871e5de3155144183e9be6d08053e2460d --- .../src/NavNodes/ConnectionNavNodeService.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts index 6933ec2f0d2..c058f45ac88 100644 --- a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts +++ b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts @@ -162,10 +162,6 @@ export class ConnectionNavNodeService { const folderId = node?.parentId; // current parent - if (folderId && folderId !== parentId && this.navTreeResource.has(parentId)) { - this.navTreeResource.moveToNode(connectionInfo.nodePath, parentId); - } - if (folderId && !outdatedTrees.includes(folderId)) { outdatedTrees.push(folderId); } From bb7377252cc1fef1bd54344aceafd217d86ce5aa Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Thu, 16 Jul 2026 18:32:02 +0200 Subject: [PATCH 4/7] dbeaver/pro#9638 fix: update node parents on move in other session Server events don't carry the actual data, so besides outdating (which lets the handlers invalidate the previous state, e.g. the old location in the navigation tree) we explicitly load the connection to guarantee that the handlers receive the actual state --- .../src/ConnectionInfoResource.ts | 58 +++++++++++++++++-- .../src/NavNodes/ConnectionNavNodeService.ts | 2 +- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 814f18c8af5..5cb72428961 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -45,6 +45,7 @@ import { ConnectionStateEventHandler, type IWsDataSourceConnectEvent, type IWsDa import type { DatabaseConnection } from './DatabaseConnection.js'; import { DBDriverResource } from './DBDriverResource.js'; import { parseConnectionKey } from './parseConnectionKey.js'; +import { isNotNullDefined } from '@dbeaver/js-helpers'; export type Connection = DatabaseConnection; export type ConnectionInitConfig = InitConnectionMutationVariables; @@ -96,7 +97,7 @@ export class ConnectionInfoResource extends CachedMapResource>( ServerEventId.CbDatasourceUpdated, key => { - if (this.isConnected(key)) { + if (this.isConnected(key) && !this.isOutdated(key)) { const connection = this.get(key); this.dataSynchronizationService .requestSynchronization('connection', connection.map(connection => connection?.name).join('\n')) .then(state => { if (state) { - this.markOutdated(key); + this.updateFromEvent(key); } }); } else { - this.markOutdated(key); + this.updateFromEvent(key); } }, data => @@ -297,6 +298,55 @@ export class ConnectionInfoResource extends CachedMapResource connection?.connected ?? false); } + private async updateFromEvent(key: ResourceKeyList): Promise { + this.markOutdated(key); + + const treeNodeIds = this.findTreeNodeIds(key); + + try { + await this.load(key); + } catch (exception: any) { + this.logger.warn(`Failed to load the connection info on a server event: ${exception?.message}`); + return; + } + + const connections = this.get(key); + const staleParents = treeNodeIds + .filter(nodeId => !connections.some(connection => connection?.nodePath === nodeId)) + .map(nodeId => this.navNodeInfoResource.get(nodeId)?.parentId) + .filter(isNotNullDefined); + + if (staleParents.length > 0) { + this.navTreeResource.markOutdated(resourceKeyList(staleParents)); + } + } + + /** + * Finds the connection nodes in the loaded part of the navigation tree by the connection id, + * independently of the cached nodePath which may be stale or missing + */ + private findTreeNodeIds(keys: ResourceKeyList): string[] { + const nodeIds: string[] = []; + + for (const param of keys) { + const suffix = `/${param.connectionId}`; + + for (const nodeId of this.navNodeInfoResource.keys) { + if (!nodeId.endsWith(suffix) || !NodeManagerUtils.isDatabaseObject(nodeId)) { + continue; + } + + const node = this.navNodeInfoResource.get(nodeId); + + if (node?.projectId === param.projectId && node.parentId !== undefined && this.navTreeResource.get(node.parentId)?.includes(nodeId)) { + nodeIds.push(nodeId); + } + } + } + + return nodeIds; + } + getConnectionIdForNodeId(projectId: string, nodeId: string): IConnectionInfoParams | undefined { if (!NodeManagerUtils.isDatabaseObject(nodeId)) { return; diff --git a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts index c058f45ac88..de5c0e34301 100644 --- a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts +++ b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts @@ -149,7 +149,7 @@ export class ConnectionNavNodeService { connectionInfos = Array.isArray(connectionInfos) ? connectionInfos : [connectionInfos]; for (const connectionInfo of connectionInfos) { if (!connectionInfo?.nodePath) { - return; + continue; } const node = this.navNodeInfoResource.get(connectionInfo.nodePath); From b6fda5640e67897525c717471fb6a4500d0b965e Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Tue, 21 Jul 2026 11:04:40 +0200 Subject: [PATCH 5/7] dbeaver/pro#9638 fix: sync connection folder move from another session --- .../src/ConnectionInfoResource.ts | 61 +++++++------------ 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 5cb72428961..f99c4c8b27d 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -45,7 +45,6 @@ import { ConnectionStateEventHandler, type IWsDataSourceConnectEvent, type IWsDa import type { DatabaseConnection } from './DatabaseConnection.js'; import { DBDriverResource } from './DBDriverResource.js'; import { parseConnectionKey } from './parseConnectionKey.js'; -import { isNotNullDefined } from '@dbeaver/js-helpers'; export type Connection = DatabaseConnection; export type ConnectionInitConfig = InitConnectionMutationVariables; @@ -299,52 +298,36 @@ export class ConnectionInfoResource extends CachedMapResource): Promise { - this.markOutdated(key); - - const treeNodeIds = this.findTreeNodeIds(key); - - try { - await this.load(key); - } catch (exception: any) { - this.logger.warn(`Failed to load the connection info on a server event: ${exception?.message}`); - return; - } - - const connections = this.get(key); - const staleParents = treeNodeIds - .filter(nodeId => !connections.some(connection => connection?.nodePath === nodeId)) - .map(nodeId => this.navNodeInfoResource.get(nodeId)?.parentId) - .filter(isNotNullDefined); - - if (staleParents.length > 0) { - this.navTreeResource.markOutdated(resourceKeyList(staleParents)); - } - } - - /** - * Finds the connection nodes in the loaded part of the navigation tree by the connection id, - * independently of the cached nodePath which may be stale or missing - */ - private findTreeNodeIds(keys: ResourceKeyList): string[] { - const nodeIds: string[] = []; + for (const connectionKey of key) { + const currentConnection = this.get(connectionKey); + let newConnection: Connection | undefined; + + try { + newConnection = await this.refresh(connectionKey); + } catch (exception: any) { + this.logger.warn(`Failed to refresh the connection info on a server event: ${exception?.message}`); + continue; + } - for (const param of keys) { - const suffix = `/${param.connectionId}`; + if (currentConnection?.nodePath !== newConnection?.nodePath) { + if (currentConnection?.nodePath) { + const parent = this.navNodeInfoResource.getParent(currentConnection.nodePath); - for (const nodeId of this.navNodeInfoResource.keys) { - if (!nodeId.endsWith(suffix) || !NodeManagerUtils.isDatabaseObject(nodeId)) { - continue; + if (parent) { + this.navTreeResource.markOutdated(parent); + } } - const node = this.navNodeInfoResource.get(nodeId); + if (newConnection?.nodePath) { + await this.navNodeInfoResource.loadNodeParents(newConnection.nodePath); + const parent = this.navNodeInfoResource.getParent(newConnection.nodePath); - if (node?.projectId === param.projectId && node.parentId !== undefined && this.navTreeResource.get(node.parentId)?.includes(nodeId)) { - nodeIds.push(nodeId); + if (parent) { + this.navTreeResource.markOutdated(parent); + } } } } - - return nodeIds; } getConnectionIdForNodeId(projectId: string, nodeId: string): IConnectionInfoParams | undefined { From 529642d04c0f293896160a0d42d5d73ad4b39cfd Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Tue, 21 Jul 2026 16:02:56 +0200 Subject: [PATCH 6/7] dbeaver/pro#9638 fix: revert back connectionUpdateHandler --- .../plugin-connections/src/NavNodes/ConnectionNavNodeService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts index de5c0e34301..c058f45ac88 100644 --- a/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts +++ b/webapp/packages/plugin-connections/src/NavNodes/ConnectionNavNodeService.ts @@ -149,7 +149,7 @@ export class ConnectionNavNodeService { connectionInfos = Array.isArray(connectionInfos) ? connectionInfos : [connectionInfos]; for (const connectionInfo of connectionInfos) { if (!connectionInfo?.nodePath) { - continue; + return; } const node = this.navNodeInfoResource.get(connectionInfo.nodePath); From eb25ffe4524db255676e2dcb8ca8e05c4bc530dd Mon Sep 17 00:00:00 2001 From: Sychev Andrey Date: Tue, 21 Jul 2026 16:12:46 +0200 Subject: [PATCH 7/7] dbeaver/pro#9638 fix: remove try catch --- .../core-connections/src/ConnectionInfoResource.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index f99c4c8b27d..dea1246fd3e 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -300,14 +300,7 @@ export class ConnectionInfoResource extends CachedMapResource): Promise { for (const connectionKey of key) { const currentConnection = this.get(connectionKey); - let newConnection: Connection | undefined; - - try { - newConnection = await this.refresh(connectionKey); - } catch (exception: any) { - this.logger.warn(`Failed to refresh the connection info on a server event: ${exception?.message}`); - continue; - } + const newConnection = await this.refresh(connectionKey); if (currentConnection?.nodePath !== newConnection?.nodePath) { if (currentConnection?.nodePath) {