diff --git a/webapp/packages/core/src/app/NavigationTree/NavigationTreeContextMenuService.ts b/webapp/packages/core/src/app/NavigationTree/NavigationTreeContextMenuService.ts index a4ce669e59d..92cd6cd2108 100644 --- a/webapp/packages/core/src/app/NavigationTree/NavigationTreeContextMenuService.ts +++ b/webapp/packages/core/src/app/NavigationTree/NavigationTreeContextMenuService.ts @@ -63,7 +63,7 @@ export class NavigationTreeContextMenuService { title: 'app_navigationTree_refreshNode', onClick: (context) => { const node = context.data; - this.navNodeManagerService.refresh(node.id); + this.navNodeManagerService.refreshTree(node.id); }, } ); diff --git a/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts b/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts index 3d2806e8292..b96cc9c6bd4 100644 --- a/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts +++ b/webapp/packages/core/src/app/NavigationTree/NavigationTreeNode/useNavigationTree.ts @@ -32,6 +32,11 @@ export function useNavigationTree(nodeId: string, parentId: string) { const isLoaded = children.isLoaded; const isExpandable = isExpandableFilter(node) && (!isLoaded || children.children!.length > 0); + const isExpandedFiltered = isExpanded + && ( + !node.objectFeatures.includes(EObjectFeature.dataSource) + || node.objectFeatures.includes(EObjectFeature.dataSourceConnected) + ); const handleDoubleClick = useCallback( () => navNodeManagerService.navToNode(nodeId, parentId), @@ -40,15 +45,16 @@ export function useNavigationTree(nodeId: string, parentId: string) { const handleExpand = useCallback( async () => { - if (!isExpanded) { + if (!isExpandedFiltered) { const state = await navigationTreeService.loadNestedNodes(nodeId); if (!state) { switchExpand(false); + return; } } - switchExpand(!isExpanded); + switchExpand(!isExpandedFiltered); }, - [isExpanded, nodeId] + [isExpandedFiltered, nodeId] ); const handleSelect = useCallback( @@ -69,10 +75,12 @@ export function useNavigationTree(nodeId: string, parentId: string) { }, [isExpandable && hasChildren]); useEffect(() => { - if (isExpanded && !children.isLoaded && !children.isLoading && !!children.children && nodeLoaded) { - navigationTreeService.loadNestedNodes(nodeId); + if (isExpandedFiltered && !children.isLoaded && !children.isLoading && !!children.children && nodeLoaded) { + navigationTreeService + .loadNestedNodes(nodeId) + .then(state => !state && switchExpand(false)); } - }, [isExpanded, children.isLoaded, children.isLoading, children.children, nodeLoaded, nodeId]); + }, [isExpandedFiltered, children.isLoaded, children.isLoading, children.children, nodeLoaded, nodeId]); // Here we subscribe to selected nodes if current node selected (mobx) if (isSelected && !navigationTreeService.isNodeSelected(nodeId)) { @@ -90,7 +98,7 @@ export function useNavigationTree(nodeId: string, parentId: string) { node, nodeType, icon, - isExpanded, + isExpanded: isExpandedFiltered, isLoaded, isLoading: children.isLoading, isExpandable, diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts index baeb175ad12..8c0bc67f57a 100644 --- a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionDialogsService.ts @@ -61,8 +61,7 @@ export class ConnectionDialogsService { title: 'Disconnect', onClick: (context: IMenuContext) => { const node = context.data; - const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(node.id); - this.connectionsManagerService.closeConnectionAsync(connectionId); + this.connectionsManagerService.closeNavNodeConnectionAsync(node.id); }, }; diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts index 1f1fe2208dc..3a84a29198c 100644 --- a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts @@ -10,6 +10,7 @@ import { computed, observable } from 'mobx'; import { Subject } from 'rxjs'; import { injectable } from '@dbeaver/core/di'; +import { NotificationService } from '@dbeaver/core/eventsLog'; import { SessionService } from '@dbeaver/core/root'; import { ConnectionInfo, @@ -21,6 +22,7 @@ import { } from '@dbeaver/core/sdk'; import { NavNodeManagerService } from '../NodesManager/NavNodeManagerService'; +import { NodeManagerUtils } from '../NodesManager/NodeManagerUtils'; export type DBDriver = Pick< DriverInfo, @@ -68,7 +70,8 @@ export class ConnectionsManagerService { constructor( private graphQLService: GraphQLService, private navNodeManagerService: NavNodeManagerService, - private sessionService: SessionService + private sessionService: SessionService, + private notificationService: NotificationService ) { this.sessionService.onUpdate.subscribe(this.restoreConnections.bind(this)); } @@ -126,13 +129,36 @@ export class ConnectionsManagerService { } } + async closeNavNodeConnectionAsync(navNodeId: string): Promise { + const node = this.navNodeManagerService.getNode(navNodeId); + if (!node) { + return; + } + + try { + const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(navNodeId); + await this.graphQLService.gql.closeConnection({ id: connectionId }); + await this.afterConnectionClose(connectionId); + this.connectionsMap.delete(connectionId); + + if (node.objectFeatures.includes('dataSourceTemporary')) { + await this.navNodeManagerService.removeNode(navNodeId); + } else { + await this.navNodeManagerService.refreshNode(navNodeId); + } + await this.navNodeManagerService.removeTree(navNodeId); + } catch (exception) { + this.notificationService.logException(exception, `Can't close connection: ${navNodeId}`); + } + } + async loadObjectContainer(connectionId: string, catalogId?: string): Promise { const data = await this.connectionObjectContainers.load(connectionId, catalogId); return data.get(connectionId)!; } private async afterConnectionClose(id: string) { - await this.navNodeManagerService.remove(id); + await this.navNodeManagerService.removeTree(id); this.onCloseConnection.next(id); } diff --git a/webapp/packages/core/src/app/shared/NodesManager/EObjectFeature.ts b/webapp/packages/core/src/app/shared/NodesManager/EObjectFeature.ts index 70667e38905..80c056e1f49 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/EObjectFeature.ts +++ b/webapp/packages/core/src/app/shared/NodesManager/EObjectFeature.ts @@ -16,4 +16,6 @@ export enum EObjectFeature { 'schema' = 'schema', 'catalog' = 'catalog', 'dataSource' = 'dataSource', // connection + 'dataSourceTemporary' = 'dataSourceTemporary', + 'dataSourceConnected' = 'dataSourceConnected', } diff --git a/webapp/packages/core/src/app/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core/src/app/shared/NodesManager/NavNodeManagerService.ts index fe44cb2ccc6..fffaf9bfd91 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core/src/app/shared/NodesManager/NavNodeManagerService.ts @@ -32,7 +32,7 @@ export interface NavNodeKey { export interface NavNodeValue { node: NavNodeInfo; - parentId: string; + parentId?: string; } export interface INodeContainerInfo { @@ -157,7 +157,7 @@ export class NavNodeManagerService { }); } - async refresh(navNodeId: string) { + async refreshTree(navNodeId: string) { await this.graphQLService.gql.navRefreshNode({ nodePath: navNodeId, }); @@ -191,6 +191,14 @@ export class NavNodeManagerService { return this.getTree(navNodeId)!; } + async removeTree(path = ROOT_NODE_PATH) { + await this.navTree.refresh(true, path, true); + } + + async refreshNode(navNodeId: string) { + await this.navNode.refresh(true, { navNodeId: [navNodeId] }); + } + getNode(navNodeId: string): NavNode | undefined getNode(navNodeKey: NavNodeKey): NavNode | undefined getNode(navNodeKey: NavNodeKey[]): (NavNode | undefined)[] @@ -239,6 +247,13 @@ export class NavNodeManagerService { return this.getNode(nodes); } + async removeNode(navNodeId = ROOT_NODE_PATH) { + await this.navNode.refresh(true, { + navNodeId: [navNodeId], + remove: true, + }); + } + getParent(node: NavNode) { return this.navNode.data.get(node.parentId); } @@ -283,10 +298,6 @@ export class NavNodeManagerService { return scanParents(initial, nodeId); } - async remove(path = ROOT_NODE_PATH) { - await this.navTree.refresh(true, path, true); - } - navigationNavNodeContext = async ( contexts: IContextProvider, data: INodeNavigationData @@ -369,10 +380,12 @@ export class NavNodeManagerService { if (data.nodesValue) { for (const nodeValue of data.nodesValue) { const itemMetadata = metadata.get(nodeValue.node.id); + const parentId = navNode.get(nodeValue.node.id)?.parentId || nodeValue.parentId || ROOT_NODE_PATH; + navNode.set(nodeValue.node.id, { ...nodeValue.node, objectFeatures: nodeValue.node.object?.features || [], - parentId: nodeValue.parentId, + parentId, }); itemMetadata.loaded = true; } @@ -448,12 +461,12 @@ export class NavNodeManagerService { if (load) { itemMetadata.loading = true; - const { navNodeChildren } = await this.graphQLService.gql.navNodeChildren({ + const { navNodeChildren, navNodeInfo } = await this.graphQLService.gql.navNodeChildren({ parentPath: parentId, }); await this.navNode.refresh(true, { - nodesValue: navNodeChildren.map(node => ({ node, parentId })), + nodesValue: [{ node: navNodeInfo }, ...navNodeChildren.map(node => ({ node, parentId }))], }); navTree.set(parentId, navNodeChildren.map(node => node.id)); itemMetadata.loaded = true; @@ -480,7 +493,10 @@ export class NavNodeManagerService { } const nestedChildren = this.getNestedChildren(childrenToRemove); - await this.navNode.refresh(true, { navNodeId: nestedChildren, remove: true }); + await this.navNode.refresh(true, { + navNodeId: nestedChildren.filter(navNodeId => navNodeId !== parentId), + remove: true, + }); for (const navNodeId of nestedChildren) { navTree.delete(navNodeId); metadata.delete(navNodeId); diff --git a/webapp/packages/core/src/app/shared/NodesManager/queries/navNodeChildren.gql b/webapp/packages/core/src/app/shared/NodesManager/queries/navNodeChildren.gql index 3a0047049de..e05bb3bba83 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/queries/navNodeChildren.gql +++ b/webapp/packages/core/src/app/shared/NodesManager/queries/navNodeChildren.gql @@ -13,4 +13,19 @@ query navNodeChildren($parentPath: ID!) { features } } + + navNodeInfo(nodePath: $parentPath) { + id + name + hasChildren + nodeType + icon + folder + inline + navigable + features + object { + features + } + } } diff --git a/webapp/packages/core/src/sdk/sdk.ts b/webapp/packages/core/src/sdk/sdk.ts index 54e6bbfc0bd..36bef9d927e 100644 --- a/webapp/packages/core/src/sdk/sdk.ts +++ b/webapp/packages/core/src/sdk/sdk.ts @@ -784,7 +784,10 @@ export type NavNodeChildrenQueryVariables = { export type NavNodeChildrenQuery = { navNodeChildren: Array<( Pick & { object?: Maybe> } - )>; }; + )>; navNodeInfo: ( + Pick + & { object?: Maybe> } + ); }; export type NavNodeInfoQueryVariables = { nodePath: Scalars['ID']; @@ -1308,6 +1311,20 @@ export const NavNodeChildrenDocument = ` features } } + navNodeInfo(nodePath: $parentPath) { + id + name + hasChildren + nodeType + icon + folder + inline + navigable + features + object { + features + } + } } `; export const NavNodeInfoDocument = `