feat(core): refresh metadata

CB-43
This commit is contained in:
Wroud
2020-06-08 19:01:19 +03:00
parent 697fef743a
commit 6ab152db94
51 changed files with 1184 additions and 918 deletions
@@ -39,7 +39,7 @@ export class UsersManagerService {
async grantRole(userId: string, roleId: string, update?: boolean) {
await this.graphQLService.gql.grantUserRole({ userId, roleId });
if (update) {
await this.users.refresh(userId);
await this.users.refresh(true, userId);
}
}
@@ -50,7 +50,7 @@ export class UsersManagerService {
await this.graphQLService.gql.deleteUser({ userId });
if (update) {
await this.users.refresh(userId);
await this.users.refresh(true, userId);
}
}
@@ -11,6 +11,7 @@ import styled, { css } from 'reshadow';
import { usePermission, EPermission } from '@dbeaver/core/root';
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
import { useChildren } from '../shared/useChildren';
import { NavigationTreeNode } from './NavigationTreeNode/NavigationTreeNode';
@@ -46,7 +47,7 @@ export const NavigationTree = observer(function NavigationTree() {
return null;
}
if (!nodeChildren || nodeChildren.children.length === 0) {
if (!nodeChildren.children || nodeChildren.children.length === 0) {
return styled(navigationTreeStyles)(
<center as="div">
<tree as="div">
@@ -62,7 +63,7 @@ export const NavigationTree = observer(function NavigationTree() {
return styled(navigationTreeStyles)(
<tree as="div">
{nodeChildren.children.map(id => (
<NavigationTreeNode id={id} key={id} />
<NavigationTreeNode key={id} id={id} parentId={ROOT_NODE_PATH}/>
))}
</tree>
);
@@ -7,63 +7,65 @@
*/
import { injectable } from '@dbeaver/core/di';
import {
ContextMenuService, IContextMenuItem, IMenuContext, IMenuPanel,
} from '@dbeaver/core/dialogs';
import { ContextMenuService, IMenuPanel } from '@dbeaver/core/dialogs';
import { NodesManagerService } from '../shared/NodesManager/NodesManagerService';
import { NodeWithParent } from '../shared/NodesManager/NodeWithParent';
import { NavNode } from '../shared/NodesManager/EntityTypes';
import { NavNodeManagerService } from '../shared/NodesManager/NavNodeManagerService';
@injectable()
export class NavigationTreeContextMenuService {
static nodeContextType = 'NodeWithParent';
private static menuToken = 'navTreeMenu';
constructor(private contextMenuService: ContextMenuService,
private nodesManagerService: NodesManagerService) {
}
constructor(
private contextMenuService: ContextMenuService,
private navNodeManagerService: NavNodeManagerService
) { }
getMenuToken() {
return NavigationTreeContextMenuService.menuToken;
}
constructMenuWithContext(node: NodeWithParent): IMenuPanel {
const context: IMenuContext<NodeWithParent> = {
constructMenuWithContext(node: NavNode): IMenuPanel {
return this.contextMenuService.createContextMenu<NavNode>({
menuId: this.getMenuToken(),
contextId: node.id,
contextType: NavigationTreeContextMenuService.nodeContextType,
data: node,
};
return this.contextMenuService.createContextMenu(context);
});
}
registerMenuItems() {
const openNodeTab: IContextMenuItem<NodeWithParent> = {
id: 'openNodeTab',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
},
order: 1,
title: 'app_navigationTree_openNodeTab',
onClick: (context: IMenuContext<NodeWithParent>) => {
const node = context.data;
this.nodesManagerService.navToNode(node.id);
},
};
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), openNodeTab);
this.contextMenuService.addMenuItem<NavNode>(
this.contextMenuService.getRootMenuToken(),
{
id: 'openNodeTab',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
},
order: 1,
title: 'app_navigationTree_openNodeTab',
onClick: (context) => {
const node = context.data;
this.navNodeManagerService.navToNode(node.id, node.parentId);
},
}
);
const refreshNode: IContextMenuItem<NodeWithParent> = {
id: 'refreshNode',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
},
order: Number.MAX_SAFE_INTEGER,
title: 'app_navigationTree_refreshNode',
onClick: (context: IMenuContext<NodeWithParent>) => {
const node = context.data;
this.nodesManagerService.refreshNode(node.id);
},
};
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), refreshNode);
this.contextMenuService.addMenuItem<NavNode>(
this.contextMenuService.getRootMenuToken(),
{
id: 'refreshNode',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
},
order: Number.MAX_SAFE_INTEGER,
title: 'app_navigationTree_refreshNode',
onClick: (context) => {
const node = context.data;
this.navNodeManagerService.refresh(node.id);
},
}
);
}
}
@@ -21,13 +21,13 @@ type NodeProps = PropsWithChildren<{
title?: string;
icon?: string;
isExpanded: boolean;
isLoaded?: boolean;
isLoading: boolean;
isSelected: boolean;
portal?: JSX.Element;
isExpandable?: boolean;
onExpand?: (id: string) => void;
onDoubleClick?: (id: string) => void;
onClick?: (id: string, isMultiple?: boolean) => void;
onExpand?: () => void;
onDoubleClick?: () => void;
onClick?: (isMultiple?: boolean) => void;
}>
const KEYCODE = {
@@ -36,23 +36,19 @@ const KEYCODE = {
DOWN: 40,
};
export function NavigationNode(
props: NodeProps
) {
const {
id,
title,
icon,
children,
isLoaded,
isExpanded,
isSelected,
portal,
isExpandable,
onExpand,
onDoubleClick,
onClick,
} = props;
export function NavigationNode({
title,
icon,
children,
isLoading,
isExpanded,
isSelected,
portal,
isExpandable,
onExpand,
onDoubleClick,
onClick,
}: NodeProps) {
const styles = useStyles(navigationNodeStyles);
@@ -60,28 +56,19 @@ export function NavigationNode(
(e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
if (onExpand) {
onExpand(id);
onExpand();
}
},
[id, onExpand]
);
const handleDoubleClick = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
if (onDoubleClick) {
onDoubleClick(id);
}
},
[id, onDoubleClick]
[onExpand]
);
const handleClick = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
if (onClick) {
onClick(id, e.ctrlKey);
onClick(e.ctrlKey);
}
},
[id, onClick]
[onClick]
);
const handleEnter = useCallback(
@@ -89,29 +76,29 @@ export function NavigationNode(
switch (event.keyCode) {
case KEYCODE.ENTER:
if (onClick) {
onClick(id, event.ctrlKey);
onClick(event.ctrlKey);
}
break;
}
return true;
},
[id, onClick]
[onClick]
);
return styled(styles)(
<>
<node as="div" {...use({ isExpanded: isExpanded && isLoaded })}>
<node as="div" {...use({ isExpanded: isExpanded && isExpandable })}>
<control
tabIndex={0}
aria-selected={isSelected}
onClick={handleClick}
onKeyDown={handleEnter}
onDoubleClick={handleDoubleClick}
onDoubleClick={onDoubleClick}
as="div"
>
<arrow as="div" hidden={!isExpandable} onClick={handleExpand}>
{!isLoaded && isExpanded && <Loader small />}
{(isLoaded || !isExpanded) && <Icon name="arrow" viewBox="0 0 16 16" />}
{isLoading && <Loader small />}
{!isLoading && <Icon name="arrow" viewBox="0 0 16 16" />}
</arrow>
<icon as="div"><StaticImage icon={icon} /></icon>
<name as="div">{title}</name>
@@ -13,24 +13,27 @@ import { useChildren } from '../../shared/useChildren';
type NavigationNodeChildrenProps = PropsWithChildren<{
parentId: string;
component: React.ElementType<{id: string}>;
component: React.ElementType<{
id: string;
parentId: string;
}>;
}>
export const NavigationNodeChildren = observer(function NavigationNodeChildren({
parentId,
component,
}: NavigationNodeChildrenProps) {
const nodeChildren = useChildren(parentId);
const { children } = useChildren(parentId);
if (!nodeChildren?.isLoaded) {
if (!children) {
return null;
}
const NavigationNodeChild = component;
return (
<>
{nodeChildren.children!.map(id => (
<NavigationNodeChild id={id} key={id} />
{children.map(id => (
<NavigationNodeChild id={id} parentId={parentId} key={id} />
))}
</>
);
@@ -15,12 +15,14 @@ import { useNavigationTree } from './useNavigationTree';
type NavigationTreeNodeProps = {
id: string;
parentId: string;
}
export const NavigationTreeNode = observer(function NavigationTreeNodeFn({
id,
parentId,
}: NavigationTreeNodeProps) {
const node = useNavigationTree(id);
const node = useNavigationTree(id, parentId);
if (!node) {
return null;
@@ -33,12 +35,12 @@ export const NavigationTreeNode = observer(function NavigationTreeNodeFn({
icon={node.icon}
isSelected={node.isSelected}
isExpanded={node.isExpanded}
isLoaded={node.isLoaded}
isExpandable={node.hasChildren && node.isExpandable}
isLoading={node.isLoading}
isExpandable={node.isExpandable}
onExpand={node.handleExpand}
onClick={node.handleSelect}
onDoubleClick={node.handleDoubleClick}
portal={<TreeNodeMenu nodeId={id} isSelected={node?.isSelected} />}
portal={<TreeNodeMenu node={node.node} isSelected={node.isSelected} />}
>
<NavigationNodeChildren
parentId={id}
@@ -15,32 +15,31 @@ import { Icon } from '@dbeaver/core/blocks';
import { useService } from '@dbeaver/core/di';
import { MenuTrigger } from '@dbeaver/core/dialogs';
import { useNode } from '../../../shared/NodesManager/useNode';
import { NavNode } from '../../../shared/NodesManager/EntityTypes';
import { NavigationTreeContextMenuService } from '../../NavigationTreeContextMenuService';
import { treeNodeMenuStyles } from './treeNodeMenuStyles';
type TreeNodeMenuProps = {
nodeId: string;
node: NavNode;
isSelected: boolean;
}
export const TreeNodeMenu = observer(function TreeNodeMenu({
nodeId,
node,
isSelected,
}: TreeNodeMenuProps) {
const navigationTreeContextMenuService = useService(NavigationTreeContextMenuService);
const node = useNode(nodeId)!;
const menuPanel = useMemo(
() => navigationTreeContextMenuService.constructMenuWithContext(node),
[nodeId]
[node]
);
const isHidden = useMemo(
() => computed(() => !menuPanel.menuItems.length
|| menuPanel.menuItems.every(item => item.isHidden)),
[nodeId]
[menuPanel]
);
if (isHidden.get()) {
@@ -10,49 +10,52 @@ import { useState, useCallback, useEffect } from 'react';
import { useService } from '@dbeaver/core/di';
import { NavNode } from '../../shared/NodesManager/EntityTypes';
import { EObjectFeature } from '../../shared/NodesManager/EObjectFeature';
import { NodesManagerService } from '../../shared/NodesManager/NodesManagerService';
import { NodeWithParent } from '../../shared/NodesManager/NodeWithParent';
import { NavNodeManagerService } from '../../shared/NodesManager/NavNodeManagerService';
import { useNode } from '../../shared/NodesManager/useNode';
import { useChildren } from '../../shared/useChildren';
import { NavigationTreeService } from '../NavigationTreeService';
export function useNavigationTree(nodeId: string) {
export function useNavigationTree(nodeId: string, parentId: string) {
const navigationTreeService = useService(NavigationTreeService);
const nodesManagerService = useService(NodesManagerService);
const navNodeManagerService = useService(NavNodeManagerService);
const [isExpanded, switchExpand] = useState(false);
const [isSelected, switchSelect] = useState(false);
const node = useNode(nodeId);
const { node, isLoaded: nodeLoaded } = useNode(nodeId);
const children = useChildren(nodeId);
if (!node) {
return undefined;
}
const isLoaded = children?.isLoaded;
const isExpandable = isExpandableFilter(node) && (!children?.isLoaded || children.children.length > 0);
const isLoaded = children.isLoaded;
const isExpandable = isExpandableFilter(node) && (!isLoaded || children.children!.length > 0);
const handleDoubleClick = useCallback(
(nodeId: string) => nodesManagerService.navToNode(nodeId),
[nodesManagerService]
() => navNodeManagerService.navToNode(nodeId, parentId),
[navNodeManagerService, nodeId, parentId]
);
const handleExpand = useCallback(
(id: string) => {
async () => {
if (!isExpanded) {
navigationTreeService.loadNestedNodes(id).then(state => !state && switchExpand(false));
const state = await navigationTreeService.loadNestedNodes(nodeId);
if (!state) {
switchExpand(false);
}
}
switchExpand(!isExpanded);
},
[isExpanded]
[isExpanded, nodeId]
);
const handleSelect = useCallback(
(id: string, isMultiple?: boolean) => {
switchSelect(navigationTreeService.selectNode(id, isMultiple));
(isMultiple?: boolean) => {
switchSelect(navigationTreeService.selectNode(nodeId, isMultiple));
},
[isSelected]
[isSelected, nodeId]
);
const {
@@ -65,17 +68,25 @@ export function useNavigationTree(nodeId: string) {
}
}, [isExpandable && hasChildren]);
useEffect(() => {
if (isExpanded && !children.isLoaded && !children.isLoading && !!children.children && nodeLoaded) {
navigationTreeService.loadNestedNodes(nodeId);
}
}, [isExpanded, children.isLoaded, children.isLoading, children.children, nodeLoaded, nodeId]);
// Here we subscribe to selected nodes if current node selected (mobx)
if (isSelected && !navigationTreeService.selectedNodes.includes(node.id)) {
if (isSelected && !navigationTreeService.selectedNodes.includes(nodeId)) {
switchSelect(false);
}
return {
name,
node,
nodeType,
icon,
isExpanded,
isLoaded,
isLoading: children.isLoading,
isExpandable,
isSelected,
hasChildren,
@@ -85,6 +96,6 @@ export function useNavigationTree(nodeId: string) {
};
}
export function isExpandableFilter(node: NodeWithParent) {
return !node.object?.features?.find(feature => feature === EObjectFeature.entity);
export function isExpandableFilter(node: NavNode) {
return node.hasChildren && !node.objectFeatures.includes(EObjectFeature.entity);
}
@@ -11,23 +11,22 @@ import { observable } from 'mobx';
import { injectable } from '@dbeaver/core/di';
import { NotificationService } from '@dbeaver/core/eventsLog';
import { NodesManagerService } from '../shared/NodesManager/NodesManagerService';
import { NavNodeManagerService, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
@injectable()
export class NavigationTreeService {
selectedNodes = observable.array<string>([]);
constructor(private nodesManagerService: NodesManagerService,
private notificationService: NotificationService) {
}
constructor(
private NavNodeManagerService: NavNodeManagerService,
private notificationService: NotificationService
) { }
async loadNestedNodes(id = '/') {
async loadNestedNodes(id = ROOT_NODE_PATH) {
try {
const children = await this.nodesManagerService.loadChildren(id);
if (children) {
return true;
}
await this.NavNodeManagerService.loadTree(id);
return true;
} catch (exception) {
this.notificationService.logException(exception, `Can't load tree node: ${id}`);
}
+7 -7
View File
@@ -15,15 +15,16 @@ export * from './shared/NodesManager/extensions/IObjectCatalogProvider';
export * from './shared/NodesManager/extensions/IObjectCatalogSetter';
export * from './shared/NodesManager/extensions/IObjectSchemaProvider';
export * from './shared/NodesManager/extensions/IObjectSchemaSetter';
export * from './shared/NodesManager/DBObjectService';
export * from './shared/NodesManager/NavNodeManagerService';
export * from './shared/NodesManager/NodeManagerUtils';
export * from './shared/Navigation/NavigationService';
export * from './shared/NavigationTabs/NavigationTabsService';
export * from './shared/NavigationTabs/TabNavigationContext';
export * from './shared/NodesManager/NodesManagerService';
export * from './NavigationTree/NavigationTreeService';
export * from './NavigationTree/NavigationTreeContextMenuService';
export * from './shared/ToolsPanel/LogViewTab/LogViewerMenuService';
export * from './shared/ToolsPanel/LogViewTab/LogViewerService';
export * from './NavigationTree/NavigationTreeService';
export * from './NavigationTree/NavigationTreeContextMenuService';
export * from './Notifications/NotificationsItem/Snackbar/styles';
export * from './Notifications/NotificationsItem/Snackbar/NotificationMark';
@@ -33,7 +34,8 @@ export * from './shared/NavigationTabs/TabHandler';
export * from './shared/Navigation/NavigationContext';
export * from './shared/TabEntity/TabEntity';
// Enums
// export * from '';
export * from './shared/NodesManager/ENodeFeature';
export * from './shared/NodesManager/EObjectFeature';
// hooks
export * from './shared/useChildren';
@@ -50,10 +52,8 @@ export * from './TopNavBar/Logo';
// Interfaces
export * from './shared/Navigation/INavigator';
export * from './shared/NodesManager/ENodeFeature';
export * from './shared/NodesManager/EObjectFeature';
export * from './shared/NodesManager/NodeManagerUtils';
export * from './shared/NodesManager/NodeWithParent';
export * from './shared/NodesManager/EntityTypes';
// tab entity
export * from './shared/TabEntity/TabContainerEntity';
@@ -15,9 +15,9 @@ import {
import { NavigationTreeContextMenuService } from '../../NavigationTree/NavigationTreeContextMenuService';
import { EMainMenu, MainMenuService } from '../../TopNavBar/MainMenu/MainMenuService';
import { NavNode } from '../NodesManager/EntityTypes';
import { EObjectFeature } from '../NodesManager/EObjectFeature';
import { NodeManagerUtils } from '../NodesManager/NodeManagerUtils';
import { NodeWithParent } from '../NodesManager/NodeWithParent';
import { ConnectionsManagerService } from './ConnectionsManagerService';
@@ -52,20 +52,20 @@ export class ConnectionDialogsService {
}
);
const closeConnection: IContextMenuItem<NodeWithParent> = {
const closeConnection: IContextMenuItem<NavNode> = {
id: 'closeConnection',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& !!context.data.object?.features?.includes(EObjectFeature.dataSource);
&& !!context.data.objectFeatures.includes(EObjectFeature.dataSource);
},
title: 'Disconnect',
onClick: (context: IMenuContext<NodeWithParent>) => {
onClick: (context: IMenuContext<NavNode>) => {
const node = context.data;
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(node.id);
this.connectionsManagerService.closeConnectionAsync(connectionId);
},
};
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), closeConnection);
this.contextMenuService.addMenuItem<NavNode>(this.contextMenuService.getRootMenuToken(), closeConnection);
}
}
@@ -20,7 +20,7 @@ import {
DatabaseObjectInfo,
} from '@dbeaver/core/sdk';
import { NodesManagerService } from '../NodesManager/NodesManagerService';
import { NavNodeManagerService } from '../NodesManager/NavNodeManagerService';
export type DBDriver = Pick<
DriverInfo,
@@ -67,7 +67,7 @@ export class ConnectionsManagerService {
constructor(
private graphQLService: GraphQLService,
private nodesManagerService: NodesManagerService,
private navNodeManagerService: NavNodeManagerService,
private sessionService: SessionService
) {
this.sessionService.onUpdate.subscribe(this.restoreConnections.bind(this));
@@ -81,10 +81,10 @@ export class ConnectionsManagerService {
return this.dbDrivers.load();
}
addOpenedConnection(connection: Connection) {
async addOpenedConnection(connection: Connection) {
this.connectionsMap.set(connection.id, connection);
this.onOpenConnection.next(connection);
this.nodesManagerService.updateRootChildren(); // Update connections list, probably here we must also request node info and add it to nodes manager
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must also request node info and add it to nodes manager
}
getConnectionById(connectionId: string): Connection | undefined {
@@ -113,7 +113,7 @@ export class ConnectionsManagerService {
for (const connection of this.connections) {
await this.closeConnectionAsync(connection.id, true);
}
await this.nodesManagerService.updateRootChildren();
await this.navNodeManagerService.updateRootChildren();
}
async closeConnectionAsync(id: string, skipNodesRefresh?: boolean): Promise<void> {
@@ -122,7 +122,7 @@ export class ConnectionsManagerService {
this.connectionsMap.delete(id);
if (!skipNodesRefresh) {
await this.nodesManagerService.updateRootChildren(); // Update connections list, probably here we must just remove nodes from nodes manager
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must just remove nodes from nodes manager
}
}
@@ -132,7 +132,7 @@ export class ConnectionsManagerService {
}
private async afterConnectionClose(id: string) {
await this.nodesManagerService.closeConnection(id);
await this.navNodeManagerService.remove(id);
this.onCloseConnection.next(id);
}
@@ -154,7 +154,7 @@ export class ConnectionsManagerService {
this.connectionsMap.delete(connection.id);
}
await this.nodesManagerService.updateRootChildren();
await this.navNodeManagerService.updateRootChildren();
}
private isObjectContainersLoaded(
@@ -14,4 +14,5 @@ export interface ITabOptions<T = any> {
export interface ITab<T = any> extends ITabOptions<T> {
id: string;
restored: boolean;
}
@@ -67,6 +67,7 @@ export class NavigationTabsService {
typeof value.id === 'string'
&& typeof value.handlerId === 'string'
) {
value.restored = false;
map[key] = value;
}
}
@@ -86,7 +87,7 @@ export class NavigationTabsService {
}
}
@action selectTab(tabId: string, skipHandlers?: boolean) {
@action async selectTab(tabId: string, skipHandlers?: boolean) {
const tab = this.tabsMap.get(tabId);
if (!tab) {
return;
@@ -99,7 +100,7 @@ export class NavigationTabsService {
}
if (!skipHandlers) {
this.callHandlerCallback(tab, handler => handler.onSelect);
await this.callHandlerCallback(tab, handler => handler.onSelect);
}
this.tabSelectSubject.next(tab);
@@ -177,7 +178,6 @@ export class NavigationTabsService {
// must be executed with low priority, because this call runs many requests to backend and blocks others
async restoreTabs() {
const removedTabs: string[] = [];
const restoreTasks: Promise<void>[] = [];
const session = await this.sessionService.session.load();
for (const tabId of this.state.tabs) {
@@ -192,10 +192,9 @@ export class NavigationTabsService {
continue;
}
restoreTasks.push(this.restoreTab(tab, removedTabs));
await this.restoreTab(tab, removedTabs);
}
await Promise.all(restoreTasks);
if (removedTabs.length > 0) {
this.notificationService.logError({ title: 'Some tabs cannot be restored properly', isSilent: true });
@@ -237,6 +236,8 @@ export class NavigationTabsService {
if (restoreFail) {
removedTabs.push(tab.id);
} else {
tab.restored = true;
}
}
}
@@ -40,6 +40,7 @@ export class TabNavigationContext implements ITabNavigationContext {
openNewTab<T = any>(options: ITabOptions<T>): ITab<T> {
this._tab = observable({
id: uuid(),
restored: true,
...options,
});
this._isNewlyCreated = true;
@@ -0,0 +1,88 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@dbeaver/core/di';
import { GraphQLService, CachedResource } from '@dbeaver/core/sdk';
import { MetadataMap } from '@dbeaver/core/utils';
import { DBObject } from './EntityTypes';
export interface IDBObjectParams {
navNodeId: string[];
remove?: boolean;
}
interface IDBObjectMetadata {
loaded: boolean;
loading: boolean;
}
@injectable()
export class DBObjectService {
readonly dbObject = new CachedResource(
new Map(),
this.loadDBObject.bind(this),
(_, metadata, { navNodeId }) => navNodeId.every(navNodeId => metadata.get(navNodeId).loaded),
new MetadataMap<string, IDBObjectMetadata>(() => ({ loaded: false, loading: false })),
(_, metadata, { navNodeId }) => navNodeId.some(navNodeId => metadata.get(navNodeId).loading)
)
constructor(private graphQLService: GraphQLService) { }
getDBObject(navNodeId: string) {
return this.dbObject.data.get(navNodeId);
}
async load(navNodeId: string): Promise< DBObject>
async load(navNodeId: string[]): Promise<DBObject[]>
async load(navNodeId: string | string[]): Promise<DBObject | DBObject[]> {
const dbObject = await this.dbObject.load({ navNodeId: Array.isArray(navNodeId) ? navNodeId : [navNodeId] });
if (!Array.isArray(navNodeId)) {
return dbObject.get(navNodeId)!;
}
return navNodeId.map(navNodeId => dbObject.get(navNodeId)!);
}
async remove(navNodeId: string[]) {
await this.dbObject.refresh(true, { navNodeId, remove: true });
}
private async loadDBObject(
dbObject: Map<string, DBObject>,
metadata: MetadataMap<string, IDBObjectMetadata>,
load: boolean,
data: IDBObjectParams
) {
for (const navNodeId of data.navNodeId) {
if (data.remove) {
dbObject.delete(navNodeId);
metadata.delete(navNodeId);
} else {
const itemMetadata = metadata.get(navNodeId);
itemMetadata.loaded = false;
if (load) {
itemMetadata.loading = true;
try {
const { objectInfo: { object } } = await this.graphQLService.gql.getDBObjectInfo({
navNodeId,
});
dbObject.set(navNodeId, { navNodeId, ...object } as DBObject);
itemMetadata.loaded = true;
} finally {
itemMetadata.loading = false;
}
}
}
}
return dbObject;
}
}
@@ -0,0 +1,20 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { DatabaseObjectInfo, NavigatorNodeInfo } from '@dbeaver/core/sdk';
export type NavNodeInfo = NavigatorNodeInfo;
export type NavNode = Omit<NavNodeInfo, 'object'> & {
parentId: string;
objectFeatures: string[];
}
export type DBObject = DatabaseObjectInfo & {
navNodeId: string;
}
@@ -0,0 +1,489 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@dbeaver/core/di';
import { PermissionsService, EPermission } from '@dbeaver/core/root';
import { GraphQLService, CachedResource } from '@dbeaver/core/sdk';
import { MetadataMap } from '@dbeaver/core/utils';
import { INavigator } from '../Navigation/INavigator';
import { IContextProvider } from '../Navigation/NavigationContext';
import { NavigationService } from '../Navigation/NavigationService';
import { DBObjectService } from './DBObjectService';
import { ENodeFeature } from './ENodeFeature';
import { NavNodeInfo, NavNode } from './EntityTypes';
import { EObjectFeature } from './EObjectFeature';
import { NodeManagerUtils } from './NodeManagerUtils';
export enum NavigationType {
open,
closeConnection
}
export interface NavNodeKey {
nodeId: string;
parentId: string;
}
export interface NavNodeValue {
node: NavNodeInfo;
parentId: string;
}
export interface INodeContainerInfo {
connectionId?: string;
catalogId?: string;
schemaId?: string;
}
export interface INavNodePath {
nodes: NavNodeKey[];
remove?: boolean;
navNodeId?: never;
nodesValue?: never;
}
export interface INavNodeData {
nodesValue: NavNodeValue[];
navNodeId?: never;
nodes?: never;
remove?: never;
}
export interface INavNodeId {
navNodeId: string[];
remove?: boolean;
nodes?: never;
nodesValue?: never;
}
export interface INodeNavigationContext {
type: NavigationType;
nodeId: string;
parentId: string;
folderId: string;
name?: string;
icon?: string;
getParents: () => string[];
loadParents: (parents: string[]) => Promise<void>;
}
export interface INodeNavigationData {
type: NavigationType;
nodeId: string;
parentId: string;
folderId?: string;
}
interface INavNodeMetadata {
loaded: boolean;
loading: boolean;
}
interface INavTreeMetadata {
loaded: boolean;
loading: boolean;
}
export const ROOT_NODE_PATH = '/';
@injectable()
export class NavNodeManagerService {
readonly navNode = new CachedResource(
new Map(),
this.loadNavNodeInfo.bind(this),
(_, metadata, data) => {
if (data.nodesValue) {
return false;
}
const nodesId = data.navNodeId
? data.navNodeId
: data.nodes.map(node => node.nodeId);
return nodesId.every(navNodeId => metadata.get(navNodeId).loaded);
},
new MetadataMap<string, INavNodeMetadata>(() => ({ loaded: false, loading: false })),
(_, metadata, data) => {
if (data.nodesValue) {
return false;
}
const nodesId = data.navNodeId
? data.navNodeId
: data.nodes.map(node => node.nodeId);
return nodesId.some(navNodeId => metadata.get(navNodeId).loading);
}
);
readonly navTree = new CachedResource(
new Map(),
this.loadNavTree.bind(this),
(_, metadata, parentId) => metadata.get(parentId).loaded,
new MetadataMap<string, INavTreeMetadata>(() => ({ loaded: false, loading: false })),
(_, metadata, parentId) => metadata.get(parentId).loading
)
readonly navigator!: INavigator<INodeNavigationData>;
constructor(
private graphQLService: GraphQLService,
private navigationService: NavigationService,
private permissionsService: PermissionsService,
private dbObjectService: DBObjectService,
) {
this.navigator = this.navigationService.createNavigator<INodeNavigationData>(
data => data.nodeId,
this.navigateHandler.bind(this),
{
type: NavigationType.open,
nodeId: ROOT_NODE_PATH,
parentId: ROOT_NODE_PATH,
}
);
}
async navToNode(nodeId: string, parentId: string, folderId?: string) {
await this.navigator.navigateTo({
type: NavigationType.open,
nodeId,
parentId,
folderId,
});
}
async refresh(navNodeId: string) {
await this.graphQLService.gql.navRefreshNode({
nodePath: navNodeId,
});
await this.navTree.refresh(true, navNodeId, false);
}
async updateRootChildren() {
if (!await this.permissionsService.hasAsync(EPermission.public)) {
return;
}
await this.navTree.refresh(true, ROOT_NODE_PATH, false);
}
getTree(navNodeId: string): string[] | undefined
getTree(navNodeKey: NavNodeKey): string[] | undefined
getTree(navNodeKey: NavNodeKey[]): (string[] | undefined)[]
getTree(navNodeId: string | NavNodeKey | NavNodeKey[]) {
if (typeof navNodeId === 'string') {
return this.navTree.data.get(navNodeId);
}
if (Array.isArray(navNodeId)) {
return navNodeId.map(node => this.navTree.data.get(node.nodeId));
}
return this.navTree.data.get(navNodeId.nodeId);
}
async loadTree(navNodeId: string) {
await this.navTree.load(navNodeId, false);
return this.getTree(navNodeId)!;
}
getNode(navNodeId: string): NavNode | undefined
getNode(navNodeKey: NavNodeKey): NavNode | undefined
getNode(navNodeKey: NavNodeKey[]): (NavNode | undefined)[]
getNode(navNodeId: string | NavNodeKey | NavNodeKey[]) {
if (typeof navNodeId === 'string') {
return this.navNode.data.get(navNodeId);
}
if (Array.isArray(navNodeId)) {
return navNodeId.map(node => this.navNode.data.get(node.nodeId));
}
return this.navNode.data.get(navNodeId.nodeId);
}
getNestedChildren(navNode: string | string[]) {
const nestedChildren: string[] = [];
let prevChildren: string[];
if (Array.isArray(navNode)) {
prevChildren = navNode.concat();
nestedChildren.push(...navNode);
} else {
prevChildren = (this.getTree(navNode)?.concat() || []);
nestedChildren.push(...prevChildren);
}
while (prevChildren.length) {
const nodeKey = prevChildren.shift()!;
const children = this.getTree(nodeKey) || [];
prevChildren.push(...children);
nestedChildren.push(...children);
}
return nestedChildren;
}
async loadNode(node: NavNodeKey): Promise<NavNode>
async loadNode(...nodes: NavNodeKey[]): Promise<NavNode>
async loadNode(...nodes: NavNodeKey[]) {
await this.navNode.load({ nodes });
if (nodes.length === 1) {
return this.getNode(nodes[0])!;
}
return this.getNode(nodes);
}
getParent(node: NavNode) {
return this.navNode.data.get(node.parentId);
}
isNodeHasData(node?: string | NavNode) {
if (typeof node === 'string') {
node = this.getNode(node);
}
if (!node || !node.objectFeatures) {
return false;
}
return node.objectFeatures.includes(ENodeFeature.dataContainer)
|| node.objectFeatures.includes(ENodeFeature.container);
}
getNodeContainerInfo(nodeId: string): INodeContainerInfo {
const initial: INodeContainerInfo = {};
const scanParents = (res: INodeContainerInfo,
nodeId?: string): INodeContainerInfo => {
if (!nodeId) {
return res;
}
const object = this.getNode(nodeId);
if (!object) {
return res;
}
if (object.objectFeatures.includes(EObjectFeature.dataSource)) {
res.connectionId = object.id;
}
if (object.objectFeatures.includes(EObjectFeature.catalog)) {
res.catalogId = object.name; // note that catalogId is node name
}
if (object.objectFeatures.includes(EObjectFeature.schema)) {
res.schemaId = object.name; // note that schemaId is node name
}
return scanParents(res, object.parentId);
};
return scanParents(initial, nodeId);
}
async remove(path = ROOT_NODE_PATH) {
await this.navTree.refresh(true, path, true);
}
navigationNavNodeContext = async (
contexts: IContextProvider<INodeNavigationData>,
data: INodeNavigationData
): Promise<INodeNavigationContext> => {
let nodeId = data.nodeId;
let parentId = data.parentId;
let folderId = '';
let name: string | undefined;
let icon: string | undefined;
if (NodeManagerUtils.isDatabaseObject(nodeId) && data.type !== NavigationType.closeConnection) {
const node = await this.loadNode({ nodeId, parentId });
name = node.name;
icon = node.icon;
if (node.folder) {
const parent = this.getNode(node.parentId);
folderId = nodeId;
if (parent && !parent.folder) {
nodeId = parent.id;
parentId = parent.parentId;
name = parent.name;
icon = parent.icon;
}
}
if (data.folderId) {
folderId = data.folderId;
}
}
const getParents = () => {
const parents: string[] = [];
let parent = this.getNode(nodeId);
while (parent && parent.parentId !== ROOT_NODE_PATH) {
parents.unshift(parent.parentId);
parent = this.getNode(parent.parentId);
}
return parents;
};
const loadParents = async (parents: string[]) => {
let parentId = ROOT_NODE_PATH;
await this.navNode.load({
nodes: parents.map((nodeId) => {
try {
return { nodeId, parentId };
} finally {
parentId = nodeId;
}
}),
});
};
return {
type: data.type,
nodeId,
parentId,
folderId,
name,
icon,
getParents,
loadParents,
};
}
private async navigateHandler(contexts: IContextProvider<INodeNavigationData>) {
}
private async loadNavNodeInfo(
navNode: Map<string, NavNode>,
metadata: MetadataMap<string, INavNodeMetadata>,
load: boolean,
data: INavNodePath | INavNodeData | INavNodeId,
) {
if (data.nodesValue) {
for (const nodeValue of data.nodesValue) {
const itemMetadata = metadata.get(nodeValue.node.id);
navNode.set(nodeValue.node.id, {
...nodeValue.node,
objectFeatures: nodeValue.node.object?.features || [],
parentId: nodeValue.parentId,
});
itemMetadata.loaded = true;
}
return navNode;
}
const nodesId = data.navNodeId
? data.navNodeId
: data.nodes.map(node => node.nodeId);
if (data.remove) {
for (const navNodeId of nodesId) {
navNode.delete(navNodeId);
metadata.delete(navNodeId);
}
await this.dbObjectService.remove(nodesId);
return navNode;
}
for (const navNodeId of nodesId) {
const itemMetadata = metadata.get(navNodeId);
try {
itemMetadata.loaded = false;
if (load) {
itemMetadata.loading = true;
const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({
nodePath: navNodeId,
});
let parentId = ROOT_NODE_PATH;
if (data.navNodeId) {
parentId = navNode.get(navNodeId)?.parentId || parentId;
} else {
parentId = data.nodes.find(node => node.nodeId === navNodeId)?.parentId || parentId;
}
navNode.set(navNodeId, {
...navNodeInfo,
objectFeatures: navNodeInfo.object?.features || [],
parentId,
});
itemMetadata.loaded = true;
}
} finally {
itemMetadata.loading = false;
}
}
await this.dbObjectService.dbObject.refresh(false, { navNodeId: nodesId });
return navNode;
}
private async loadNavTree(
navTree: Map<string, string[]>,
metadata: MetadataMap<string, INavTreeMetadata>,
load: boolean,
parentId: string,
remove: boolean
) {
const itemMetadata = metadata.get(parentId);
let childrenToRemove = navTree.get(parentId)?.concat() || [];
if (remove) {
childrenToRemove = [parentId];
} else {
try {
itemMetadata.loaded = false;
if (load) {
itemMetadata.loading = true;
const { navNodeChildren } = await this.graphQLService.gql.navNodeChildren({
parentPath: parentId,
});
await this.navNode.refresh(true, {
nodesValue: navNodeChildren.map(node => ({ node, parentId })),
});
navTree.set(parentId, navNodeChildren.map(node => node.id));
itemMetadata.loaded = true;
}
} finally {
itemMetadata.loading = false;
}
const newChildren = navTree.get(parentId);
const nestedChildren = this.getNestedChildren(parentId);
await this.navNode.refresh(false, {
navNodeId: nestedChildren.filter(navNodeId => !newChildren || !newChildren.includes(navNodeId)),
});
for (const navNodeId of nestedChildren) {
const itemMetadata = metadata.get(navNodeId);
itemMetadata.loaded = false;
}
if (newChildren) {
childrenToRemove = childrenToRemove.filter(navNodeId => !newChildren.includes(navNodeId));
}
}
const nestedChildren = this.getNestedChildren(childrenToRemove);
await this.navNode.refresh(true, { navNodeId: nestedChildren, remove: true });
for (const navNodeId of nestedChildren) {
navTree.delete(navNodeId);
metadata.delete(navNodeId);
}
return navTree;
}
}
@@ -1,29 +0,0 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { DatabaseObjectInfo, NavigatorNodeInfo } from '@dbeaver/core/sdk';
// Node Info without ObjectProperties
export type NavigatorNodeInfoLight = Omit<NavigatorNodeInfo, 'object'> & {
object?: {
features?: string[]; // EObjectFeature
};
}
export type NodeWithParent = NavigatorNodeInfoLight & {
parentId: string;
}
export type DatabaseObjectInfoWithId = DatabaseObjectInfo & {
id: string;
}
export type NodeChildren = {
children: string[];
isLoaded: boolean;
}
@@ -1,314 +0,0 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@dbeaver/core/di';
import { PermissionsService, EPermission } from '@dbeaver/core/root';
import { DatabaseObjectInfo, GraphQLService } from '@dbeaver/core/sdk';
import { INavigator } from '../Navigation/INavigator';
import { IContextProvider } from '../Navigation/NavigationContext';
import { NavigationService } from '../Navigation/NavigationService';
import { ENodeFeature } from './ENodeFeature';
import { EObjectFeature } from './EObjectFeature';
import { NodesStore } from './NodesStore';
import { DatabaseObjectInfoWithId, NodeChildren, NodeWithParent } from './NodeWithParent';
export enum NavigationType {
open,
closeConnection
}
export interface INodeContainerInfo {
connectionId?: string;
catalogId?: string;
schemaId?: string;
}
export interface INodeNavigationContext {
type: NavigationType;
nodeId: string;
folderId: string;
name?: string;
icon?: string;
}
export interface INodeNavigationData {
type: NavigationType;
nodeId: string;
folderId?: string;
}
const ROOT_NODE_PATH = '/';
// TODO: should be renamed to DBObjectManagerService
@injectable()
export class NodesManagerService {
readonly navigator!: INavigator<INodeNavigationData>;
private nodesStore = new NodesStore();
constructor(private graphQLService: GraphQLService,
private navigationService: NavigationService,
private permissionsService: PermissionsService) {
this.navigator = this.navigationService.createNavigator<INodeNavigationData>(
data => data.nodeId,
this.navigateHandler.bind(this),
{
type: NavigationType.open,
nodeId: ROOT_NODE_PATH,
}
);
}
async closeConnection(nodeId: string) {
await this.navigator.navigateTo({
type: NavigationType.closeConnection,
nodeId,
});
}
async navToNode(nodeId: string, folderId?: string) {
await this.navigator.navigateTo({
type: NavigationType.open,
nodeId,
folderId,
});
}
async refreshNode(nodeId: string) {
await this.purgeServerCache(nodeId);
this.nodesStore.removeNode(nodeId);
await this.updateNodeInfo(nodeId);
}
getDatabaseObjectInfo(nodeId: string): DatabaseObjectInfoWithId | undefined {
return this.nodesStore.getDatabaseObjectInfo(nodeId);
}
getNode(nodeId: string): NodeWithParent | undefined {
return this.nodesStore.getNode(nodeId);
}
getChildren(nodeId: string): NodeChildren | undefined {
return this.nodesStore.getChildren(nodeId);
}
async updateNodeInfo(nodeId: string) {
const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({
nodePath: nodeId,
});
let parentId = nodeId.match(/^(.*)\/.*?$/)![1];
if (parentId === 'database:/') {
parentId = ROOT_NODE_PATH;
}
const newNode = { ...navNodeInfo, parentId };
this.nodesStore.updateNodeInfo(newNode);
return newNode;
}
async updateDatabaseObjectInfo(nodeId: string) {
const node = this.nodesStore.getNode(nodeId);
if (!node) {
await this.updateNodeInfo(nodeId);
}
const { objectInfo } = await this.graphQLService.gql.queryDatabaseObjectInfo({
nodeId,
// filter: { features: ['viewable', 'editPossible'] },
});
const info = { ...objectInfo.object, id: objectInfo.id };
this.nodesStore.updateDatabaseObjectInfo(info);
return info;
}
async purgeServerCache(nodeId: string) {
await this.graphQLService.gql.navRefreshNode({
nodePath: nodeId,
});
}
async updateChildren(parentId: string) {
const { navNodeChildren } = await this.graphQLService.gql.navNodeChildren({
parentPath: parentId,
});
const children = navNodeChildren.map(child => child.id);
this.nodesStore.updateChildren(parentId, children);
for (const child of navNodeChildren) {
this.nodesStore.updateNodeInfo({ ...child, parentId });
}
return children;
}
async updateRootChildren() {
if (!await this.permissionsService.hasAsync(EPermission.public)) {
return;
}
this.updateChildren(ROOT_NODE_PATH);
}
async updateChildrenDatabaseObjectInfo(nodeId: string) {
const {
childrenDatabaseObjectInfo,
} = await this.graphQLService.gql.queryChildrenDatabaseObjectInfo({
nodePath: nodeId,
// filter: { features: ['viewable', 'editPossible'] }, // filter properties for view
});
const children = childrenDatabaseObjectInfo.map(children => ({
...children.object,
id: children.id,
}));
this.nodesStore.updateChildrenDatabaseObjectInfo(children);
return children;
}
async loadNodeInfo(nodeId: string): Promise<NodeWithParent> {
let node = this.nodesStore.getNode(nodeId);
if (node) {
if (node.parentId !== ROOT_NODE_PATH) {
await this.loadNodeInfo(node.parentId);
}
return node;
}
node = await this.updateNodeInfo(nodeId);
if (node.parentId !== ROOT_NODE_PATH) {
await this.loadNodeInfo(node.parentId);
}
return node;
}
async loadChildren(parentId = ROOT_NODE_PATH): Promise<string[]> {
const nodeChildren = this.nodesStore.getChildren(parentId);
if (nodeChildren?.isLoaded) {
return nodeChildren.children;
}
return this.updateChildren(parentId);
}
async loadDatabaseObjectInfo(
nodeId: string
): Promise<DatabaseObjectInfo> {
const databaseObjectInfo = this.nodesStore.getDatabaseObjectInfo(nodeId);
if (databaseObjectInfo) {
return databaseObjectInfo;
}
return this.updateDatabaseObjectInfo(nodeId);
}
async loadChildrenDatabaseObjectInfo(parentId: string): Promise<DatabaseObjectInfoWithId[]> {
const children = await this.loadChildren(parentId);
const childrenDatabaseObjectInfo: DatabaseObjectInfoWithId[] = [];
for (const child of children) {
const databaseObjectInfo = this.nodesStore.getDatabaseObjectInfo(child);
if (!databaseObjectInfo) {
return this.updateChildrenDatabaseObjectInfo(parentId);
}
childrenDatabaseObjectInfo.push(databaseObjectInfo);
}
return childrenDatabaseObjectInfo;
}
getParent(node: NodeWithParent) {
return this.nodesStore.getNode(node.parentId);
}
getNodeContainerInfo(nodeId: string): INodeContainerInfo {
const initial: INodeContainerInfo = {};
const scanParents = (res: INodeContainerInfo,
nodeId?: string): INodeContainerInfo => {
if (!nodeId) {
return res;
}
const node = this.getNode(nodeId);
if (!node) {
return res;
}
if (node?.object?.features?.includes(EObjectFeature.dataSource)) {
res.connectionId = node.id;
}
if (node?.object?.features?.includes(EObjectFeature.catalog)) {
res.catalogId = node.name; // note that catalogId is node name
}
if (node?.object?.features?.includes(EObjectFeature.schema)) {
res.schemaId = node.name; // note that schemaId is node name
}
return scanParents(res, node.parentId);
};
return scanParents(initial, nodeId);
}
removeNodes(path = ROOT_NODE_PATH) {
this.nodesStore.removeNode(path);
}
navigationNodeContext = async (
contexts: IContextProvider<INodeNavigationData>,
data: INodeNavigationData
): Promise<INodeNavigationContext> => {
let nodeId = data.nodeId;
let folderId = '';
let name: string | undefined;
let icon: string | undefined;
if (isDatabaseObject(nodeId) && data.type !== NavigationType.closeConnection) {
const node = await this.loadNodeInfo(nodeId);
name = node.name;
icon = node.icon;
if (node.folder) {
const parent = await this.loadNodeInfo(node.parentId);
folderId = nodeId;
if (parent && !parent.folder) {
nodeId = parent.id;
name = parent.name;
icon = parent.icon;
}
}
if (data.folderId) {
folderId = data.folderId;
}
}
return {
type: data.type,
nodeId,
folderId,
name,
icon,
};
}
async navigateHandler(contexts: IContextProvider<INodeNavigationData>) {
}
isNodeHasData(info?: string | DatabaseObjectInfo) {
if (typeof info === 'string') {
info = this.nodesStore.getDatabaseObjectInfo(info);
}
if (!info || !info.features) {
return false;
}
return info.features.includes(ENodeFeature.dataContainer)
|| info.features.includes(ENodeFeature.container);
}
}
@@ -1,90 +0,0 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { action, observable } from 'mobx';
import {
DatabaseObjectInfoWithId,
NodeChildren,
NodeWithParent,
} from './NodeWithParent';
export class NodesStore {
@observable
private nodesMap: Map<string, NodeWithParent> = new Map()
@observable
private databaseObjectInfoMap: Map<string, DatabaseObjectInfoWithId> = new Map()
@observable
private nodeChildrenMap: Map<string, NodeChildren> = new Map()
getNode(nodeId: string): NodeWithParent | undefined {
return this.nodesMap.get(nodeId);
}
getChildren(nodeId: string): NodeChildren | undefined {
return this.nodeChildrenMap.get(nodeId);
}
getDatabaseObjectInfo(nodeId: string): DatabaseObjectInfoWithId | undefined {
return this.databaseObjectInfoMap.get(nodeId);
}
@action
updateNodeInfo(node: NodeWithParent) {
this.nodesMap.set(node.id, node);
const parent = this.nodeChildrenMap.get(node.parentId);
if (!parent) {
this.nodeChildrenMap.set(node.parentId, {
children: [node.id],
isLoaded: false,
});
} else if (!parent.children.includes(node.id)) {
parent.children.push(node.id);
}
}
@action
updateDatabaseObjectInfo(databaseObjectInfo: DatabaseObjectInfoWithId) {
this.databaseObjectInfoMap.set(databaseObjectInfo.id, databaseObjectInfo);
}
@action
updateChildren(parentId: string, children: string[]) {
this.nodeChildrenMap.set(parentId, {
children,
isLoaded: true,
});
}
@action
updateChildrenDatabaseObjectInfo(children: DatabaseObjectInfoWithId[]) {
for (const child of children) {
this.databaseObjectInfoMap.set(child.id, child);
}
}
@action
removeNode(nodeId: string) {
const node = this.nodesMap.get(nodeId);
if (!node) {
return;
}
const parent = this.nodeChildrenMap.get(node.id);
if (parent) {
for (const children of parent.children) {
this.removeNode(children);
}
this.nodeChildrenMap.delete(node.id);
}
this.databaseObjectInfoMap.delete(nodeId);
this.nodesMap.delete(nodeId);
}
}
@@ -1,8 +1,8 @@
query queryChildrenDatabaseObjectInfo(
$nodePath: ID!
query getChildrenDBObjectInfo(
$navNodeId: ID!
$filter: ObjectPropertyFilter
) {
childrenDatabaseObjectInfo: navNodeChildren(parentPath: $nodePath) {
dbObjects: navNodeChildren(parentPath: $navNodeId) {
id
object {
features
@@ -1,6 +1,5 @@
query queryDatabaseObjectInfo($nodeId: ID!, $filter: ObjectPropertyFilter) {
objectInfo: navNodeInfo(nodePath: $nodeId) {
id
query getDBObjectInfo($navNodeId: ID!, $filter: ObjectPropertyFilter) {
objectInfo: navNodeInfo(nodePath: $navNodeId) {
object {
features
properties(filter: $filter) {
@@ -8,10 +8,10 @@
import { useService } from '@dbeaver/core/di';
import { NodesManagerService } from './NodesManagerService';
import { DBObjectService } from './DBObjectService';
export function useDatabaseObjectInfo(nodeId: string) {
const nodesManagerService = useService(NodesManagerService);
export function useDatabaseObjectInfo(navNodeId: string) {
const dbObjectService = useService(DBObjectService);
return nodesManagerService.getDatabaseObjectInfo(nodeId);
return dbObjectService.getDBObject(navNodeId);
}
@@ -8,10 +8,19 @@
import { useService } from '@dbeaver/core/di';
import { NodesManagerService } from './NodesManagerService';
import { NavNodeManagerService } from './NavNodeManagerService';
export function useNode(nodeId: string) {
const nodesManagerService = useService(NodesManagerService);
export function useNode(navNodeId: string) {
const navNodeManagerService = useService(NavNodeManagerService);
const node = navNodeManagerService.getNode(navNodeId);
const isLoading = navNodeManagerService.navNode.isDataLoading({
navNodeId: [navNodeId],
});
const isLoaded = navNodeManagerService.navNode.isLoaded({
navNodeId: [navNodeId],
});
return nodesManagerService.getNode(nodeId);
return {
node, isLoading, isLoaded,
};
}
@@ -8,10 +8,17 @@
import { useService } from '@dbeaver/core/di';
import { NodesManagerService } from './NodesManager/NodesManagerService';
import { NavNodeManagerService, ROOT_NODE_PATH } from './NodesManager/NavNodeManagerService';
export function useChildren(parentId = '/') {
const nodesManagerService = useService(NodesManagerService);
export function useChildren(navNodeId = ROOT_NODE_PATH) {
const navNodeManagerService = useService(NavNodeManagerService);
const children = navNodeManagerService.getTree(navNodeId);
const isLoading = navNodeManagerService.navTree.isDataLoading(navNodeId, false);
const isLoaded = navNodeManagerService.navTree.isLoaded(navNodeId, false);
return nodesManagerService.getChildren(parentId);
return {
children,
isLoaded,
isLoading,
};
}
+3 -2
View File
@@ -8,7 +8,7 @@
import {
ConnectionDialogsService, ConnectionsManagerService,
MainMenuService, NavigationService, NavigationTreeService, NodesManagerService,
MainMenuService, NavigationService, NavigationTreeService, NavNodeManagerService, DBObjectService,
ConnectionSchemaManagerService, NavigationTabsService,
NavigationTreeContextMenuService,
SettingsMenuService, LogViewerService, LogViewerMenuService, TopNavService,
@@ -64,7 +64,8 @@ export const coreManifest: PluginManifest = {
NavigationTabsService,
NavigationTreeContextMenuService,
NavigationTreeService,
NodesManagerService,
NavNodeManagerService,
DBObjectService,
NotificationService,
SessionService,
SettingsMenuService,
@@ -52,7 +52,7 @@ export class PermissionsService {
}
async update() {
await this.permissions.refresh();
await this.permissions.refresh(true);
this.updateSubject.next();
}
@@ -33,7 +33,7 @@ export class SessionService {
}
async update() {
await this.session.refresh();
await this.session.refresh(true);
}
private async refreshSessionStateAsync(data: SessionState | undefined): Promise<SessionState> {
+44 -9
View File
@@ -7,6 +7,7 @@
*/
import { observable } from 'mobx';
import { Subject, Observable } from 'rxjs';
export type Loader<TData, TMetadata = {}, TArgs extends any[] = []> = (
current: TData,
@@ -21,13 +22,23 @@ export type IsLoaded<TData, TMetadata = {}, TArgs extends any[] = []> = (
...args: TArgs
) => boolean
export type IsLoading<TData, TMetadata = {}, TArgs extends any[] = []> = (
current: TData,
metadata: TMetadata,
...args: TArgs
) => boolean
export class CachedResource<TData, TMetadata = {}, TArgs extends any[] = []> {
@observable data: TData;
@observable private loading = false;
@observable private metadata: TMetadata;
readonly onDataUpdate: Observable<TData>;
private dataSubject: Subject<TData>;
private loader: Loader<TData, TMetadata, TArgs>;
private isLoadedCheck: IsLoaded<TData, TMetadata, TArgs>;
private isLoadingCheck?: IsLoading<TData, TMetadata, TArgs>;
private activePromise: Promise<TData> | null = null;
constructor(
@@ -35,11 +46,15 @@ export class CachedResource<TData, TMetadata = {}, TArgs extends any[] = []> {
loader: Loader<TData, TMetadata, TArgs>,
isLoadedCheck: IsLoaded<TData, TMetadata, TArgs>,
metadata?: TMetadata,
isLoadingCheck?: IsLoading<TData, TMetadata, TArgs>,
) {
this.data = defaultValue;
this.loader = loader;
this.isLoadedCheck = isLoadedCheck;
this.isLoadingCheck = isLoadingCheck;
this.metadata = metadata || {} as TMetadata;
this.dataSubject = new Subject<TData>();
this.onDataUpdate = this.dataSubject.asObservable();
}
isLoaded(...args: TArgs): boolean {
@@ -49,36 +64,56 @@ export class CachedResource<TData, TMetadata = {}, TArgs extends any[] = []> {
isLoading(): boolean {
return this.loading;
}
isDataLoading(...args: TArgs): boolean {
if (this.isLoadingCheck) {
return this.loading && this.isLoadingCheck(this.data, this.metadata, ...args);
}
return this.loading;
}
async refresh(...args: TArgs): Promise<TData> {
return this.loadAll(true, args);
async refresh(load = false, ...args: TArgs): Promise<TData> {
return this.loadData(load, true, args);
}
async load(...args: TArgs): Promise<TData> {
return this.loadAll(false, args);
return this.loadData(true, false, args);
}
private async loadAll(update: boolean, args: TArgs) {
async refreshUnblocked(load = false, ...args: TArgs): Promise<TData> {
return this.loadData(load, true, args, true);
}
async loadUnblocked(...args: TArgs): Promise<TData> {
return this.loadData(true, false, args, true);
}
private async loadData(load: boolean, update: boolean, args: TArgs, unblocked?: boolean) {
if (unblocked) {
return this.loadingTask(load, update, args);
}
await this.waitActive();
this.activePromise = this.loadingTask(update, args);
this.activePromise = this.loadingTask(load, update, args);
try {
return await this.activePromise;
} finally {
this.activePromise = null;
this.dataSubject.next(this.data);
}
}
private async loadingTask(update: boolean, args: TArgs): Promise<TData> {
private async loadingTask(load: boolean, update: boolean, args: TArgs): Promise<TData> {
const prevState = this.loading;
this.loading = true;
try {
if (this.isLoaded(...args) && !update) {
// don't load existed data & don't refresh doesn't loaded data
if ((this.isLoaded(...args) && !update) || (!this.isLoaded(...args) && update && !load)) {
return this.data;
}
this.data = await this.loader(this.data, this.metadata, update, ...args);
this.data = await this.loader(this.data, this.metadata, load, ...args);
} finally {
this.loading = false;
this.loading = prevState;
}
return this.data;
+99 -102
View File
@@ -1,5 +1,6 @@
/* eslint-disable max-len */
import { GraphQLClient } from 'graphql-request';
export type Maybe<T> = T;
/** All built-in and custom scalars, mapped to their actual values */
@@ -686,7 +687,7 @@ export type NavGetStructContainersQueryVariables = {
};
export type NavGetStructContainersQuery = { navGetStructContainers: { catalogList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>>, schemaList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>> } };
export type NavGetStructContainersQuery = { navGetStructContainers: { catalogList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>>; schemaList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>> } };
export type CloseConnectionMutationVariables = {
id: Scalars['ID'];
@@ -727,7 +728,7 @@ export type DriverPropertiesQueryVariables = {
export type DriverPropertiesQuery = { driver: Array<(
Pick<DriverInfo, 'driverParameters'>
& { driverProperties?: Maybe<Array<Maybe<Pick<DriverPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues'>>>> }
)> };
)>; };
export type GetDriverByIdQueryVariables = {
driverId: Scalars['ID'];
@@ -750,6 +751,31 @@ export type TestConnectionMutationVariables = {
export type TestConnectionMutation = { testConnection: Pick<ConnectionInfo, 'id'> };
export type GetChildrenDbObjectInfoQueryVariables = {
navNodeId: Scalars['ID'];
filter?: Maybe<ObjectPropertyFilter>;
};
export type GetChildrenDbObjectInfoQuery = { dbObjects: Array<(
Pick<NavigatorNodeInfo, 'id'>
& { object?: Maybe<(
Pick<DatabaseObjectInfo, 'features'>
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
)>; }
)>; };
export type GetDbObjectInfoQueryVariables = {
navNodeId: Scalars['ID'];
filter?: Maybe<ObjectPropertyFilter>;
};
export type GetDbObjectInfoQuery = { objectInfo: { object?: Maybe<(
Pick<DatabaseObjectInfo, 'features'>
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
)>; }; };
export type NavNodeChildrenQueryVariables = {
parentPath: Scalars['ID'];
};
@@ -758,7 +784,7 @@ export type NavNodeChildrenQueryVariables = {
export type NavNodeChildrenQuery = { navNodeChildren: Array<(
Pick<NavigatorNodeInfo, 'id' | 'name' | 'hasChildren' | 'nodeType' | 'icon' | 'folder' | 'inline' | 'navigable' | 'features'>
& { object?: Maybe<Pick<DatabaseObjectInfo, 'features'>> }
)> };
)>; };
export type NavNodeInfoQueryVariables = {
nodePath: Scalars['ID'];
@@ -768,7 +794,7 @@ export type NavNodeInfoQueryVariables = {
export type NavNodeInfoQuery = { navNodeInfo: (
Pick<NavigatorNodeInfo, 'id' | 'name' | 'hasChildren' | 'nodeType' | 'icon' | 'folder' | 'inline' | 'navigable' | 'features'>
& { object?: Maybe<Pick<DatabaseObjectInfo, 'features'>> }
) };
); };
export type NavRefreshNodeQueryVariables = {
nodePath: Scalars['ID'];
@@ -777,34 +803,6 @@ export type NavRefreshNodeQueryVariables = {
export type NavRefreshNodeQuery = Pick<Query, 'navRefreshNode'>;
export type QueryChildrenDatabaseObjectInfoQueryVariables = {
nodePath: Scalars['ID'];
filter?: Maybe<ObjectPropertyFilter>;
};
export type QueryChildrenDatabaseObjectInfoQuery = { childrenDatabaseObjectInfo: Array<(
Pick<NavigatorNodeInfo, 'id'>
& { object?: Maybe<(
Pick<DatabaseObjectInfo, 'features'>
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
)> }
)> };
export type QueryDatabaseObjectInfoQueryVariables = {
nodeId: Scalars['ID'];
filter?: Maybe<ObjectPropertyFilter>;
};
export type QueryDatabaseObjectInfoQuery = { objectInfo: (
Pick<NavigatorNodeInfo, 'id'>
& { object?: Maybe<(
Pick<DatabaseObjectInfo, 'features'>
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
)> }
) };
export type ReadSessionLogQueryVariables = {
maxEntries: Scalars['Int'];
clearEntries: Scalars['Boolean'];
@@ -891,7 +889,7 @@ export type GetAuthProvidersQueryVariables = {};
export type GetAuthProvidersQuery = { providers: Array<(
Pick<AuthProviderInfo, 'id' | 'label' | 'icon' | 'description' | 'defaultProvider'>
& { credentialParameters: Array<Pick<AuthCredentialInfo, 'id' | 'displayName' | 'description' | 'admin' | 'user' | 'possibleValues' | 'encryption'>> }
)> };
)>; };
export type GetSessionUserQueryVariables = {};
@@ -906,7 +904,7 @@ export type AsyncExportTaskStatusMutationVariables = {
export type AsyncExportTaskStatusMutation = { taskInfo: (
Pick<AsyncTaskInfo, 'id' | 'name' | 'running' | 'status' | 'taskResult'>
& { error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
) };
); };
export type ExportDataFromContainerQueryVariables = {
connectionId: Scalars['ID'];
@@ -918,7 +916,7 @@ export type ExportDataFromContainerQueryVariables = {
export type ExportDataFromContainerQuery = { taskInfo: (
Pick<AsyncTaskInfo, 'id' | 'running' | 'taskResult'>
& { error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
) };
); };
export type ExportDataFromResultsQueryVariables = {
connectionId: Scalars['ID'];
@@ -931,7 +929,7 @@ export type ExportDataFromResultsQueryVariables = {
export type ExportDataFromResultsQuery = { taskInfo: (
Pick<AsyncTaskInfo, 'id' | 'running' | 'taskResult'>
& { error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
) };
); };
export type GetDataTransferProcessorsQueryVariables = {};
@@ -939,7 +937,7 @@ export type GetDataTransferProcessorsQueryVariables = {};
export type GetDataTransferProcessorsQuery = { processors: Array<(
Pick<DataTransferProcessorInfo, 'id' | 'name' | 'description' | 'fileExtension' | 'appFileExtension' | 'appName' | 'order' | 'icon' | 'isBinary' | 'isHTML'>
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues' | 'features'>>>> }
)> };
)>; };
export type RemoveDataTransferFileQueryVariables = {
dataFileId: Scalars['String'];
@@ -965,10 +963,10 @@ export type AsyncSqlExecuteQueryMutation = { taskInfo: (
& { resultSet?: Maybe<(
Pick<SqlResultSet, 'id' | 'rows'>
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
)> }
)> }
)>, error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
) };
)>; }
)>; }
)>; error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>>; }
); };
export type AsyncTaskCancelMutationVariables = {
taskId: Scalars['String'];
@@ -991,10 +989,10 @@ export type AsyncTaskStatusMutation = { taskInfo: (
& { resultSet?: Maybe<(
Pick<SqlResultSet, 'id' | 'rows'>
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
)> }
)> }
)>, error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
) };
)>; }
)>; }
)>; error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>>; }
); };
export type ExecuteSqlQueryMutationVariables = {
connectionId: Scalars['ID'];
@@ -1011,9 +1009,9 @@ export type ExecuteSqlQueryMutation = { result?: Maybe<(
& { resultSet?: Maybe<(
Pick<SqlResultSet, 'id' | 'rows'>
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
)> }
)> }
)> };
)>; }
)>; }
)>; };
export type MetadataGetNodeDdlQueryVariables = {
nodeId: Scalars['ID'];
@@ -1055,9 +1053,9 @@ export type ReadDataFromContainerMutation = { readDataFromContainer?: Maybe<(
& { resultSet?: Maybe<(
Pick<SqlResultSet, 'id' | 'rows'>
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
)> }
)> }
)> };
)>; }
)>; }
)>; };
export type SqlContextCreateMutationVariables = {
connectionId: Scalars['ID'];
@@ -1109,8 +1107,8 @@ export type UpdateResultsDataMutation = { result?: Maybe<(
& { results: Array<(
Pick<SqlQueryResults, 'updateRowCount'>
& { resultSet?: Maybe<Pick<SqlResultSet, 'id' | 'rows'>> }
)> }
)> };
)>; }
)>; };
export type OpenSessionMutationVariables = {};
@@ -1118,7 +1116,7 @@ export type OpenSessionMutationVariables = {};
export type OpenSessionMutation = { session: (
Pick<SessionInfo, 'createTime' | 'lastAccessTime' | 'cacheExpired' | 'locale'>
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'>> }
) };
); };
export type ServerConfigQueryVariables = {};
@@ -1126,7 +1124,7 @@ export type ServerConfigQueryVariables = {};
export type ServerConfigQuery = { serverConfig: (
Pick<ServerConfig, 'name' | 'version' | 'productConfiguration' | 'supportsPredefinedConnections' | 'supportsProvidedConnections' | 'supportsCustomConnections' | 'supportsConnectionBrowser' | 'supportsWorkspaces' | 'anonymousAccessEnabled' | 'authenticationEnabled'>
& { supportedLanguages: Array<Pick<ServerLanguage, 'isoCode' | 'displayName' | 'nativeName'>> }
) };
); };
export type SessionPermissionsQueryVariables = {};
@@ -1139,7 +1137,7 @@ export type SessionStateQueryVariables = {};
export type SessionStateQuery = { sessionState: (
Pick<SessionInfo, 'createTime' | 'lastAccessTime' | 'locale' | 'cacheExpired'>
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'>> }
) };
); };
export type TouchSessionMutationVariables = {};
@@ -1257,6 +1255,43 @@ export const TestConnectionDocument = `
}
}
`;
export const GetChildrenDbObjectInfoDocument = `
query getChildrenDBObjectInfo($navNodeId: ID!, $filter: ObjectPropertyFilter) {
dbObjects: navNodeChildren(parentPath: $navNodeId) {
id
object {
features
properties(filter: $filter) {
id
category
dataType
description
displayName
features
value
}
}
}
}
`;
export const GetDbObjectInfoDocument = `
query getDBObjectInfo($navNodeId: ID!, $filter: ObjectPropertyFilter) {
objectInfo: navNodeInfo(nodePath: $navNodeId) {
object {
features
properties(filter: $filter) {
id
category
dataType
description
displayName
features
value
}
}
}
}
`;
export const NavNodeChildrenDocument = `
query navNodeChildren($parentPath: ID!) {
navNodeChildren(parentPath: $parentPath) {
@@ -1298,44 +1333,6 @@ export const NavRefreshNodeDocument = `
navRefreshNode(nodePath: $nodePath)
}
`;
export const QueryChildrenDatabaseObjectInfoDocument = `
query queryChildrenDatabaseObjectInfo($nodePath: ID!, $filter: ObjectPropertyFilter) {
childrenDatabaseObjectInfo: navNodeChildren(parentPath: $nodePath) {
id
object {
features
properties(filter: $filter) {
id
category
dataType
description
displayName
features
value
}
}
}
}
`;
export const QueryDatabaseObjectInfoDocument = `
query queryDatabaseObjectInfo($nodeId: ID!, $filter: ObjectPropertyFilter) {
objectInfo: navNodeInfo(nodePath: $nodeId) {
id
object {
features
properties(filter: $filter) {
id
category
dataType
description
displayName
features
value
}
}
}
}
`;
export const ReadSessionLogDocument = `
query readSessionLog($maxEntries: Int!, $clearEntries: Boolean!) {
log: readSessionLog(maxEntries: $maxEntries, clearEntries: $clearEntries) {
@@ -1841,6 +1838,12 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
testConnection(variables: TestConnectionMutationVariables): Promise<TestConnectionMutation> {
return withWrapper(() => client.request<TestConnectionMutation>(TestConnectionDocument, variables));
},
getChildrenDBObjectInfo(variables: GetChildrenDbObjectInfoQueryVariables): Promise<GetChildrenDbObjectInfoQuery> {
return withWrapper(() => client.request<GetChildrenDbObjectInfoQuery>(GetChildrenDbObjectInfoDocument, variables));
},
getDBObjectInfo(variables: GetDbObjectInfoQueryVariables): Promise<GetDbObjectInfoQuery> {
return withWrapper(() => client.request<GetDbObjectInfoQuery>(GetDbObjectInfoDocument, variables));
},
navNodeChildren(variables: NavNodeChildrenQueryVariables): Promise<NavNodeChildrenQuery> {
return withWrapper(() => client.request<NavNodeChildrenQuery>(NavNodeChildrenDocument, variables));
},
@@ -1850,12 +1853,6 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
navRefreshNode(variables: NavRefreshNodeQueryVariables): Promise<NavRefreshNodeQuery> {
return withWrapper(() => client.request<NavRefreshNodeQuery>(NavRefreshNodeDocument, variables));
},
queryChildrenDatabaseObjectInfo(variables: QueryChildrenDatabaseObjectInfoQueryVariables): Promise<QueryChildrenDatabaseObjectInfoQuery> {
return withWrapper(() => client.request<QueryChildrenDatabaseObjectInfoQuery>(QueryChildrenDatabaseObjectInfoDocument, variables));
},
queryDatabaseObjectInfo(variables: QueryDatabaseObjectInfoQueryVariables): Promise<QueryDatabaseObjectInfoQuery> {
return withWrapper(() => client.request<QueryDatabaseObjectInfoQuery>(QueryDatabaseObjectInfoDocument, variables));
},
readSessionLog(variables: ReadSessionLogQueryVariables): Promise<ReadSessionLogQuery> {
return withWrapper(() => client.request<ReadSessionLogQuery>(ReadSessionLogDocument, variables));
},
@@ -1963,7 +1960,7 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
},
touchSession(variables?: TouchSessionMutationVariables): Promise<TouchSessionMutation> {
return withWrapper(() => client.request<TouchSessionMutation>(TouchSessionDocument, variables));
}
},
};
}
export type Sdk = ReturnType<typeof getSdk>;
export type Sdk = ReturnType<typeof getSdk>;
@@ -7,7 +7,7 @@
*/
import {
NavigationTreeContextMenuService, NodeManagerUtils, NodeWithParent, EObjectFeature
NavigationTreeContextMenuService, NodeManagerUtils, NavNode, EObjectFeature
} from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import {
@@ -39,13 +39,13 @@ export class DataExportMenuService {
};
this.tableFooterMenuService.registerMenuItem(exportData);
this.contextMenuService.addMenuItem<NodeWithParent>(
this.contextMenuService.addMenuItem<NavNode>(
this.contextMenuService.getRootMenuToken(),
{
id: 'export',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& !!context.data.object?.features?.includes(EObjectFeature.dataContainer);
&& context.data.objectFeatures.includes(EObjectFeature.dataContainer);
},
order: 2,
title: 'Export',
@@ -8,7 +8,7 @@
import { observable, computed } from 'mobx';
import { ErrorDetailsDialog, NodesManagerService } from '@dbeaver/core/app';
import { ErrorDetailsDialog, NavNodeManagerService } from '@dbeaver/core/app';
import { IInitializableController, injectable } from '@dbeaver/core/di';
import { CommonDialogService } from '@dbeaver/core/dialogs';
import { INotification } from '@dbeaver/core/eventsLog';
@@ -46,7 +46,7 @@ export class ExportNotificationController implements IInitializableController {
return '';
}
if (this.task.context.containerNodePath) {
const node = this.nodesManagerService.getNode(this.task.context.containerNodePath);
const node = this.navNodeManagerService.getNode(this.task.context.containerNodePath);
return `${this.localization.translate('data_transfer_exporting_table')} ${node?.name}`;
}
@@ -76,7 +76,7 @@ export class ExportNotificationController implements IInitializableController {
constructor(
private commonDialogService: CommonDialogService,
private dataExportProcessService: DataExportProcessService,
private nodesManagerService: NodesManagerService,
private navNodeManagerService: NavNodeManagerService,
private localization: LocalizationService,
) {}
@@ -9,7 +9,7 @@
import { observer } from 'mobx-react';
import styled from 'reshadow';
import { NodesManagerService } from '@dbeaver/core/app';
import { NavNodeManagerService } from '@dbeaver/core/app';
import { TabIcon, Tab, TabTitle } from '@dbeaver/core/blocks';
import { useService } from '@dbeaver/core/di';
import { useStyles } from '@dbeaver/core/theming';
@@ -18,9 +18,9 @@ import { ObjectPageTabProps } from '@dbeaver/object-viewer-plugin';
export const DataViewerTab = observer(function DataViewerTab({
tab, page, onSelect, style,
}: ObjectPageTabProps) {
const nodesManagerService = useService(NodesManagerService);
const navNodeManagerService = useService(NavNodeManagerService);
if (!nodesManagerService.isNodeHasData(tab.handlerState.objectId)) {
if (!navNodeManagerService.isNodeHasData(tab.handlerState.objectId)) {
return null;
}
@@ -7,7 +7,7 @@
*/
import {
NodesManagerService,
NavNodeManagerService,
INodeNavigationData,
IContextProvider,
ITab,
@@ -29,11 +29,13 @@ import { DataViewerTableService } from './DataViewerTableService';
export class DataViewerTabService {
page: ObjectPage;
constructor(private nodesManagerService: NodesManagerService,
private dataViewerTableService: DataViewerTableService,
private objectViewerTabService: ObjectViewerTabService,
private dbObjectPageService: DBObjectPageService,
private notificationService: NotificationService) {
constructor(
private navNodeManagerService: NavNodeManagerService,
private dataViewerTableService: DataViewerTableService,
private objectViewerTabService: ObjectViewerTabService,
private dbObjectPageService: DBObjectPageService,
private notificationService: NotificationService
) {
this.page = this.dbObjectPageService.register({
key: dataViewerHandlerKey,
@@ -49,7 +51,7 @@ export class DataViewerTabService {
}
registerTabHandler() {
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
}
private async navigationHandler(contexts: IContextProvider<INodeNavigationData>) {
@@ -60,14 +62,12 @@ export class DataViewerTabService {
trySwitchPage,
} = await contexts.getContext(this.objectViewerTabService.objectViewerTabContext);
if (nodeInfo.type === NavigationType.closeConnection) {
return;
}
// const tabInfo = await contexts.getContext(this.navigationTabsService.navigationTabContext);
const objectInfo = await this.nodesManagerService.loadDatabaseObjectInfo(nodeInfo.nodeId);
const node = await this.navNodeManagerService.loadNode(nodeInfo);
if (!this.nodesManagerService.isNodeHasData(objectInfo)) {
if (!this.navNodeManagerService.isNodeHasData(node)) {
return;
}
@@ -83,9 +83,12 @@ export class DataViewerTabService {
}
private async handleTabSelect(tab: ITab<IObjectViewerTabState>) {
const objectInfo = await this.nodesManagerService.loadDatabaseObjectInfo(tab.handlerState.objectId);
const node = await this.navNodeManagerService.loadNode({
nodeId: tab.handlerState.objectId,
parentId: tab.handlerState.parentId,
});
if (!this.nodesManagerService.isNodeHasData(objectInfo)) {
if (!this.navNodeManagerService.isNodeHasData(node)) {
return;
}
@@ -93,7 +96,7 @@ export class DataViewerTabService {
return;
}
const nodeInfo = this.nodesManagerService
const nodeInfo = this.navNodeManagerService
.getNodeContainerInfo(tab.handlerState.objectId);
if (!nodeInfo.connectionId) {
@@ -111,11 +114,12 @@ export class DataViewerTabService {
// if (!this.nodesManagerService.isNodeHasData(tab.handlerState.objectId)) {
// return;
// }
const info = await this.nodesManagerService.loadDatabaseObjectInfo(tab.handlerState.objectId);
if (info) {
return true;
}
return false;
await this.navNodeManagerService.loadNode({
nodeId: tab.handlerState.objectId,
parentId: tab.handlerState.parentId,
});
// await this.dbObjectService.load(tab.handlerState.objectId);
return true;
}
private handleTabClose(tab: ITab<IObjectViewerTabState>) {
@@ -8,7 +8,7 @@
import { observable, when } from 'mobx';
import { NodeManagerUtils, NodesManagerService } from '@dbeaver/core/app';
import { NodeManagerUtils, NavNodeManagerService } from '@dbeaver/core/app';
import { IDestructibleController, IInitializableController, injectable } from '@dbeaver/core/di';
import { NotificationService } from '@dbeaver/core/eventsLog';
import { SqlDialectInfo } from '@dbeaver/core/sdk';
@@ -25,11 +25,12 @@ export class DdlViewerController implements IInitializableController, IDestructi
private nodeId!: string;
constructor(private ddlViewerService: DdlViewerService,
private nodesManagerService: NodesManagerService,
private sqlDialectInfoService: SqlDialectInfoService,
private notificationService: NotificationService) {
}
constructor(
private ddlViewerService: DdlViewerService,
private navNodeManagerService: NavNodeManagerService,
private sqlDialectInfoService: SqlDialectInfoService,
private notificationService: NotificationService
) { }
init(nodeId: string) {
this.nodeId = nodeId;
@@ -55,7 +56,7 @@ export class DdlViewerController implements IInitializableController, IDestructi
}
private async loadDialect(nodeId: string): Promise<void> {
const { connectionId } = this.nodesManagerService.getNodeContainerInfo(nodeId);
const { connectionId } = this.navNodeManagerService.getNodeContainerInfo(nodeId);
if (!connectionId) {
return;
}
@@ -7,7 +7,7 @@
*/
import {
EObjectFeature, NodesManagerService, IContextProvider, INodeNavigationData
EObjectFeature, NavNodeManagerService, IContextProvider, INodeNavigationData
} from '@dbeaver/core/app';
import { ITab } from '@dbeaver/core/blocks';
import { injectable } from '@dbeaver/core/di';
@@ -22,16 +22,18 @@ const ddlViewerTabId = 'ddl-viewer';
@injectable()
export class DdlViewerTabService {
constructor(private nodesManagerService: NodesManagerService,
private objectViewerTabService: ObjectViewerTabService,
private notificationService: NotificationService,
private ddlViewerService: DdlViewerService) {
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
constructor(
private navNodeManagerService: NavNodeManagerService,
private objectViewerTabService: ObjectViewerTabService,
private notificationService: NotificationService,
private ddlViewerService: DdlViewerService
) {
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
}
buildTab(nodeId: string): ITab | null {
const node = this.nodesManagerService.getNode(nodeId);
const isDdlRequired = node?.object?.features?.includes(EObjectFeature.script);
const node = this.navNodeManagerService.getNode(nodeId);
const isDdlRequired = node?.objectFeatures.includes(EObjectFeature.script);
if (!isDdlRequired) {
return null;
}
@@ -39,7 +41,7 @@ export class DdlViewerTabService {
tabId: ddlViewerTabId,
title: 'DDL',
icon: 'sql-text',
onActivate: () => this.activateDDLTab(nodeId),
onActivate: () => this.activateDDLTab(nodeId, node?.parentId!),
panel: () => ddlViewer(nodeId),
};
return ddlTab;
@@ -57,7 +59,7 @@ export class DdlViewerTabService {
}
}
private activateDDLTab(nodeId: string) {
this.nodesManagerService.navToNode(nodeId, ddlViewerTabId);
private activateDDLTab(nodeId: string, parentId: string) {
this.navNodeManagerService.navToNode(nodeId, parentId, ddlViewerTabId);
}
}
@@ -8,6 +8,8 @@
export interface IObjectViewerTabState {
objectId: string;
parentId: string;
parents: string[];
folderId: string;
tabIcon?: string;
tabTitle?: string;
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { NodesManagerService, TabEntity } from '@dbeaver/core/app';
import { NavNodeManagerService, TabEntity } from '@dbeaver/core/app';
import { ITab } from '@dbeaver/core/blocks';
import { injectable } from '@dbeaver/core/di';
@@ -15,7 +15,7 @@ import { ObjectFolderTabModel } from './ObjectFolderTabModel';
@injectable()
export class ObjectFoldersTabService {
constructor(private nodesManagerService: NodesManagerService) {
constructor(private navNodeManagerService: NavNodeManagerService) {
}
createTabEntities(nodeId: string): TabEntity[] {
@@ -24,22 +24,22 @@ export class ObjectFoldersTabService {
private tabBuilder(nodeId: string): ITab[] {
const children = this.nodesManagerService.getChildren(nodeId)?.children || [];
const children = this.navNodeManagerService.getTree(nodeId) || [];
const folderTabs = children.filter((nodeId) => {
const node = this.nodesManagerService.getNode(nodeId);
const node = this.navNodeManagerService.getNode(nodeId);
return node ? node.folder : false;
});
const tabList: ITab[] = folderTabs.map((nodeId) => {
const node = this.nodesManagerService.getNode(nodeId);
const node = this.navNodeManagerService.getNode(nodeId);
if (!node) {
throw Error(`node not found: ${nodeId}`);
}
return new ObjectFolderTabModel(
node,
() => this.nodesManagerService.navToNode(node.id)
() => this.navNodeManagerService.navToNode(node.id, node.parentId)
);
});
return tabList;
@@ -35,9 +35,9 @@ const viewerStyles = composes(
export const ObjectPropertiesPagePanel = observer(function ObjectPropertiesPagePanel({
tab,
}: ObjectPagePanelProps) {
const children = useChildren(tab.handlerState.objectId);
const { children, isLoading } = useChildren(tab.handlerState.objectId);
if (!children?.isLoaded) {
if (!children || isLoading) {
return <Loader />;
}
@@ -7,7 +7,7 @@
*/
import {
NodesManagerService, ITab, IContextProvider, INodeNavigationData
NavNodeManagerService, ITab, IContextProvider, INodeNavigationData
} from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import { NotificationService } from '@dbeaver/core/eventsLog';
@@ -24,7 +24,7 @@ export class ObjectPropertiesPageService {
page?: ObjectPage;
constructor(
private nodesManagerService: NodesManagerService,
private navNodeManagerService: NavNodeManagerService,
private notificationService: NotificationService,
private objectViewerTabService: ObjectViewerTabService,
private dbObjectPageService: DBObjectPageService
@@ -41,7 +41,7 @@ export class ObjectPropertiesPageService {
getPanelComponent: () => ObjectPropertiesPagePanel,
onSelect: this.handlePageSelect.bind(this),
});
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
}
private async handlePageSelect(tab: ITab<IObjectViewerTabState>) {
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react';
import { useCallback } from 'react';
import styled, { css } from 'reshadow';
import { NodesManagerService, useDatabaseObjectInfo, useNode } from '@dbeaver/core/app';
import { NavNodeManagerService, useDatabaseObjectInfo, useNode } from '@dbeaver/core/app';
import {
StaticImage, TableItem, TableColumnValue, TableItemSelect
} from '@dbeaver/core/blocks';
@@ -51,12 +51,12 @@ type ItemProps = {
export const Item = observer(function Item({
objectId, columns,
}: ItemProps) {
const nodesManagerService = useService(NodesManagerService);
const object = useNode(objectId);
const navNodeManagerService = useService(NavNodeManagerService);
const { node } = useNode(objectId);
const databaseObjectInfo = useDatabaseObjectInfo(objectId);
const handleOpen = useCallback(() => nodesManagerService.navToNode(objectId), [objectId]);
const handleOpen = useCallback(() => navNodeManagerService.navToNode(node!.id, node!.parentId), [node]);
if (!object || !databaseObjectInfo?.properties) {
if (!node || !databaseObjectInfo?.properties) {
return styled(useStyles(itemStyles))(
<TableItem item={objectId} onDoubleClick={handleOpen}>
<TableColumnValue centerContent><TableItemSelect /></TableColumnValue>
@@ -81,7 +81,7 @@ export const Item = observer(function Item({
<TableColumnValue centerContent><TableItemSelect /></TableColumnValue>
<TableColumnValue>
<icon as="div">
<StaticImage icon={object.icon} />
<StaticImage icon={node.icon} />
</icon>
</TableColumnValue>
{databaseObjectInfo.properties.map(property => (
@@ -22,15 +22,18 @@ const styles = css`
`;
type ObjectPropertyTableProps = {
parentId: string;
objectId: string;
parentId: string;
}
export const ObjectPropertyTable = observer(function ObjectPropertyTable(props: ObjectPropertyTableProps) {
const children = useChildren(props.objectId);
const { isLoading } = useObjectFolder(props.parentId, props.objectId);
export const ObjectPropertyTable = observer(function ObjectPropertyTable({
objectId,
parentId,
}: ObjectPropertyTableProps) {
const children = useChildren(objectId);
const { isLoading } = useObjectFolder(parentId);
if (!children?.isLoaded || isLoading) {
if (!children.children || children.isLoading || isLoading) {
return <Loader />;
}
@@ -8,7 +8,7 @@
import { observable } from 'mobx';
import { NodesManagerService } from '@dbeaver/core/app';
import { DBObjectService } from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import { VirtualFolderTabData } from './VirtualFolderTabData';
@@ -18,9 +18,10 @@ export class VirtualFolderTabMixin {
@observable isActivated = false;
constructor(private virtualFolderTabData: VirtualFolderTabData,
private nodesManagerService: NodesManagerService) {
}
constructor(
private virtualFolderTabData: VirtualFolderTabData,
private dbObjectService: DBObjectService
) {}
getChildrenId() {
return this.virtualFolderTabData.childrenIds;
@@ -37,7 +38,7 @@ export class VirtualFolderTabMixin {
try {
for (const nodeId of this.getChildrenId()) {
await this.nodesManagerService.loadDatabaseObjectInfo(nodeId);
await this.dbObjectService.load(nodeId);
}
}
finally {
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { NodesManagerService, NodeWithParent, TabEntity } from '@dbeaver/core/app';
import { NavNodeManagerService, NavNode, TabEntity } from '@dbeaver/core/app';
import { injectable, MixinProvider } from '@dbeaver/core/di';
import { VirtualFolderTabData } from './VirtualFolderTabData';
@@ -17,15 +17,14 @@ import { VirtualFolderTabModel } from './VirtualFolderTabModel';
@injectable()
export class VirtualFolderTabService {
constructor(private nodesManagerService: NodesManagerService) {
}
constructor(private navNodeManagerService: NavNodeManagerService) { }
createTabEntities(nodeId: string): TabEntity[] {
const children = this.nodesManagerService.getChildren(nodeId)?.children || [];
const children = this.navNodeManagerService.getTree(nodeId) || [];
const notFolderNodes = children.reduce<NodeWithParent[]>(
const notFolderNodes = children.reduce<NavNode[]>(
(nodes, nodeId) => {
const node = this.nodesManagerService.getNode(nodeId);
const node = this.navNodeManagerService.getNode(nodeId);
if (node && !node.folder) {
nodes.push(node);
}
@@ -39,11 +39,11 @@ const stylesArray = [styles];
export const ObjectViewerPanel = observer(function ObjectViewerPanel({
tab, handler,
}: TabHandlerPanelProps<IObjectViewerTabState>) {
const children = useChildren(tab.handlerState.objectId);
const { children, isLoading } = useChildren(tab.handlerState.objectId);
const dbObjectPagesService = useService(DBObjectPageService);
const pages = dbObjectPagesService.orderedPages;
if (!children?.isLoaded) {
if (!children || isLoading) {
return <Loader />;
}
@@ -18,7 +18,7 @@ import { IObjectViewerTabState } from './IObjectViewerTabState';
export const ObjectViewerTab = observer(function ObjectViewerTab({
tab, handler, onSelect, onClose, style,
}: TabHandlerTabProps<IObjectViewerTabState>) {
const node = useNode(tab.handlerState.objectId);
const { node } = useNode(tab.handlerState.objectId);
return styled(useStyles(...style))(
<Tab tabId={tab.id} onOpen={onSelect} onClose={onClose} >
@@ -6,10 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { observable } from 'mobx';
import {
NodesManagerService,
NavigationTabsService,
INodeNavigationData,
IContextProvider,
@@ -21,6 +18,9 @@ import {
objectCatalogProvider,
objectSchemaProvider,
NavNodeManagerService,
DBObjectService,
ROOT_NODE_PATH,
NavNode,
} from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import { NotificationService } from '@dbeaver/core/eventsLog';
@@ -35,13 +35,15 @@ import { objectViewerTabHandlerKey } from './objectViewerTabHandlerKey';
@injectable()
export class ObjectViewerTabService {
@observable private propertiesTabLoadingState: Map<string, boolean> = new Map();
readonly tabHandler: TabHandler<IObjectViewerTabState>
constructor(private nodesManagerService: NodesManagerService,
private dbObjectPageService: DBObjectPageService,
private notificationService: NotificationService,
private navigationTabsService: NavigationTabsService) {
constructor(
private navNodeManagerService: NavNodeManagerService,
private dbObjectService: DBObjectService,
private dbObjectPageService: DBObjectPageService,
private notificationService: NotificationService,
private navigationTabsService: NavigationTabsService
) {
this.tabHandler = this.navigationTabsService
.registerTabHandler<IObjectViewerTabState>({
@@ -60,95 +62,9 @@ export class ObjectViewerTabService {
});
}
getConnection(context: ITab<IObjectViewerTabState>) {
const nodeInfo = this.nodesManagerService
.getNodeContainerInfo(context.handlerState.objectId);
if (!nodeInfo.connectionId) {
return;
}
// connection node id differs from connection id
return NodeManagerUtils.connectionNodeIdToConnectionId(nodeInfo.connectionId);
}
getDBObjectCatalog(context: ITab<IObjectViewerTabState>) {
const nodeInfo = this.nodesManagerService
.getNodeContainerInfo(context.handlerState.objectId);
if (nodeInfo.catalogId) {
return;
}
return nodeInfo.catalogId;
}
getDBObjectSchema(context: ITab<IObjectViewerTabState>) {
const nodeInfo = this.nodesManagerService
.getNodeContainerInfo(context.handlerState.objectId);
if (!nodeInfo.schemaId) {
return;
}
return nodeInfo.schemaId;
}
registerTabHandler() {
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
}
isTabLoading(id: string): boolean {
return !this.propertiesTabLoadingState.get(id);
}
async selectObjectTab(tab: ITab<IObjectViewerTabState>) {
try {
const currentPage = this.dbObjectPageService.getPage(tab.handlerState.pageId);
if (currentPage) {
await this.dbObjectPageService.selectPage(tab, currentPage);
}
// TODO: must be loaded by info folder?
await this.nodesManagerService.loadDatabaseObjectInfo(tab.handlerState.objectId);
await this.nodesManagerService.loadNodeInfo(tab.handlerState.objectId);
const children = await this.nodesManagerService.loadChildren(tab.handlerState.objectId);
const folderId = tab.handlerState.folderId;
const propertiesTabLoadingKey = `${tab.handlerState.objectId}_${folderId}`;
try {
if (children.length === 0 || !NodeManagerUtils.isDatabaseObject(folderId)) {
return;
}
this.propertiesTabLoadingState.set(propertiesTabLoadingKey, false);
await this.nodesManagerService.loadChildrenDatabaseObjectInfo(folderId);
} finally {
this.propertiesTabLoadingState.set(propertiesTabLoadingKey, true);
}
} catch (exception) {
this.notificationService.logException(exception, 'Error in Object Viewer while tab selecting');
}
}
async restoreObjectTab(tab: ITab<IObjectViewerTabState>) {
if (
typeof tab.handlerState?.folderId === 'string'
&& typeof tab.handlerState?.objectId === 'string'
&& (!tab.handlerState.tabIcon || typeof tab.handlerState.tabIcon === 'string')
&& (!tab.handlerState.tabTitle || typeof tab.handlerState.tabTitle === 'string')
) {
const node = await this.nodesManagerService.loadNodeInfo(tab.handlerState.objectId);
if (node) {
tab.handlerState.tabIcon = node.icon;
tab.handlerState.tabTitle = node.name;
return this.dbObjectPageService.restorePages(tab);
}
}
return false;
}
async closeObjectTab(tab: ITab<IObjectViewerTabState>) {
await this.dbObjectPageService.closePages(tab);
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
this.navNodeManagerService.navNode.onDataUpdate.subscribe(this.updateTabs.bind(this));
}
objectViewerTabContext = async (
@@ -156,7 +72,7 @@ export class ObjectViewerTabService {
data: INodeNavigationData
): Promise<IObjectViewerTabContext> => {
const tabInfo = await contexts.getContext(this.navigationTabsService.navigationTabContext);
const nodeInfo = await contexts.getContext(this.nodesManagerService.navigationNodeContext);
const nodeInfo = await contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
if (data.type !== NavigationType.closeConnection) {
// check if tab already exist for object
@@ -173,6 +89,8 @@ export class ObjectViewerTabService {
handlerId: objectViewerTabHandlerKey,
handlerState: {
objectId: nodeInfo.nodeId,
parentId: nodeInfo.parentId,
parents: await nodeInfo.getParents(),
folderId: nodeInfo.folderId,
pageId: '',
pagesState: new Map(),
@@ -214,6 +132,115 @@ export class ObjectViewerTabService {
};
}
private async updateTabs(data: Map<string, NavNode>) {
for (const tab of this.navigationTabsService.findTabs(isObjectViewerTab(tab => tab.restored))) {
if (!data.has(tab.handlerState.objectId)) {
await this.navigationTabsService.closeTab(tab.id, true);
} else if (tab.id === this.navigationTabsService.currentTabId) {
const loaded = this.navNodeManagerService.navNode.isLoaded({
nodes: [{ nodeId: tab.handlerState.objectId, parentId: tab.handlerState.parentId }],
});
const loading = this.navNodeManagerService.navNode.isDataLoading({
nodes: [{ nodeId: tab.handlerState.objectId, parentId: tab.handlerState.parentId }],
});
if (!loaded && !loading) {
await this.navigationTabsService.selectTab(tab.id);
}
}
}
}
private getConnection(context: ITab<IObjectViewerTabState>) {
const nodeInfo = this.navNodeManagerService
.getNodeContainerInfo(context.handlerState.objectId);
if (!nodeInfo.connectionId) {
return;
}
// connection node id differs from connection id
return NodeManagerUtils.connectionNodeIdToConnectionId(nodeInfo.connectionId);
}
private getDBObjectCatalog(context: ITab<IObjectViewerTabState>) {
const nodeInfo = this.navNodeManagerService
.getNodeContainerInfo(context.handlerState.objectId);
if (nodeInfo.catalogId) {
return;
}
return nodeInfo.catalogId;
}
private getDBObjectSchema(context: ITab<IObjectViewerTabState>) {
const nodeInfo = this.navNodeManagerService
.getNodeContainerInfo(context.handlerState.objectId);
if (!nodeInfo.schemaId) {
return;
}
return nodeInfo.schemaId;
}
private async selectObjectTab(tab: ITab<IObjectViewerTabState>) {
try {
const currentPage = this.dbObjectPageService.getPage(tab.handlerState.pageId);
if (currentPage) {
await this.dbObjectPageService.selectPage(tab, currentPage);
}
// TODO: must be loaded by info folder?
await this.navNodeManagerService.loadNode({
nodeId: tab.handlerState.objectId,
parentId: tab.handlerState.parentId,
});
const children = await this.navNodeManagerService.loadTree(tab.handlerState.objectId);
await this.dbObjectService.load(tab.handlerState.objectId);
const folderId = tab.handlerState.folderId;
if (children.length === 0 || !NodeManagerUtils.isDatabaseObject(folderId)) {
return;
}
const folderChildren = await this.navNodeManagerService.loadTree(folderId);
await this.dbObjectService.load(folderChildren);
} catch (exception) {
this.notificationService.logException(exception, 'Error in Object Viewer while tab selecting');
}
}
private async restoreObjectTab(tab: ITab<IObjectViewerTabState>) {
if (
typeof tab.handlerState?.folderId === 'string'
&& typeof tab.handlerState.parentId === 'string'
&& Array.isArray(tab.handlerState.parents)
&& typeof tab.handlerState.objectId === 'string'
&& (!tab.handlerState.tabIcon || typeof tab.handlerState.tabIcon === 'string')
&& (!tab.handlerState.tabTitle || typeof tab.handlerState.tabTitle === 'string')
) {
for (const nodeId of tab.handlerState.parents) {
await this.navNodeManagerService.loadTree(nodeId);
}
const node = await this.navNodeManagerService.loadNode({
nodeId: tab.handlerState.objectId,
parentId: tab.handlerState.parentId,
});
if (node) {
tab.handlerState.tabIcon = node.icon;
tab.handlerState.tabTitle = node.name;
return this.dbObjectPageService.restorePages(tab);
}
}
return false;
}
private async closeObjectTab(tab: ITab<IObjectViewerTabState>) {
await this.dbObjectPageService.closePages(tab);
}
private async navigationHandler(contexts: IContextProvider<INodeNavigationData>) {
try {
const { tab, tabInfo, nodeInfo } = await contexts.getContext(this.objectViewerTabContext);
@@ -6,14 +6,18 @@
* you may not use this file except in compliance with the License.
*/
import { NavNodeManagerService, DBObjectService } from '@dbeaver/core/app';
import { useService } from '@dbeaver/core/di';
import { ObjectViewerTabService } from './ObjectViewerTabService';
export function useObjectFolder(objectId: string, folderId: string) {
const objectViewerTabService = useService(ObjectViewerTabService);
export function useObjectFolder(objectId: string) {
const navNodeManagerService = useService(NavNodeManagerService);
const dbObjectService = useService(DBObjectService);
const isLoading = objectViewerTabService.isTabLoading(`${objectId}_${folderId}`);
const children = navNodeManagerService.getTree(objectId) || [];
const isLoading = children.some(navNodeId => !dbObjectService.getDBObject(navNodeId))
&& dbObjectService.dbObject.isDataLoading({ navNodeId: children });
return { isLoading };
}
@@ -11,7 +11,7 @@ import {
NavigationTreeContextMenuService,
EObjectFeature,
NodeManagerUtils,
NodeWithParent, ConnectionsManagerService,
NavNode, ConnectionsManagerService,
} from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import { ContextMenuService, IContextMenuItem, IMenuContext } from '@dbeaver/core/dialogs';
@@ -21,11 +21,13 @@ import { SqlEditorTabService } from './SqlEditorTabService';
@injectable()
export class SqlEditorBootstrap {
constructor(private mainMenuService: MainMenuService,
private contextMenuService: ContextMenuService,
private connectionsManagerService: ConnectionsManagerService,
private sqlEditorTabService: SqlEditorTabService,
private sqlEditorNavigatorService: SqlEditorNavigatorService) {}
constructor(
private mainMenuService: MainMenuService,
private contextMenuService: ContextMenuService,
private connectionsManagerService: ConnectionsManagerService,
private sqlEditorTabService: SqlEditorTabService,
private sqlEditorNavigatorService: SqlEditorNavigatorService
) {}
async bootstrap() {
this.sqlEditorTabService.registerTabHandler();
@@ -40,20 +42,20 @@ export class SqlEditorBootstrap {
}
);
const openSqlEditor: IContextMenuItem<NodeWithParent> = {
const openSqlEditor: IContextMenuItem<NavNode> = {
id: 'open-sql-editor',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& Boolean((context.data as NodeWithParent)?.object?.features?.includes(EObjectFeature.dataSource));
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
},
title: 'SQL',
order: 2,
onClick: (context: IMenuContext<NodeWithParent>) => {
onClick: (context: IMenuContext<NavNode>) => {
const node = context.data;
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(node.id);
this.sqlEditorNavigatorService.openNewEditor(connectionId);
},
};
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), openSqlEditor);
this.contextMenuService.addMenuItem<NavNode>(this.contextMenuService.getRootMenuToken(), openSqlEditor);
}
}
@@ -14,7 +14,7 @@ import {
NavigationService,
IContextProvider,
ITabOptions,
NodesManagerService,
NavNodeManagerService,
INodeNavigationData,
NavigationType,
} from '@dbeaver/core/app';
@@ -57,19 +57,21 @@ export interface SQLEditorAction extends SQLEditorActionContext {
export class SqlEditorNavigatorService {
private readonly navigator: INavigator<SQLCreateAction | SQLEditorAction>;
constructor(private navigationTabsService: NavigationTabsService,
private connectionsManagerService: ConnectionsManagerService,
private notificationService: NotificationService,
private gql: GraphQLService,
private sqlDialectInfoService: SqlDialectInfoService,
private nodesManagerService: NodesManagerService,
private navigationService: NavigationService) {
constructor(
private navigationTabsService: NavigationTabsService,
private connectionsManagerService: ConnectionsManagerService,
private notificationService: NotificationService,
private gql: GraphQLService,
private sqlDialectInfoService: SqlDialectInfoService,
private navNodeManagerService: NavNodeManagerService,
private navigationService: NavigationService
) {
this.navigator = this.navigationService.createNavigator<SQLCreateAction | SQLEditorAction>(
null,
this.navigateHandler.bind(this)
);
this.nodesManagerService.navigator.addHandler(this.nodeNavigationHandler.bind(this));
this.navNodeManagerService.navigator.addHandler(this.nodeNavigationHandler.bind(this));
}
registerTabHandler() {
@@ -100,7 +102,7 @@ export class SqlEditorNavigatorService {
private async nodeNavigationHandler(contexts: IContextProvider<INodeNavigationData>) {
try {
const nodeInfo = await contexts.getContext(this.nodesManagerService.navigationNodeContext);
const nodeInfo = await contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
if (nodeInfo.type === NavigationType.closeConnection) {
for (const tab of this.navigationTabsService.findTabs(