Merge pull request #83 from dbeaver/feat-data-refresh

fix(core): connection info loading behavior
This commit is contained in:
Serge Rider
2020-06-14 15:19:00 +03:00
committed by GitHub
15 changed files with 202 additions and 31 deletions
@@ -10,9 +10,12 @@ import { useState, useCallback, useEffect } from 'react';
import { useService } from '@dbeaver/core/di';
import { EConnectionFeature } from '../../shared/ConnectionsManager/EConnectionFeature';
import { useConnectionInfo } from '../../shared/ConnectionsManager/useConnectionInfo';
import { NavNode } from '../../shared/NodesManager/EntityTypes';
import { EObjectFeature } from '../../shared/NodesManager/EObjectFeature';
import { NavNodeManagerService } from '../../shared/NodesManager/NavNodeManagerService';
import { NodeManagerUtils } from '../../shared/NodesManager/NodeManagerUtils';
import { useNode } from '../../shared/NodesManager/useNode';
import { useChildren } from '../../shared/useChildren';
import { NavigationTreeService } from '../NavigationTreeService';
@@ -32,11 +35,15 @@ export function useNavigationTree(nodeId: string, parentId: string) {
const isLoaded = children.isLoaded;
const isExpandable = isExpandableFilter(node) && (!isLoaded || children.children!.length > 0);
const isExpandedFiltered = isExpanded
&& (
!node.objectFeatures.includes(EObjectFeature.dataSource)
|| node.objectFeatures.includes(EObjectFeature.dataSourceConnected)
);
let isExpandedFiltered = isExpanded;
if (node.objectFeatures.includes(EObjectFeature.dataSource)) {
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(nodeId);
const { connectionInfo } = useConnectionInfo(connectionId);
if (!connectionInfo?.features.includes(EConnectionFeature.connected)) {
isExpandedFiltered = false;
}
}
const handleDoubleClick = useCallback(
() => navNodeManagerService.navToNode(nodeId, parentId),
@@ -12,7 +12,10 @@ import { Subject, Observable } from 'rxjs';
import { injectable } from '@dbeaver/core/di';
import { NotificationService } from '@dbeaver/core/eventsLog';
import { ConnectionsManagerService } from '../shared/ConnectionsManager/ConnectionsManagerService';
import { EObjectFeature } from '../shared/NodesManager/EObjectFeature';
import { NavNodeManagerService, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
import { NodeManagerUtils } from '../shared/NodesManager/NodeManagerUtils';
@injectable()
export class NavigationTreeService {
@@ -22,8 +25,9 @@ export class NavigationTreeService {
private nodeSelectSubject: Subject<[string, boolean]>;
constructor(
private NavNodeManagerService: NavNodeManagerService,
private notificationService: NotificationService
private navNodeManagerService: NavNodeManagerService,
private notificationService: NotificationService,
private connectionsManagerService: ConnectionsManagerService
) {
this.nodeSelectSubject = new Subject();
this.onNodeSelect = this.nodeSelectSubject.asObservable();
@@ -31,7 +35,14 @@ export class NavigationTreeService {
async loadNestedNodes(id = ROOT_NODE_PATH) {
try {
await this.NavNodeManagerService.loadTree(id);
await this.navNodeManagerService.loadTree(id);
const node = this.navNodeManagerService.getNode(id);
if (node?.objectFeatures.includes(EObjectFeature.dataSource)) {
await this.connectionsManagerService.refreshConnectionInfoAsync(
NodeManagerUtils.connectionNodeIdToConnectionId(id)
);
}
return true;
} catch (exception) {
this.notificationService.logException(exception, `Can't load tree node: ${id}`);
@@ -177,6 +177,12 @@ export class ConnectionSchemaManagerService {
return;
}
try {
await this.connectionsManagerService.loadConnectionInfoAsync(connectionId);
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load connection info');
}
try {
await this.connectionsManagerService.dbDrivers.load();
} catch (exception) {
+2
View File
@@ -37,9 +37,11 @@ export * from './shared/TabEntity/TabEntity';
// Enums
export * from './shared/NodesManager/ENodeFeature';
export * from './shared/NodesManager/EObjectFeature';
export * from './shared/ConnectionsManager/EConnectionFeature';
// hooks
export * from './shared/useChildren';
export * from './shared/ConnectionsManager/useConnectionInfo';
export * from './shared/NavigationTabs/NavigationTabsBar/Tabs/useTab';
export * from './shared/NodesManager/useDatabaseObjectInfo';
export * from './shared/NodesManager/useNode';
@@ -20,9 +20,11 @@ import {
CachedResource,
DatabaseObjectInfo,
} from '@dbeaver/core/sdk';
import { MetadataMap } from '@dbeaver/core/utils';
import { NavNodeManagerService } from '../NodesManager/NavNodeManagerService';
import { NodeManagerUtils } from '../NodesManager/NodeManagerUtils';
import { EConnectionFeature } from './EConnectionFeature';
export type DBDriver = Pick<
DriverInfo,
@@ -37,23 +39,47 @@ export type DBDriver = Pick<
| 'promotedScore'
>
export type DBSource = Pick<DataSourceInfo, 'id' | 'name' | 'driverId' | 'description'>
export type Connection = Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId'>
export type Connection = Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>
export type ObjectContainer = Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>
type ConnectionLoadArgs = {
connectionId: string;
close?: boolean;
remove?: boolean;
connection?: never;
}
type ConnectionSetArgs = {
connection: Connection;
connectionId?: never;
remove?: never;
close?: never;
}
type DBDriversMetadata = {
loaded: boolean;
}
type ConnectionInfoMetadata = {
loading: boolean;
loaded: boolean;
}
@injectable()
export class ConnectionsManagerService {
@observable private connectionsMap: Map<string, Connection> = new Map();
readonly dbDrivers = new CachedResource(
new Map(),
this.refreshDriversAsync.bind(this),
(_, { loaded }) => loaded,
{ loaded: false }
);
readonly connectionInfo = new CachedResource(
new Map(),
this.loadConnectionInfo.bind(this),
(_, metadata, args) => metadata.get(args.connectionId || args.connection!.id).loaded,
new MetadataMap<string, ConnectionInfoMetadata>(() => ({ loaded: false, loading: false })),
(_, metadata, args) => metadata.get(args.connectionId || args.connection!.id).loading
);
readonly connectionObjectContainers = new CachedResource(
new Map(),
this.refreshObjectContainersAsync.bind(this),
@@ -61,7 +87,7 @@ export class ConnectionsManagerService {
);
@computed get connections(): Connection[] {
return Array.from(this.connectionsMap.values());
return Array.from(this.connectionInfo.data.values());
}
onOpenConnection = new Subject<Connection>();
@@ -80,18 +106,35 @@ export class ConnectionsManagerService {
return this.dbDrivers.data;
}
async loadConnectionInfoAsync(connectionId: string): Promise<Connection> {
const connections = await this.connectionInfo.load({ connectionId });
return connections.get(connectionId)!;
}
async refreshConnectionInfoAsync(connectionId: string): Promise<Connection> {
const connections = await this.connectionInfo.refresh(true, { connectionId });
return connections.get(connectionId)!;
}
async loadDriversAsync(): Promise<Map<string, DBDriver>> {
return this.dbDrivers.load();
}
async addOpenedConnection(connection: Connection) {
this.connectionsMap.set(connection.id, connection);
await this.connectionInfo.refresh(
true,
{
connection,
}
);
this.onOpenConnection.next(connection);
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 {
return this.connectionsMap.get(connectionId);
return this.connectionInfo.data.get(connectionId);
}
getObjectContainerById(
@@ -122,7 +165,7 @@ export class ConnectionsManagerService {
async closeConnectionAsync(id: string, skipNodesRefresh?: boolean): Promise<void> {
await this.graphQLService.gql.closeConnection({ id });
await this.afterConnectionClose(id);
this.connectionsMap.delete(id);
await this.connectionInfo.refresh(true, { connectionId: id, close: true });
if (!skipNodesRefresh) {
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must just remove nodes from nodes manager
@@ -139,10 +182,9 @@ export class ConnectionsManagerService {
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(navNodeId);
await this.graphQLService.gql.closeConnection({ id: connectionId });
await this.afterConnectionClose(connectionId);
this.connectionsMap.delete(connectionId);
await this.connectionInfo.refresh(true, { connectionId, close: true });
if (node.objectFeatures.includes('dataSourceTemporary')) {
await this.navNodeManagerService.removeNode(navNodeId);
} else {
await this.navNodeManagerService.refreshNode(navNodeId);
}
@@ -177,7 +219,10 @@ export class ConnectionsManagerService {
for (const connection of connectionsToRemove) {
await this.afterConnectionClose(connection.id);
this.connectionsMap.delete(connection.id);
await this.connectionInfo.refresh(true, {
connectionId: connection.id,
remove: true,
});
}
await this.navNodeManagerService.updateRootChildren();
@@ -224,14 +269,57 @@ export class ConnectionsManagerService {
return data;
}
private async restoreConnection(connection: Connection) {
// TODO: Must be loaded based on connection id
// const { connection } = await this.graphQLService.gql.connectionState({ id });
if (this.connectionsMap.has(connection.id)) {
return;
private async loadConnectionInfo(
data: Map<string, Connection>,
metadata: MetadataMap<string, ConnectionInfoMetadata>,
load: boolean,
args: ConnectionLoadArgs | ConnectionSetArgs,
): Promise<Map<string, Connection>> {
let connectionId: string;
if (args.connection) {
connectionId = args.connection.id;
} else {
connectionId = args.connectionId;
}
this.connectionsMap.set(connection.id, connection);
const connectionInfo = data.get(connectionId);
const itemMetadata = metadata.get(connectionId);
if (args.connection) {
data.set(connectionId, args.connection);
itemMetadata.loaded = true;
return data;
}
if (args.remove || (args.close && connectionInfo?.features.includes(EConnectionFeature.temporary))) {
data.delete(connectionId);
metadata.delete(connectionId);
return data;
}
if (load) {
try {
itemMetadata.loading = true;
const { connection } = await this.graphQLService.gql.connectionState({ id: connectionId });
data.set(connectionId, connection);
itemMetadata.loaded = true;
} finally {
itemMetadata.loading = false;
}
}
return data;
}
private async restoreConnection(connection: Connection) {
await this.connectionInfo.refresh(
true,
{
connection,
}
);
this.onOpenConnection.next(connection);
}
}
@@ -0,0 +1,15 @@
/*
* 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.
*/
export enum EConnectionFeature {
connected = 'connected',
virtual = 'virtual',
temporary = 'temporary',
readOnly = 'readOnly',
provided = 'provided'
}
@@ -4,5 +4,6 @@ query connectionState($id: ID!) {
name
driverId
connected
features
}
}
@@ -4,5 +4,6 @@ mutation createConnection($config: ConnectionConfig!) {
name
driverId
connected
features
}
}
@@ -4,5 +4,6 @@ mutation openConnection($config: ConnectionConfig!) {
name
driverId
connected
features
}
}
@@ -0,0 +1,31 @@
/*
* 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 { useCallback } from 'react';
import { useService } from '@dbeaver/core/di';
import { ConnectionsManagerService } from './ConnectionsManagerService';
export function useConnectionInfo(connectionId: string) {
const service = useService(ConnectionsManagerService);
const connectionInfo = service.getConnectionById(connectionId);
const load = useCallback(() => service.loadConnectionInfoAsync(connectionId), [service, connectionId]);
const refresh = useCallback(() => service.refreshConnectionInfoAsync(connectionId), [service, connectionId]);
const isLoading = useCallback(() => service.connectionInfo.isDataLoading({ connectionId }), [service, connectionId]);
const isLoaded = useCallback(() => service.connectionInfo.isLoaded({ connectionId }), [service, connectionId]);
return {
connectionInfo,
isLoading,
isLoaded,
load,
refresh,
};
}
@@ -166,6 +166,7 @@ export class NavNodeManagerService {
async updateRootChildren() {
if (!await this.permissionsService.hasAsync(EPermission.public)) {
await this.navTree.refresh(true, ROOT_NODE_PATH, true);
return;
}
await this.navTree.refresh(true, ROOT_NODE_PATH, false);
@@ -16,7 +16,7 @@ import {
import { SessionSettingsService } from '../settings/SessionSettingsService';
export type SessionState = Pick<SessionInfo, 'createTime' | 'cacheExpired' | 'lastAccessTime' | 'locale'> & {
connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId'>>;
connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>>;
};
@injectable()
@@ -9,6 +9,7 @@ mutation openSession {
name
driverId
connected
features
}
}
}
@@ -9,6 +9,7 @@ query sessionState {
name
driverId
connected
features
}
}
}
+10 -5
View File
@@ -701,14 +701,14 @@ export type ConnectionStateQueryVariables = {
};
export type ConnectionStateQuery = { connection: Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'> };
export type ConnectionStateQuery = { connection: Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected' | 'features'> };
export type CreateConnectionMutationVariables = {
config: ConnectionConfig;
};
export type CreateConnectionMutation = { createConnection: Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'> };
export type CreateConnectionMutation = { createConnection: Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected' | 'features'> };
export type DataSourceListQueryVariables = {};
@@ -742,7 +742,7 @@ export type OpenConnectionMutationVariables = {
};
export type OpenConnectionMutation = { openConnection: Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'> };
export type OpenConnectionMutation = { openConnection: Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected' | 'features'> };
export type TestConnectionMutationVariables = {
config: ConnectionConfig;
@@ -1118,7 +1118,7 @@ export type OpenSessionMutationVariables = {};
export type OpenSessionMutation = { session: (
Pick<SessionInfo, 'createTime' | 'lastAccessTime' | 'cacheExpired' | 'locale'>
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'>> }
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected' | 'features'>> }
); };
export type ServerConfigQueryVariables = {};
@@ -1139,7 +1139,7 @@ export type SessionStateQueryVariables = {};
export type SessionStateQuery = { sessionState: (
Pick<SessionInfo, 'createTime' | 'lastAccessTime' | 'locale' | 'cacheExpired'>
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'>> }
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected' | 'features'>> }
); };
export type TouchSessionMutationVariables = {};
@@ -1178,6 +1178,7 @@ export const ConnectionStateDocument = `
name
driverId
connected
features
}
}
`;
@@ -1188,6 +1189,7 @@ export const CreateConnectionDocument = `
name
driverId
connected
features
}
}
`;
@@ -1248,6 +1250,7 @@ export const OpenConnectionDocument = `
name
driverId
connected
features
}
}
`;
@@ -1766,6 +1769,7 @@ export const OpenSessionDocument = `
name
driverId
connected
features
}
}
}
@@ -1809,6 +1813,7 @@ export const SessionStateDocument = `
name
driverId
connected
features
}
}
}