Merge remote-tracking branch 'origin/devel' into devel

This commit is contained in:
Serge Rider
2020-07-08 14:26:30 +03:00
63 changed files with 1206 additions and 890 deletions
@@ -11,7 +11,7 @@ import styled, { css } from 'reshadow';
import { usePermission, EPermission } from '@cloudbeaver/core-root';
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource';
import { useChildren } from '../shared/useChildren';
import { NavigationTreeNode } from './NavigationTreeNode/NavigationTreeNode';
@@ -25,7 +25,7 @@ export function useNavigationTree(nodeId: string, parentId: string) {
const navNodeManagerService = useService(NavNodeManagerService);
const [isExpanded, switchExpand] = useState(false);
const [isSelected, switchSelect] = useState(false);
const { node, isLoaded: nodeLoaded } = useNode(nodeId);
const { node, isOutdated } = useNode(nodeId);
const children = useChildren(nodeId);
if (!node) {
@@ -81,12 +81,12 @@ export function useNavigationTree(nodeId: string, parentId: string) {
}, [isExpandable && hasChildren]);
useEffect(() => {
if (isExpandedFiltered && !children.isLoaded && !children.isLoading && !!children.children && nodeLoaded) {
if (isExpandedFiltered && children.isOutdated && !children.isLoading && children.isLoaded && !isOutdated) {
navigationTreeService
.loadNestedNodes(nodeId)
.then(state => !state && switchExpand(false));
}
}, [isExpandedFiltered, children.isLoaded, children.isLoading, children.children, nodeLoaded, nodeId]);
}, [isExpandedFiltered, children.isOutdated, children.isLoading, children.children, isOutdated, nodeId]);
// Here we subscribe to selected nodes if current node selected (mobx)
if (isSelected && !navigationTreeService.isNodeSelected(nodeId)) {
@@ -14,7 +14,8 @@ import { NotificationService } from '@cloudbeaver/core-events';
import { ConnectionsManagerService } from '../shared/ConnectionsManager/ConnectionsManagerService';
import { EObjectFeature } from '../shared/NodesManager/EObjectFeature';
import { NavNodeManagerService, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource';
import { NavNodeManagerService } from '../shared/NodesManager/NavNodeManagerService';
import { NodeManagerUtils } from '../shared/NodesManager/NodeManagerUtils';
@injectable()
@@ -13,7 +13,8 @@ import { NotificationService } from '@cloudbeaver/core-events';
import { IExtension } from '@cloudbeaver/core-extensions';
import { NavigationTreeService } from '../../NavigationTree/NavigationTreeService';
import { ConnectionsManagerService, ObjectContainer } from '../../shared/ConnectionsManager/ConnectionsManagerService';
import { ConnectionsManagerService } from '../../shared/ConnectionsManager/ConnectionsManagerService';
import { ObjectContainer } from '../../shared/ConnectionsManager/ContainerResource';
import { isConnectionProvider, IConnectionProvider } from '../../shared/ConnectionsManager/extensions/IConnectionProvider';
import { isConnectionSetter, IConnectionSetter } from '../../shared/ConnectionsManager/extensions/IConnectionSetter';
import { ITab } from '../../shared/NavigationTabs/ITab';
@@ -184,7 +185,7 @@ export class ConnectionSchemaManagerService {
}
try {
await this.connectionsManagerService.dbDrivers.load();
await this.connectionsManagerService.dbDrivers.load('');
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load database drivers', true);
}
@@ -11,7 +11,8 @@ import { computed } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { ComputedMenuItemModel, ComputedMenuPanelModel, IMenuItem } from '@cloudbeaver/core-dialogs';
import { ConnectionsManagerService, Connection } from '../../../shared/ConnectionsManager/ConnectionsManagerService';
import { Connection } from '../../../shared/ConnectionsManager/ConnectionInfoResource';
import { ConnectionsManagerService } from '../../../shared/ConnectionsManager/ConnectionsManagerService';
import { EObjectFeature } from '../../../shared/NodesManager/EObjectFeature';
import { NodeManagerUtils } from '../../../shared/NodesManager/NodeManagerUtils';
import { ConnectionSchemaManagerService } from '../ConnectionSchemaManagerService';
@@ -9,7 +9,7 @@
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { IComputedMenuItemOptions, StaticMenu } from '@cloudbeaver/core-dialogs';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { ServerService } from '@cloudbeaver/core-root';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { ThemeService } from '@cloudbeaver/core-theming';
@injectable()
@@ -23,7 +23,7 @@ export class SettingsMenuService extends Bootstrap {
constructor(
private localizationService: LocalizationService,
private themeService: ThemeService,
private serverService: ServerService
private serverConfigResource: ServerConfigResource
) {
super();
}
@@ -70,7 +70,7 @@ export class SettingsMenuService extends Bootstrap {
}
private async addLocales() {
const config = await this.serverService.config.load();
const config = await this.serverConfigResource.load(null);
if (!config) {
return;
+5
View File
@@ -8,7 +8,10 @@ export * from './TopNavBar/MainMenu/MainMenuService';
export * from './TopNavBar/SettingsMenu/SettingsMenuService';
export * from './TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService';
export * from './TopNavBar/TopNavBarService';
export * from './shared/ConnectionsManager/ConnectionInfoResource';
export * from './shared/ConnectionsManager/ConnectionsManagerService';
export * from './shared/ConnectionsManager/ContainerResource';
export * from './shared/ConnectionsManager/DBDriverResource';
export * from './shared/ConnectionsManager/ConnectionDialogsService';
export * from './shared/ConnectionsManager/extensions/IConnectionProvider';
export * from './shared/ConnectionsManager/extensions/IConnectionSetter';
@@ -17,7 +20,9 @@ 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/NavNodeInfoResource';
export * from './shared/NodesManager/NavNodeManagerService';
export * from './shared/NodesManager/NavTreeResource';
export * from './shared/NodesManager/NavNodeExtensionsService';
export * from './shared/NodesManager/NodeManagerUtils';
export * from './shared/Navigation/NavigationService';
@@ -0,0 +1,30 @@
/*
* 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 '@cloudbeaver/core-di';
import {
ConnectionInfo,
GraphQLService,
CachedMapResource,
} from '@cloudbeaver/core-sdk';
export type Connection = Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>
@injectable()
export class ConnectionInfoResource extends CachedMapResource<string, Connection> {
constructor(private graphQLService: GraphQLService) {
super(new Map());
}
protected async loader(connectionId: string): Promise<Map<string, Connection>> {
const { connection } = await this.graphQLService.gql.connectionState({ id: connectionId });
this.set(connectionId, connection);
return this.data;
}
}
@@ -11,81 +11,20 @@ import { Subject } from 'rxjs';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { SessionService } from '@cloudbeaver/core-root';
import {
ConnectionInfo,
DataSourceInfo,
DriverInfo,
GraphQLService,
CachedResource,
DatabaseObjectInfo,
} from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { SessionResource } from '@cloudbeaver/core-root';
import { DataSourceInfo, GraphQLService } from '@cloudbeaver/core-sdk';
import { NavNodeManagerService } from '../NodesManager/NavNodeManagerService';
import { NodeManagerUtils } from '../NodesManager/NodeManagerUtils';
import { ConnectionInfoResource, Connection } from './ConnectionInfoResource';
import { ContainerResource, ObjectContainer } from './ContainerResource';
import { DBDriverResource, DBDriver } from './DBDriverResource';
import { EConnectionFeature } from './EConnectionFeature';
export type DBDriver = Pick<
DriverInfo,
| 'id'
| 'name'
| 'icon'
| 'description'
| 'defaultPort'
| 'sampleURL'
| 'embedded'
| 'anonymousAccess'
| 'promotedScore'
>
export type DBSource = Pick<DataSourceInfo, 'id' | 'name' | 'driverId' | 'description'>
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 {
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),
this.isObjectContainersLoaded.bind(this)
);
@computed get connections(): Connection[] {
return Array.from(this.connectionInfo.data.values());
}
@@ -95,11 +34,14 @@ export class ConnectionsManagerService {
constructor(
private graphQLService: GraphQLService,
readonly connectionInfo: ConnectionInfoResource,
readonly connectionObjectContainers: ContainerResource,
readonly dbDrivers: DBDriverResource,
private navNodeManagerService: NavNodeManagerService,
private sessionService: SessionService,
private sessionResource: SessionResource,
private notificationService: NotificationService
) {
this.sessionService.onUpdate.subscribe(this.restoreConnections.bind(this));
this.sessionResource.onDataUpdate.subscribe(this.restoreConnections.bind(this));
}
getDBDrivers(): Map<string, DBDriver> {
@@ -107,28 +49,20 @@ export class ConnectionsManagerService {
}
async loadConnectionInfoAsync(connectionId: string): Promise<Connection> {
const connections = await this.connectionInfo.load({ connectionId });
return connections.get(connectionId)!;
return this.connectionInfo.load(connectionId);
}
async refreshConnectionInfoAsync(connectionId: string): Promise<Connection> {
const connections = await this.connectionInfo.refresh(true, { connectionId });
return connections.get(connectionId)!;
return this.connectionInfo.refresh(connectionId);
}
async loadDriversAsync(): Promise<Map<string, DBDriver>> {
return this.dbDrivers.load();
await this.dbDrivers.load('');
return this.dbDrivers.data;
}
async addOpenedConnection(connection: Connection) {
await this.connectionInfo.refresh(
true,
{
connection,
}
);
this.connectionInfo.set(connection.id, 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
}
@@ -165,7 +99,7 @@ export class ConnectionsManagerService {
async closeConnectionAsync(id: string, skipNodesRefresh?: boolean): Promise<void> {
await this.graphQLService.gql.closeConnection({ id });
await this.afterConnectionClose(id);
await this.connectionInfo.refresh(true, { connectionId: id, close: true });
this.connectionInfo.delete(id);
if (!skipNodesRefresh) {
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must just remove nodes from nodes manager
@@ -182,7 +116,9 @@ export class ConnectionsManagerService {
try {
await this.graphQLService.gql.closeConnection({ id: connectionId });
await this.afterConnectionClose(connectionId);
await this.connectionInfo.refresh(true, { connectionId, close: true });
if (connection?.features.includes(EConnectionFeature.temporary)) {
this.connectionInfo.delete(connectionId);
}
if (connection.features.includes(EConnectionFeature.temporary)) {
const node = this.navNodeManagerService.getNode(navNodeId);
@@ -200,17 +136,17 @@ export class ConnectionsManagerService {
}
async loadObjectContainer(connectionId: string, catalogId?: string): Promise<ObjectContainer[]> {
const data = await this.connectionObjectContainers.load(connectionId, catalogId);
return data.get(connectionId)!;
await this.connectionObjectContainers.load({ connectionId, catalogId });
return this.connectionObjectContainers.data.get(connectionId)!;
}
private async afterConnectionClose(id: string) {
await this.navNodeManagerService.removeTree(id);
this.navNodeManagerService.removeTree(id);
this.onCloseConnection.next(id);
}
private async restoreConnections() {
const config = await this.sessionService.session.load();
const config = await this.sessionResource.load(null);
if (!config) {
return;
}
@@ -224,106 +160,14 @@ export class ConnectionsManagerService {
for (const connection of connectionsToRemove) {
await this.afterConnectionClose(connection.id);
await this.connectionInfo.refresh(true, {
connectionId: connection.id,
remove: true,
});
this.connectionInfo.delete(connection.id);
}
await this.navNodeManagerService.updateRootChildren();
}
private isObjectContainersLoaded(
data: Map<string, ObjectContainer[]>,
metadata: {},
connectionId: string,
catalogId?: string,
) {
return data.has(connectionId);
}
private async refreshObjectContainersAsync(
data: Map<string, ObjectContainer[]>,
metadata: {},
update: boolean,
connectionId: string,
catalogId?: string,
): Promise<Map<string, ObjectContainer[]>> {
const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({
connectionId,
catalogId,
});
data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]);
return data;
}
private async refreshDriversAsync(
data: Map<string, DBDriver>,
metadata: DBDriversMetadata,
update: boolean
): Promise<Map<string, DBDriver>> {
const { driverList } = await this.graphQLService.gql.driverList();
data.clear();
for (const driver of driverList) {
data.set(driver.id, driver);
}
metadata.loaded = true;
return data;
}
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;
}
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.connectionInfo.set(connection.id, connection);
this.onOpenConnection.next(connection);
}
}
@@ -0,0 +1,42 @@
/*
* 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 '@cloudbeaver/core-di';
import {
GraphQLService,
CachedDataResource,
DatabaseObjectInfo,
} from '@cloudbeaver/core-sdk';
export type ObjectContainer = Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>
type ObjectContainerParams = {
connectionId: string;
catalogId?: string;
}
@injectable()
export class ContainerResource extends CachedDataResource<Map<string, ObjectContainer[]>, ObjectContainerParams> {
constructor(private graphQLService: GraphQLService) {
super(new Map());
}
isLoaded({ connectionId }: ObjectContainerParams) {
return this.data.has(connectionId);
}
protected async loader({ connectionId, catalogId }: ObjectContainerParams): Promise<Map<string, ObjectContainer[]>> {
const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({
connectionId,
catalogId,
});
this.data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]);
return this.data;
}
}
@@ -0,0 +1,53 @@
/*
* 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 { observable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import {
GraphQLService,
CachedMapResource,
DriverInfo,
} from '@cloudbeaver/core-sdk';
export type DBDriver = Pick<
DriverInfo,
| 'id'
| 'name'
| 'icon'
| 'description'
| 'defaultPort'
| 'sampleURL'
| 'embedded'
| 'anonymousAccess'
| 'promotedScore'
>
@injectable()
export class DBDriverResource extends CachedMapResource<string, DBDriver> {
@observable private loaded = false;
constructor(private graphQLService: GraphQLService) {
super(new Map());
}
isLoaded() {
return this.loaded;
}
protected async loader(param: string): Promise<Map<string, DBDriver>> {
const { driverList } = await this.graphQLService.gql.driverList();
this.data.clear();
for (const driver of driverList) {
this.data.set(driver.id, driver);
}
this.loaded = true;
return this.data;
}
}
@@ -18,8 +18,8 @@ export function useConnectionInfo(connectionId: string) {
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]);
const isLoading = useCallback(() => service.connectionInfo.isDataLoading(connectionId), [service, connectionId]);
const isLoaded = useCallback(() => service.connectionInfo.isLoaded(connectionId), [service, connectionId]);
return {
connectionInfo,
@@ -178,7 +178,7 @@ 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 session = await this.sessionService.session.load();
const session = await this.sessionService.session.load(null);
for (const tabId of this.state.tabs) {
if (session?.cacheExpired) {
@@ -7,128 +7,70 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
import {
GraphQLService, CachedMapResource, ResourceKey, isResourceKeyList
} from '@cloudbeaver/core-sdk';
import { DBObject } from './EntityTypes';
export interface IDBObjectParams {
navNodeId: string[];
remove?: boolean;
parentId?: never;
}
export interface IDBObjectValueParams {
parentId: string;
navNodeId: string[];
remove?: never;
}
interface IDBObjectMetadata {
loaded: boolean;
loading: boolean;
}
import { NavNodeInfoResource } from './NavNodeInfoResource';
@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);
export class DBObjectService extends CachedMapResource<string, DBObject> {
constructor(
private graphQLService: GraphQLService,
private navNodeInfoResource: NavNodeInfoResource,
) {
super(new Map());
this.navNodeInfoResource.onDataOutdated.subscribe(key => this.markOutdated(key));
this.navNodeInfoResource.onItemDelete.subscribe(key => this.delete(key));
}
async load(navNodeId: string): Promise< DBObject>
async load(navNodeId: string[], parentId?: string): Promise<DBObject[]>
async load(navNodeId: string | string[], parentId?: string): Promise<DBObject | DBObject[]> {
const dbObject = await this.dbObject.load({
navNodeId: Array.isArray(navNodeId) ? navNodeId : [navNodeId],
parentId,
async loadChildren(parentId: string, key: ResourceKey<string>) {
if (this.isLoaded(key) && !this.isOutdated(key)) {
return;
}
await this.waitActive();
await this.setActivePromise(key, this.loadFromChildren(parentId));
this.dataSubject.next(this.data);
return this.data;
}
private async loadFromChildren(parentId: string) {
const { dbObjects } = await this.graphQLService.gql.getChildrenDBObjectInfo({
navNodeId: parentId,
});
if (!Array.isArray(navNodeId)) {
return dbObject.get(navNodeId)!;
for (const dbObject of dbObjects) {
this.set(
dbObject.id,
{
navNodeId: dbObject.id,
...dbObject.object,
}
);
}
return navNodeId.map(navNodeId => dbObject.get(navNodeId)!);
}
async remove(navNodeId: string[]) {
await this.dbObject.refresh(true, { navNodeId, remove: true });
protected async loader(key: ResourceKey<string>) {
if (isResourceKeyList(key)) {
const values: DBObject[] = [];
for (const navNodeId of key.list) {
values.push(await this.loadDBObjectInfo(navNodeId));
}
this.set(key, values);
} else {
this.set(key, await this.loadDBObjectInfo(key));
}
return this.data;
}
private async loadDBObject(
dbObject: Map<string, DBObject>,
metadata: MetadataMap<string, IDBObjectMetadata>,
load: boolean,
data: IDBObjectParams | IDBObjectValueParams
) {
private async loadDBObjectInfo(navNodeId: string): Promise<DBObject> {
const { objectInfo: { object } } = await this.graphQLService.gql.getDBObjectInfo({
navNodeId,
});
if (data.parentId) {
for (const navNodeId of data.navNodeId) {
const itemMetadata = metadata.get(navNodeId);
itemMetadata.loaded = false;
if (load) {
itemMetadata.loading = true;
}
}
if (load) {
try {
const { dbObjects } = await this.graphQLService.gql.getChildrenDBObjectInfo({
navNodeId: data.parentId,
});
for (const navNodeId of data.navNodeId) {
const data = dbObjects.find(dbObject => dbObject.id === navNodeId);
if (data) {
const itemMetadata = metadata.get(navNodeId);
dbObject.set(navNodeId, { navNodeId, ...data.object } as DBObject);
itemMetadata.loaded = true;
}
}
} finally {
for (const navNodeId of data.navNodeId) {
const itemMetadata = metadata.get(navNodeId);
itemMetadata.loading = false;
}
}
}
return dbObject;
}
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;
return { navNodeId, ...object };
}
}
@@ -0,0 +1,56 @@
/*
* 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 '@cloudbeaver/core-di';
import {
GraphQLService, CachedMapResource, ResourceKey, isResourceKeyList, NavigatorNodeInfo, DatabaseObjectInfo
} from '@cloudbeaver/core-sdk';
import { NavNode } from './EntityTypes';
type NavNodeInfo = Pick<NavigatorNodeInfo, 'id' | 'name' | 'hasChildren' | 'nodeType' | 'icon' | 'folder' | 'inline' | 'navigable' | 'features'>
& { object?: Pick<DatabaseObjectInfo, 'features'> };
export const ROOT_NODE_PATH = '';
@injectable()
export class NavNodeInfoResource extends CachedMapResource<string, NavNode> {
constructor(private graphQLService: GraphQLService) {
super(new Map());
}
protected async loader(key: ResourceKey<string>) {
if (isResourceKeyList(key)) {
const values: NavNode[] = [];
for (const nodePath of key.list) {
values.push(await this.loadNodeInfo(nodePath));
}
this.set(key, values);
} else {
this.set(key, await this.loadNodeInfo(key));
}
return this.data;
}
navNodeInfoToNavNode(node: NavNodeInfo, parentId?: string): NavNode {
return {
...node,
objectFeatures: node.object?.features || [],
parentId: parentId || this.get(node.id)?.parentId || ROOT_NODE_PATH,
};
}
private async loadNodeInfo(nodePath: string): Promise<NavNode> {
const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({
nodePath,
});
return this.navNodeInfoToNavNode(navNodeInfo);
}
}
@@ -8,16 +8,16 @@
import { injectable } from '@cloudbeaver/core-di';
import { PermissionsService, EPermission } from '@cloudbeaver/core-root';
import { GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { GraphQLService, resourceKeyList } from '@cloudbeaver/core-sdk';
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 { NavNodeInfoResource, ROOT_NODE_PATH } from './NavNodeInfoResource';
import { NavTreeResource } from './NavTreeResource';
import { NodeManagerUtils } from './NodeManagerUtils';
export enum NavigationType {
@@ -80,61 +80,16 @@ export interface INodeNavigationData {
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,
readonly navTree: NavTreeResource,
readonly navNodeInfoResource: NavNodeInfoResource
) {
this.navigator = this.navigationService.createNavigator<INodeNavigationData>(
@@ -161,15 +116,16 @@ export class NavNodeManagerService {
await this.graphQLService.gql.navRefreshNode({
nodePath: navNodeId,
});
await this.navTree.refresh(true, navNodeId, false);
this.navTree.markOutdated(resourceKeyList(this.navTree.getNestedChildren(navNodeId)));
await this.navTree.refresh(navNodeId);
}
async updateRootChildren() {
if (!await this.permissionsService.hasAsync(EPermission.public)) {
await this.navTree.refresh(true, ROOT_NODE_PATH, true);
this.navTree.delete(ROOT_NODE_PATH);
return;
}
await this.navTree.refresh(true, ROOT_NODE_PATH, false);
await this.navTree.refresh(ROOT_NODE_PATH);
}
getTree(navNodeId: string): string[] | undefined
@@ -188,16 +144,16 @@ export class NavNodeManagerService {
}
async loadTree(navNodeId: string) {
await this.navTree.load(navNodeId, false);
await this.navTree.load(navNodeId);
return this.getTree(navNodeId)!;
}
async removeTree(path = ROOT_NODE_PATH) {
await this.navTree.refresh(true, path, true);
removeTree(path = ROOT_NODE_PATH) {
this.navTree.delete(path);
}
async refreshNode(navNodeId: string) {
await this.navNode.refresh(true, { navNodeId: [navNodeId] });
await this.navNodeInfoResource.refresh(navNodeId);
}
getNode(navNodeId: string): NavNode | undefined
@@ -205,41 +161,24 @@ export class NavNodeManagerService {
getNode(navNodeKey: NavNodeKey[]): (NavNode | undefined)[]
getNode(navNodeId: string | NavNodeKey | NavNodeKey[]) {
if (typeof navNodeId === 'string') {
return this.navNode.data.get(navNodeId);
return this.navNodeInfoResource.get(navNodeId);
}
if (Array.isArray(navNodeId)) {
return navNodeId.map(node => this.navNode.data.get(node.nodeId));
return navNodeId.map(node => this.navNodeInfoResource.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;
return this.navNodeInfoResource.get(navNodeId.nodeId);
}
async loadNode(node: NavNodeKey): Promise<NavNode>
async loadNode(...nodes: NavNodeKey[]): Promise<NavNode>
async loadNode(...nodes: NavNodeKey[]) {
await this.navNode.load({ nodes });
const items = await this.navNodeInfoResource.load(resourceKeyList(nodes.map(n => n.nodeId)));
for (let i = 0; i < items.length; i++) {
items[i].parentId = nodes[i].parentId;
}
if (nodes.length === 1) {
return this.getNode(nodes[0])!;
@@ -248,15 +187,12 @@ export class NavNodeManagerService {
return this.getNode(nodes);
}
async removeNode(navNodeId = ROOT_NODE_PATH) {
await this.navNode.refresh(true, {
navNodeId: [navNodeId],
remove: true,
});
removeNode(navNodeId = ROOT_NODE_PATH) {
this.navNodeInfoResource.delete(navNodeId);
}
getParent(node: NavNode) {
return this.navNode.data.get(node.parentId);
return this.navNodeInfoResource.get(node.parentId);
}
isNodeHasData(node?: string | NavNode) {
@@ -335,7 +271,7 @@ export class NavNodeManagerService {
const parents: string[] = [];
let parent = this.getNode(nodeId);
while (parent && parent.parentId !== ROOT_NODE_PATH) {
while (parent && parent.parentId !== ROOT_NODE_PATH && parent.id !== parent.parentId) {
parents.unshift(parent.parentId);
parent = this.getNode(parent.parentId);
}
@@ -346,15 +282,12 @@ export class NavNodeManagerService {
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;
}
}),
});
const nodes = await this.navNodeInfoResource.load(resourceKeyList(parents));
for (const node of nodes) {
node.parentId = parentId;
parentId = node.id;
}
};
return {
@@ -371,138 +304,4 @@ export class NavNodeManagerService {
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);
const parentId = navNode.get(nodeValue.node.id)?.parentId || nodeValue.parentId || ROOT_NODE_PATH;
navNode.set(nodeValue.node.id, {
...nodeValue.node,
objectFeatures: nodeValue.node.object?.features || [],
parentId,
});
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;
}
}
if (load) {
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, navNodeInfo } = await this.graphQLService.gql.navNodeChildren({
parentPath: parentId,
});
await this.navNode.refresh(true, {
nodesValue: [{ node: navNodeInfo }, ...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.filter(navNodeId => navNodeId !== parentId),
remove: true,
});
for (const navNodeId of nestedChildren) {
navTree.delete(navNodeId);
metadata.delete(navNodeId);
}
return navTree;
}
}
@@ -0,0 +1,149 @@
/*
* 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 '@cloudbeaver/core-di';
import {
GraphQLService,
CachedMapResource,
ResourceKey,
isResourceKeyList,
ResourceKeyList,
resourceKeyList,
NavNodeChildrenQuery
} from '@cloudbeaver/core-sdk';
import { NavNodeInfoResource } from './NavNodeInfoResource';
@injectable()
export class NavTreeResource extends CachedMapResource<string, string[]> {
constructor(
private graphQLService: GraphQLService,
private navNodeInfoResource: NavNodeInfoResource,
) {
super(new Map());
this.onDataOutdated.subscribe(navNodeInfoResource.markOutdated.bind(navNodeInfoResource));
}
set(key: string, value: string[]): void;
set(key: ResourceKeyList<string>, value: string[][]): void;
set(keyObject: ResourceKey<string>, valueObject: string[] | string[][]): void {
if (isResourceKeyList(keyObject)) {
for (let i = 0; i < keyObject.list.length; i++) {
const key = keyObject.list[i];
const value = (valueObject as string[][])[i];
const childrenToRemove = this.data.get(key)?.concat() || [];
this.data.set(key, value);
this.delete(resourceKeyList(childrenToRemove.filter(navNodeId => !value.includes(navNodeId))));
}
} else {
const childrenToRemove = this.data.get(keyObject)?.concat() || [];
this.data.set(keyObject, valueObject as string[]);
this.delete(resourceKeyList(
childrenToRemove.filter(navNodeId => !(valueObject as string[]).includes(navNodeId))
));
}
this.markUpdated(keyObject);
this.itemAddSubject.next(keyObject);
}
delete(key: string): void;
delete(key: ResourceKeyList<string>): void;
delete(key: ResourceKey<string>): void;
delete(key: ResourceKey<string>) {
const items = this.getNestedChildren(key);
if (items.length === 0) {
return;
}
for (const id of items) {
this.data.delete(id);
}
const allKeys = resourceKeyList(items);
this.markUpdated(allKeys);
this.itemDeleteSubject.next(allKeys);
this.navNodeInfoResource.delete(resourceKeyList(items.filter(navNodeId => navNodeId !== key)));
}
protected async loader(key: ResourceKey<string>) {
if (isResourceKeyList(key)) {
const values: NavNodeChildrenQuery[] = [];
for (const nodePath of key.list) {
values.push(await this.loadNodeChildren(nodePath));
}
this.setNavObject(values);
} else {
this.setNavObject(await this.loadNodeChildren(key));
}
return this.data;
}
getNestedChildren(navNode: ResourceKey<string>) {
const nestedChildren: string[] = [];
let prevChildren: string[];
if (isResourceKeyList(navNode)) {
prevChildren = navNode.list.concat();
nestedChildren.push(...navNode.list);
} else {
prevChildren = (this.get(navNode)?.concat() || []);
nestedChildren.push(...prevChildren);
}
while (prevChildren.length) {
const nodeKey = prevChildren.shift()!;
const children = this.get(nodeKey) || [];
prevChildren.push(...children);
nestedChildren.push(...children);
}
return nestedChildren;
}
private setNavObject(data: NavNodeChildrenQuery | NavNodeChildrenQuery[]) {
if (Array.isArray(data)) {
this.navNodeInfoResource.set(
resourceKeyList(data.map(data => data.navNodeInfo.id)),
data.map(data => this.navNodeInfoResource.navNodeInfoToNavNode(data.navNodeInfo)).flat()
);
this.navNodeInfoResource.set(
resourceKeyList(data.map(data => data.navNodeChildren.map(node => node.id)).flat()),
data.map(
data => data.navNodeChildren.map(
node => this.navNodeInfoResource.navNodeInfoToNavNode(node, data.navNodeInfo.id)
)
).flat()
);
this.set(
resourceKeyList(data.map(data => data.navNodeInfo.id)),
data.map(data => data.navNodeChildren.map(node => node.id))
);
} else {
this.navNodeInfoResource.set(
data.navNodeInfo.id,
this.navNodeInfoResource.navNodeInfoToNavNode(data.navNodeInfo)
);
this.navNodeInfoResource.set(
resourceKeyList(data.navNodeChildren.map(node => node.id)),
data.navNodeChildren.map(node => this.navNodeInfoResource.navNodeInfoToNavNode(node, data.navNodeInfo.id))
);
this.set(data.navNodeInfo.id, data.navNodeChildren.map(node => node.id));
}
}
private async loadNodeChildren(parentPath: string) {
const { navNodeChildren, navNodeInfo } = await this.graphQLService.gql.navNodeChildren({
parentPath,
});
return { navNodeChildren, navNodeInfo };
}
}
@@ -12,9 +12,9 @@ import { DBObjectService } from './DBObjectService';
export function useDatabaseObjectInfo(navNodeId: string) {
const dbObjectService = useService(DBObjectService);
const dbObject = dbObjectService.getDBObject(navNodeId);
const isLoading = dbObjectService.dbObject.isDataLoading({ navNodeId: [navNodeId] })
const isLoaded = dbObjectService.dbObject.isLoaded({ navNodeId: [navNodeId] })
const dbObject = dbObjectService.get(navNodeId);
const isLoading = dbObjectService.isDataLoading(navNodeId);
const isLoaded = dbObjectService.isLoaded(navNodeId);
return { dbObject, isLoading, isLoaded };
}
@@ -8,19 +8,16 @@
import { useService } from '@cloudbeaver/core-di';
import { NavNodeManagerService } from './NavNodeManagerService';
import { NavNodeInfoResource } from './NavNodeInfoResource';
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],
});
const navNodeInfoResource = useService(NavNodeInfoResource);
const node = navNodeInfoResource.get(navNodeId);
const isLoading = navNodeInfoResource.isDataLoading(navNodeId);
const isLoaded = navNodeInfoResource.isLoaded(navNodeId);
const isOutdated = navNodeInfoResource.isOutdated(navNodeId);
return {
node, isLoading, isLoaded,
node, isLoading, isLoaded, isOutdated,
};
}
@@ -8,17 +8,20 @@
import { useService } from '@cloudbeaver/core-di';
import { NavNodeManagerService, ROOT_NODE_PATH } from './NodesManager/NavNodeManagerService';
import { ROOT_NODE_PATH } from './NodesManager/NavNodeInfoResource';
import { NavNodeManagerService } from './NodesManager/NavNodeManagerService';
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);
const isLoading = navNodeManagerService.navTree.isDataLoading(navNodeId);
const isLoaded = navNodeManagerService.navTree.isLoaded(navNodeId);
const isOutdated = navNodeManagerService.navTree.isOutdated(navNodeId);
return {
children,
isLoaded,
isLoading,
isOutdated,
};
}
@@ -25,5 +25,6 @@ export async function bootstrap(plugins: PluginManifest[]) {
} catch (e) {
console.log(e);
showErrorPage();
throw e;
}
}
+18 -3
View File
@@ -9,8 +9,8 @@
import {
ConnectionDialogsService, ConnectionsManagerService,
MainMenuService, NavigationService, NavigationTreeService,
NavNodeManagerService, DBObjectService, NavNodeExtensionsService,
ConnectionSchemaManagerService, NavigationTabsService,
NavNodeManagerService, DBObjectService, NavNodeExtensionsService, NavNodeInfoResource, NavTreeResource,
ConnectionSchemaManagerService, ConnectionInfoResource, ContainerResource, DBDriverResource, NavigationTabsService,
NavigationTreeContextMenuService,
SettingsMenuService, LogViewerService, LogViewerMenuService, TopNavService,
RouterService, ScreenService, AppScreenService, CoreSettingsService
@@ -22,7 +22,14 @@ import { LocalizationService } from '@cloudbeaver/core-localization';
import { PluginManagerService } from '@cloudbeaver/core-plugin';
import { ProductManagerService, ProductSettingsService } from '@cloudbeaver/core-product';
import {
SessionService, ServerService, PermissionsService, SessionSettingsService, ServerSettingsService
SessionService,
ServerService,
PermissionsService,
SessionSettingsService,
ServerSettingsService,
ServerConfigResource,
PermissionsResource,
SessionResource
} from '@cloudbeaver/core-root';
import { EnvironmentService, GraphQLService } from '@cloudbeaver/core-sdk';
import { LocalStorageSaveService, SettingsService } from '@cloudbeaver/core-settings';
@@ -39,6 +46,9 @@ export const coreManifest: PluginManifest = {
ProductManagerService,
PluginManagerService,
ServerSettingsService,
ServerConfigResource,
PermissionsResource,
SessionResource,
SessionSettingsService,
PermissionsService,
CoreSettingsService,
@@ -46,6 +56,9 @@ export const coreManifest: PluginManifest = {
SessionExpireService,
ConnectionDialogsService,
ConnectionSchemaManagerService,
ConnectionInfoResource,
ContainerResource,
DBDriverResource,
ConnectionsManagerService,
RouterService,
ScreenService,
@@ -67,6 +80,8 @@ export const coreManifest: PluginManifest = {
NavigationTreeService,
NavNodeManagerService,
NavNodeExtensionsService,
NavNodeInfoResource,
NavTreeResource,
DBObjectService,
NotificationService,
SessionService,
@@ -70,7 +70,7 @@ export class LocalizationService extends Bootstrap {
}
async load(): Promise<void> {
const session = await this.sessionService.session.load();
const session = await this.sessionService.session.load(null);
await this.loadLocaleAsync(DEFAULT_LOCALE_NAME);
if (!session) {
@@ -113,7 +113,7 @@ export class LocalizationService extends Bootstrap {
}
private async setLocale(key: string) {
const config = await this.serverService.config.load();
const config = await this.serverService.config.load(null);
if (!config) {
throw new Error('Cant\'t get server settings');
@@ -0,0 +1,44 @@
/*
* 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 { observable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { GraphQLService, CachedDataResource } from '@cloudbeaver/core-sdk';
@injectable()
export class PermissionsResource extends CachedDataResource<Set<string>, null> {
@observable private loaded = false;
constructor(
private graphQLService: GraphQLService,
) {
super(new Set());
}
isLoaded() {
return this.loaded;
}
has(id: string): boolean {
return this.data.has(id);
}
protected async loader(key: null): Promise<Set<string>> {
const { permissions } = await this.graphQLService.gql.sessionPermissions();
this.data.clear();
for (const permission of permissions) {
this.data.add(permission);
}
this.markUpdated(key);
this.loaded = true;
return this.data;
}
}
@@ -9,13 +9,9 @@
import { Subject, Observable } from 'rxjs';
import { injectable } from '@cloudbeaver/core-di';
import { GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
import { SessionService } from './SessionService';
type PermissionsMetadata = {
loaded: boolean;
}
import { PermissionsResource } from './PermissionsResource';
import { SessionResource } from './SessionResource';
export enum EPermission {
public = 'public'
@@ -26,47 +22,27 @@ export class PermissionsService {
readonly onUpdate: Observable<unknown>;
private updateSubject: Subject<unknown>;
private permissions = new CachedResource(
new Map(),
this.refreshAsync.bind(this),
(_, { loaded }) => loaded
);
constructor(
private graphQLService: GraphQLService,
private sessionService: SessionService,
private sessionResource: SessionResource,
private permissions: PermissionsResource,
) {
this.updateSubject = new Subject();
this.onUpdate = this.updateSubject.asObservable();
this.sessionService.onUpdate.subscribe(this.update.bind(this));
this.sessionResource.onDataUpdate.subscribe(this.update.bind(this));
}
has(id: string): boolean {
return this.permissions.data.has(id);
return this.permissions.has(id);
}
async hasAsync(id: string): Promise<boolean> {
const permissions = await this.permissions.load();
return permissions.has(id);
await this.permissions.load(null);
return this.has(id);
}
async update() {
await this.permissions.refresh(true);
await this.permissions.refresh(null);
this.updateSubject.next();
}
private async refreshAsync(
data: Map<string, string>,
metadata: PermissionsMetadata
): Promise<Map<string, string>> {
const { permissions } = await this.graphQLService.gql.sessionPermissions();
data.clear();
for (const permission of permissions) {
data.set(permission, permission);
}
metadata.loaded = true;
return data;
}
}
@@ -0,0 +1,30 @@
/*
* 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 '@cloudbeaver/core-di';
import { GraphQLService, CachedDataResource, ServerConfig } from '@cloudbeaver/core-sdk';
@injectable()
export class ServerConfigResource extends CachedDataResource<ServerConfig | null, null> {
constructor(
private graphQLService: GraphQLService,
) {
super(null);
}
isLoaded() {
return !!this.data;
}
protected async loader(key: null): Promise<ServerConfig> {
const { serverConfig } = await this.graphQLService.gql.serverConfig();
this.markUpdated(key);
return serverConfig;
}
}
+11 -9
View File
@@ -7,35 +7,37 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { GraphQLService, ServerConfig, CachedResource } from '@cloudbeaver/core-sdk';
import { parseJSONFlat } from '@cloudbeaver/core-utils';
import { ServerConfigResource } from './ServerConfigResource';
import { ServerSettingsService } from './ServerSettingsService';
import { SessionService } from './SessionService';
@injectable()
export class ServerService {
readonly config = new CachedResource(undefined, this.refreshConfigAsync.bind(this), data => !!data);
readonly settings = new ServerSettingsService(this.sessionService.settings);
private lastConfig: any = null
constructor(
private graphQLService: GraphQLService,
readonly config: ServerConfigResource,
private sessionService: SessionService,
) {
this.config.onDataUpdate.subscribe(this.refreshConfigAsync.bind(this));
}
private async refreshConfigAsync(data: ServerConfig | undefined): Promise<ServerConfig> {
const { serverConfig } = await this.graphQLService.gql.serverConfig();
private refreshConfigAsync() {
if (!this.config.data) {
return;
}
if (serverConfig.productConfiguration !== this.lastConfig) {
this.lastConfig = serverConfig.productConfiguration;
if (this.config.data.productConfiguration !== this.lastConfig) {
this.lastConfig = this.config.data.productConfiguration;
this.settings.clear();
parseJSONFlat(
serverConfig.productConfiguration,
this.config.data.productConfiguration,
this.settings.setSelfValue.bind(this.settings)
);
}
return serverConfig;
}
}
@@ -0,0 +1,39 @@
/*
* 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 '@cloudbeaver/core-di';
import {
GraphQLService,
CachedDataResource,
SessionInfo,
ConnectionInfo
} from '@cloudbeaver/core-sdk';
export type SessionState = Pick<SessionInfo, 'createTime' | 'cacheExpired' | 'lastAccessTime' | 'locale'> & {
connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>>;
};
@injectable()
export class SessionResource extends CachedDataResource<SessionState | null, null> {
constructor(
private graphQLService: GraphQLService,
) {
super(null);
}
isLoaded() {
return !!this.data;
}
protected async loader(key: null): Promise<SessionState> {
const { session } = await this.graphQLService.gql.openSession();
this.markUpdated(key);
return session;
}
}
@@ -6,40 +6,21 @@
* you may not use this file except in compliance with the License.
*/
import { Subject, Observable } from 'rxjs';
import { injectable } from '@cloudbeaver/core-di';
import {
ConnectionInfo, GraphQLService, SessionInfo, CachedResource,
} from '@cloudbeaver/core-sdk';
import { SessionResource } from './SessionResource';
import { SessionSettingsService } from './SessionSettingsService';
export type SessionState = Pick<SessionInfo, 'createTime' | 'cacheExpired' | 'lastAccessTime' | 'locale'> & {
connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>>;
};
@injectable()
export class SessionService {
readonly session = new CachedResource(undefined, this.refreshSessionStateAsync.bind(this), data => !!data);
readonly settings = new SessionSettingsService('session_settings');
readonly onUpdate: Observable<unknown>;
private updateSubject: Subject<unknown>;
constructor(private graphQLService: GraphQLService) {
this.updateSubject = new Subject();
this.onUpdate = this.updateSubject.asObservable();
constructor(
readonly session: SessionResource
) {
}
async update() {
await this.session.refresh(true);
}
private async refreshSessionStateAsync(data: SessionState | undefined): Promise<SessionState> {
const { session } = await this.graphQLService.gql.openSession();
this.updateSubject.next();
return session;
await this.session.refresh(null);
}
}
+5 -2
View File
@@ -1,6 +1,9 @@
export * from './SessionService';
export * from './ServerService';
export * from './PermissionsResource';
export * from './PermissionsService';
export * from './ServerConfigResource';
export * from './SessionService';
export * from './SessionResource';
export * from './ServerService';
export * from './usePermission';
export * from './ServerSettingsService';
export * from './SessionSettingsService';
@@ -0,0 +1,29 @@
/*
* 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 '@cloudbeaver/core-di';
import { CachedResource } from './CachedResource';
@injectable()
export abstract class CachedDataResource<
TData,
TParam,
> extends CachedResource<TData, TParam> {
async refresh(param: TParam): Promise<TData> {
this.markOutdated(param);
await this.loadData(param);
return this.data;
}
async load(param: TParam): Promise<TData> {
await this.loadData(param);
return this.data;
}
}
@@ -0,0 +1,153 @@
/*
* 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 { Subject, Observable } from 'rxjs';
import { injectable } from '@cloudbeaver/core-di';
import { CachedResource } from './CachedResource';
export const RESOURCE_KEY_LIST = Symbol('@CachedMapResource/list');
export type ResourceKeyList<TKey> = {
list: TKey[];
[RESOURCE_KEY_LIST]: true;
}
export type ResourceKey<TKey> = TKey | ResourceKeyList<TKey>;
@injectable()
export abstract class CachedMapResource<TKey, TValue> extends CachedResource<
Map<TKey, TValue>,
ResourceKey<TKey>
>
{
readonly onItemAdd: Observable<ResourceKey<TKey>>;
readonly onItemDelete: Observable<ResourceKey<TKey>>;
protected itemAddSubject: Subject<ResourceKey<TKey>>;
protected itemDeleteSubject: Subject<ResourceKey<TKey>>;
constructor(defaultValue?: Map<TKey, TValue>) {
super(defaultValue || new Map());
this.itemAddSubject = new Subject();
this.onItemAdd = this.itemAddSubject.asObservable();
this.itemDeleteSubject = new Subject();
this.onItemDelete = this.itemDeleteSubject.asObservable();
}
isOutdated(key: ResourceKey<TKey>): boolean {
if (isResourceKeyList(key)) {
return key.list.some(key => this.outdated.has(key));
}
return this.outdated.has(key);
}
markOutdated(key: ResourceKey<TKey>): void {
if (isResourceKeyList(key)) {
for (const itemKey of key.list) {
this.outdated.add(itemKey);
}
} else {
this.outdated.add(key);
}
this.outdatedSubject.next(key);
}
markUpdated(key: ResourceKey<TKey>): void {
if (isResourceKeyList(key)) {
for (const itemKey of key.list) {
this.outdated.delete(itemKey);
}
} else {
this.outdated.delete(key);
}
}
isLoaded(key: ResourceKey<TKey>): boolean {
if (isResourceKeyList(key)) {
return key.list.every(key => this.data.has(key));
}
return this.data.has(key);
}
isDataLoading(key: ResourceKey<TKey>): boolean {
if (isResourceKeyList(key)) {
return key.list.some(
key => (isResourceKeyList(this.activePromiseParam)
? this.activePromiseParam.list.includes(key)
: key === this.activePromiseParam)
);
}
return this.activePromiseParam === key;
}
get(key: TKey): TValue | undefined;
get(key: ResourceKeyList<TKey>): Array<TValue | undefined>;
get(key: ResourceKey<TKey>): Array<TValue | undefined>| TValue | undefined;
get(key: ResourceKey<TKey>): Array<TValue | undefined>| TValue | undefined {
if (isResourceKeyList(key)) {
return key.list.map(key => this.data.get(key));
}
return this.data.get(key);
}
set(key: TKey, value: TValue): void;
set(key: ResourceKeyList<TKey>, value: TValue[]): void;
set(key: ResourceKey<TKey>, value: TValue | TValue[]): void {
if (isResourceKeyList(key)) {
for (let i = 0; i < key.list.length; i++) {
this.data.set(key.list[i], (value as TValue[])[i]);
}
} else {
this.data.set(key, value as TValue);
}
this.markUpdated(key);
this.itemAddSubject.next(key);
}
delete(key: TKey): void;
delete(key: ResourceKeyList<TKey>): void;
delete(key: ResourceKey<TKey>): void;
delete(key: ResourceKey<TKey>) {
if (isResourceKeyList(key)) {
for (let i = 0; i < key.list.length; i++) {
this.data.delete(key.list[i]);
}
} else {
this.data.delete(key);
}
this.markUpdated(key);
this.itemDeleteSubject.next(key);
}
async refresh(key: TKey): Promise<TValue>;
async refresh(key: ResourceKeyList<TKey>): Promise<Array<TValue>>;
async refresh(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue>;
async refresh(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue> {
this.markOutdated(key);
await this.loadData(key);
return this.get(key) as Array<TValue>| TValue;
}
async load(key: TKey): Promise<TValue>;
async load(key: ResourceKeyList<TKey>): Promise<Array<TValue>>;
async load(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue>;
async load(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue> {
await this.loadData(key);
return this.get(key) as Array<TValue>| TValue;
}
}
export function isResourceKeyList<T>(data: any): data is ResourceKeyList<T> {
return data && typeof data === 'object' && RESOURCE_KEY_LIST in data;
}
export function resourceKeyList<T>(list: T[]): ResourceKeyList<T> {
return { [RESOURCE_KEY_LIST]: true, list };
}
+60 -79
View File
@@ -9,117 +9,98 @@
import { observable } from 'mobx';
import { Subject, Observable } from 'rxjs';
export type Loader<TData, TMetadata = {}, TArgs extends any[] = []> = (
current: TData,
metadata: TMetadata,
update: boolean,
...args: TArgs
) => Promise<TData>
import { injectable } from '@cloudbeaver/core-di';
export type IsLoaded<TData, TMetadata = {}, TArgs extends any[] = []> = (
current: TData,
metadata: TMetadata,
...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[] = []> {
@injectable()
export abstract class CachedResource<
TData,
TParam,
> {
@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;
readonly onDataOutdated: Observable<TParam>;
constructor(
defaultValue: TData,
loader: Loader<TData, TMetadata, TArgs>,
isLoadedCheck: IsLoaded<TData, TMetadata, TArgs>,
metadata?: TMetadata,
isLoadingCheck?: IsLoading<TData, TMetadata, TArgs>,
) {
@observable protected outdated = new Set<TParam>();
@observable protected loading = false;
protected outdatedSubject: Subject<TParam>;
protected dataSubject: Subject<TData>;
@observable protected activePromiseParam: TParam | null = null;
private activePromise: Promise<any> | null = null;
constructor(defaultValue: TData) {
this.data = defaultValue;
this.loader = loader;
this.isLoadedCheck = isLoadedCheck;
this.isLoadingCheck = isLoadingCheck;
this.metadata = metadata || {} as TMetadata;
this.dataSubject = new Subject<TData>();
this.outdatedSubject = new Subject();
this.dataSubject = new Subject();
this.onDataOutdated = this.outdatedSubject.asObservable();
this.onDataUpdate = this.dataSubject.asObservable();
}
isLoaded(...args: TArgs): boolean {
return this.isLoadedCheck(this.data, this.metadata, ...args);
abstract isLoaded(param: TParam): boolean;
isOutdated(param: TParam): boolean {
return this.outdated.has(param);
}
isLoading(): boolean {
return this.loading;
}
isDataLoading(...args: TArgs): boolean {
if (this.isLoadingCheck) {
return this.loading && this.isLoadingCheck(this.data, this.metadata, ...args);
markOutdated(param: TParam): void {
this.outdated.add(param);
this.outdatedSubject.next(param);
}
markUpdated(param: TParam): void {
this.outdated.delete(param);
}
async refresh(param: TParam): Promise<any> {
this.markOutdated(param);
await this.loadData(param);
return this.data;
}
async load(param: TParam): Promise<any> {
await this.loadData(param);
return this.data;
}
protected abstract loader(param: TParam): Promise<TData>;
protected async loadData(param: TParam) {
if (this.isLoaded(param) && !this.isOutdated(param)) {
return;
}
return this.loading;
}
async refresh(load = false, ...args: TArgs): Promise<TData> {
return this.loadData(load, true, args);
}
async load(...args: TArgs): Promise<TData> {
return this.loadData(true, false, args);
}
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(load, update, args);
await this.setActivePromise(param, this.loadingTask(param));
this.dataSubject.next(this.data);
}
protected async setActivePromise(param: TParam, promise: Promise<any>) {
this.activePromise = promise;
this.activePromiseParam = param;
try {
return await this.activePromise;
await this.activePromise;
} finally {
this.activePromise = null;
this.dataSubject.next(this.data);
this.activePromiseParam = null;
}
}
private async loadingTask(load: boolean, update: boolean, args: TArgs): Promise<TData> {
private async loadingTask(param: TParam) {
const prevState = this.loading;
this.loading = true;
try {
// 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, load, ...args);
this.data = await this.loader(param);
} finally {
this.loading = prevState;
}
return this.data;
}
private async waitActive() {
protected async waitActive() {
if (this.activePromise) {
try {
await this.activePromise;
+2
View File
@@ -1,8 +1,10 @@
export * from './CachedDataResource';
export * from './GraphQLService';
export * from './EnvironmentService';
export * from './sdk';
export * from './GQLError';
export * from './ServerInternalError';
export * from './CachedResource';
export * from './CachedMapResource';
export * from './EServerErrorCode';
export * from './GQLErrorCatcher';
@@ -7,41 +7,13 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import {
CachedResource, GraphQLService, AdminRoleInfo
} from '@cloudbeaver/core-sdk';
import { RolesResource } from './RolesResource';
@injectable()
export class RolesManagerService {
readonly roles = new CachedResource(
[],
this.refreshAsync.bind(this),
data => !!data.length
)
constructor(
private graphQLService: GraphQLService,
readonly roles: RolesResource,
) {
}
private async refreshAsync(
data: AdminRoleInfo[],
_: any,
update: boolean,
roleId?: string
): Promise<AdminRoleInfo[]> {
const { roles } = await this.graphQLService.gql.getRolesList({ roleId });
if (!roleId) {
return roles as AdminRoleInfo[];
}
const index = data.findIndex(role => role.roleId === roleId);
if (index !== -1) {
data.splice(index, 1, ...roles as AdminRoleInfo[]);
} else {
data.push(...roles as AdminRoleInfo[]);
}
return data;
}
}
@@ -0,0 +1,49 @@
/*
* 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 '@cloudbeaver/core-di';
import {
GraphQLService,
CachedDataResource,
AdminRoleInfo
} from '@cloudbeaver/core-sdk';
@injectable()
export class RolesResource extends CachedDataResource<AdminRoleInfo[], string | undefined> {
constructor(
private graphQLService: GraphQLService,
) {
super([]);
}
isLoaded(roleId?: string) {
return roleId
? this.data.some(role => role.roleId === roleId)
: !!this.data.length;
}
protected async loader(roleId?: string): Promise<AdminRoleInfo[]> {
const { roles } = await this.graphQLService.gql.getRolesList({ roleId });
if (!roleId) {
this.markUpdated(roleId);
return roles as AdminRoleInfo[];
}
const index = this.data.findIndex(role => role.roleId === roleId);
if (index !== -1) {
this.data.splice(index, 1, ...roles as AdminRoleInfo[]);
} else {
this.data.push(...roles as AdminRoleInfo[]);
}
this.markUpdated(roleId);
return this.data;
}
}
@@ -80,7 +80,7 @@ export class CreateUserDialogController implements IInitializableController, IDe
await this.usersManagerService.grantRole(user.userId, roleId);
}
}
await this.usersManagerService.users.refresh(true, user.userId);
await this.usersManagerService.users.refresh(user.userId);
this.notificationService.logInfo({ title: 'authentication_user_user_created' });
this.close();
} catch (exception) {
@@ -113,7 +113,7 @@ export class CreateUserDialogController implements IInitializableController, IDe
private async loadRoles() {
try {
await this.rolesManagerService.roles.load();
await this.rolesManagerService.roles.load(undefined);
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load roles');
}
@@ -41,7 +41,7 @@ export class UsersAdministrationController {
update = async () => {
try {
await this.usersManagerService.users.refresh(true, undefined);
await this.usersManagerService.users.refresh(undefined);
} catch (exception) {
if (!this.error.catch(exception)) {
this.notificationService.logException(exception, 'Users update failed');
@@ -78,7 +78,7 @@ export class UsersAdministrationController {
await this.usersManagerService.delete(userId);
}
this.selectedItems.clear();
await this.usersManagerService.users.refresh(true, undefined);
await this.usersManagerService.users.refresh(undefined);
} catch (exception) {
if (!this.error.catch(exception)) {
this.notificationService.logException(exception, 'User delete failed');
@@ -7,22 +7,19 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { CachedResource, GraphQLService, AdminUserInfo } from '@cloudbeaver/core-sdk';
import { GraphQLService, AdminUserInfo } from '@cloudbeaver/core-sdk';
import { AuthInfoService } from '../AuthInfoService';
import { AuthProviderService } from '../AuthProviderService';
import { UsersResource } from './UsersResource';
@injectable()
export class UsersManagerService {
readonly users = new CachedResource(
[],
this.refreshAsync.bind(this),
(data, _, userId) => (userId ? data.some(user => user.userId === userId) : !!data.length)
)
constructor(
private graphQLService: GraphQLService,
private authProviderService: AuthProviderService,
private authInfoService: AuthInfoService,
readonly users: UsersResource
) {
}
@@ -39,7 +36,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(true, userId);
await this.users.refresh(userId);
}
}
@@ -50,7 +47,7 @@ export class UsersManagerService {
await this.graphQLService.gql.deleteUser({ userId });
if (update) {
await this.users.refresh(true, userId);
await this.users.refresh(userId);
}
}
@@ -64,26 +61,4 @@ export class UsersManagerService {
credentials: processedCredentials,
});
}
private async refreshAsync(
data: AdminUserInfo[],
_: any,
update: boolean,
userId?: string
): Promise<AdminUserInfo[]> {
const { users } = await this.graphQLService.gql.getUsersList({ userId });
if (!userId) {
return users as AdminUserInfo[];
}
const index = data.findIndex(user => user.userId === userId);
if (index !== -1) {
data.splice(index, 1, ...users as AdminUserInfo[]);
} else {
data.push(...users as AdminUserInfo[]);
}
return data;
}
}
@@ -0,0 +1,49 @@
/*
* 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 '@cloudbeaver/core-di';
import {
GraphQLService,
CachedDataResource,
AdminUserInfo
} from '@cloudbeaver/core-sdk';
@injectable()
export class UsersResource extends CachedDataResource<AdminUserInfo[], string | undefined> {
constructor(
private graphQLService: GraphQLService,
) {
super([]);
}
isLoaded(userId?: string) {
return userId
? this.data.some(role => role.userId === userId)
: !!this.data.length;
}
protected async loader(userId?: string): Promise<AdminUserInfo[]> {
const { users } = await this.graphQLService.gql.getUsersList({ userId });
if (!userId) {
this.markUpdated(userId);
return users as AdminUserInfo[];
}
const index = this.data.findIndex(user => user.userId === userId);
if (index !== -1) {
this.data.splice(index, 1, ...users as AdminUserInfo[]);
} else {
this.data.push(...users as AdminUserInfo[]);
}
this.markUpdated(userId);
return this.data;
}
}
@@ -9,22 +9,18 @@
import { createHash } from 'crypto';
import { injectable } from '@cloudbeaver/core-di';
import {
GraphQLService, CachedResource, AuthProviderInfo
} from '@cloudbeaver/core-sdk';
export type AuthProvider = Omit<AuthProviderInfo, 'configurationParameters'>
import { AuthProvidersResource } from './AuthProvidersResource';
@injectable()
export class AuthProviderService {
readonly providers = new CachedResource([], this.refreshAsync.bind(this), data => !!data.length)
constructor(
private graphQLService: GraphQLService,
private providers: AuthProvidersResource,
) { }
async processCredentials(providerId: string, credentials: Record<string, any>) {
const providers = await this.providers.load();
const providers = await this.providers.load(null);
const provider = providers.find(provider => provider.id === providerId);
if (!provider) {
@@ -44,10 +40,4 @@ export class AuthProviderService {
return credentialsProcessed;
}
private async refreshAsync(data: AuthProvider[]): Promise<AuthProvider[]> {
const { providers } = await this.graphQLService.gql.getAuthProviders();
return providers;
}
}
@@ -0,0 +1,36 @@
/*
* 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 '@cloudbeaver/core-di';
import {
GraphQLService,
CachedDataResource,
AuthProviderInfo
} from '@cloudbeaver/core-sdk';
export type AuthProvider = Omit<AuthProviderInfo, 'configurationParameters'>
@injectable()
export class AuthProvidersResource extends CachedDataResource<AuthProvider[], null> {
constructor(
private graphQLService: GraphQLService,
) {
super([]);
}
isLoaded() {
return !!this.data.length;
}
protected async loader(key: null): Promise<AuthProvider[]> {
const { providers } = await this.graphQLService.gql.getAuthProviders();
this.markUpdated(key);
return providers;
}
}
@@ -21,7 +21,7 @@ export class AuthenticationService {
) { }
async auth() {
const config = await this.serverService.config.load();
const config = await this.serverService.config.load(null);
if (!config) {
throw new Error('Can\'t configure Authentication');
}
@@ -15,7 +15,7 @@ import { NotificationService } from '@cloudbeaver/core-events';
import { GQLErrorCatcher } from '@cloudbeaver/core-sdk';
import { AuthInfoService } from '../AuthInfoService';
import { AuthProviderService, AuthProvider } from '../AuthProviderService';
import { AuthProvidersResource, AuthProvider } from '../AuthProvidersResource';
@injectable()
export class AuthDialogController implements IInitializableController, IDestructibleController {
@@ -24,12 +24,11 @@ export class AuthDialogController implements IInitializableController, IDestruct
@observable credentials = {};
get isLoading() {
return this.authProviderService.providers.isLoading();
return this.authProvidersResource.isLoading();
}
@computed get providers(): AuthProvider[] {
return this.authProviderService
.providers
return this.authProvidersResource
.data
.concat()
.sort(this.compareProviders);
@@ -41,7 +40,7 @@ export class AuthDialogController implements IInitializableController, IDestruct
constructor(
private notificationService: NotificationService,
private authProviderService: AuthProviderService,
private authProvidersResource: AuthProvidersResource,
private authInfoService: AuthInfoService,
private commonDialogService: CommonDialogService,
) { }
@@ -77,8 +76,8 @@ export class AuthDialogController implements IInitializableController, IDestruct
if (providerId === this.provider?.id) {
return;
}
this.provider = this.authProviderService
.providers.data.find(provider => provider.id === providerId) || null;
this.provider = this.authProvidersResource
.data.find(provider => provider.id === providerId) || null;
this.credentials = {};
}
@@ -90,7 +89,7 @@ export class AuthDialogController implements IInitializableController, IDestruct
private async loadProviders() {
try {
await this.authProviderService.providers.load();
await this.authProvidersResource.load(null);
if (this.providers.length > 0) {
this.provider = this.providers[0];
}
@@ -13,7 +13,7 @@ import styled from 'reshadow';
import { InputField, InFocus } from '@cloudbeaver/core-blocks';
import { useStyles } from '@cloudbeaver/core-theming';
import { AuthProvider } from '../../AuthProviderService';
import { AuthProvider } from '../../AuthProvidersResource';
import { formStyles } from './formStyles';
type Props = {
@@ -5,4 +5,5 @@ export default manifest;
export * from './AuthenticationService';
export * from './AuthInfoService';
export * from './AuthProviderService';
export * from './AuthProvidersResource';
export * from './Dialog/AuthDialogService';
@@ -9,13 +9,16 @@
import { IServiceInjector, PluginManifest } from '@cloudbeaver/core-di';
import { RolesManagerService } from './Administration/RolesManagerService';
import { RolesResource } from './Administration/RolesResource';
import { UsersAdministrationService } from './Administration/Users/UsersAdministrationService';
import { UsersManagerService } from './Administration/UsersManagerService';
import { UsersResource } from './Administration/UsersResource';
import { AuthenticationLocaleService } from './AuthenticationLocaleService';
import { AuthenticationService } from './AuthenticationService';
import { AuthInfoService } from './AuthInfoService';
import { AuthMenuService } from './AuthMenuService';
import { AuthProviderService } from './AuthProviderService';
import { AuthProvidersResource } from './AuthProvidersResource';
import { Bootstrap } from './Bootstrap';
import { AuthDialogService } from './Dialog/AuthDialogService';
@@ -28,12 +31,15 @@ export const manifest: PluginManifest = {
AuthenticationService,
AuthInfoService,
AuthProviderService,
AuthProvidersResource,
AuthDialogService,
AuthMenuService,
UsersAdministrationService,
UsersManagerService,
RolesManagerService,
AuthenticationLocaleService,
RolesResource,
UsersResource,
],
async initialize(services: IServiceInjector) {
@@ -10,7 +10,7 @@ import { MainMenuService, ConnectionDialogsService } from '@cloudbeaver/core-app
import { injectable } from '@cloudbeaver/core-di';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { PermissionsService, EPermission, SessionService } from '@cloudbeaver/core-root';
import { PermissionsService, EPermission, SessionResource } from '@cloudbeaver/core-root';
import { BasicConnectionService } from './BasicConnectionService';
import { ConnectionDialog } from './ConnectionDialog/ConnectionDialog';
@@ -25,13 +25,13 @@ export class BasicConnectionPluginBootstrap {
private commonDialogService: CommonDialogService,
private notificationService: NotificationService,
private permissionsService: PermissionsService,
private sessionService: SessionService
private sessionResource: SessionResource
) {
}
bootstrap() {
this.loadDbSources();
this.sessionService.onUpdate.subscribe(this.loadDbSources.bind(this));
this.sessionResource.onDataUpdate.subscribe(this.loadDbSources.bind(this));
this.mainMenuService.registerMenuItem(
this.connectionDialogsService.newConnectionMenuToken,
{
@@ -51,7 +51,7 @@ export class BasicConnectionPluginBootstrap {
private async loadDbSources() {
try {
await this.basicConnectionService.dbSources.refresh(true);
await this.basicConnectionService.dbSources.refresh(null);
} catch (error) {
this.notificationService.logException(error, 'DBSources loading failed');
}
@@ -10,33 +10,27 @@ import {
Connection, DBSource, ConnectionsManagerService
} from '@cloudbeaver/core-app';
import { injectable } from '@cloudbeaver/core-di';
import { PermissionsService, EPermission } from '@cloudbeaver/core-root';
import { ConnectionConfig, GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
import { PermissionsService } from '@cloudbeaver/core-root';
import { ConnectionConfig, GraphQLService } from '@cloudbeaver/core-sdk';
import { DataSourcesResource } from './DataSourcesResource';
@injectable()
export class BasicConnectionService {
readonly dbSources = new CachedResource([], this.loadDBSourcesAsync.bind(this), data => !!data.length)
constructor(
private graphQLService: GraphQLService,
private connectionsManagerService: ConnectionsManagerService,
private permissionsService: PermissionsService
private permissionsService: PermissionsService,
readonly dbSources: DataSourcesResource
) {
this.permissionsService.onUpdate.subscribe(() => this.dbSources.refresh());
this.permissionsService.onUpdate.subscribe(() => this.dbSources.refresh(null));
}
public getDBSources(): DBSource[] {
return this.dbSources.data;
}
private async loadDBSourcesAsync(data: DBSource[]): Promise<DBSource[]> {
if (!await this.permissionsService.hasAsync(EPermission.public)) {
return [];
}
const { dataSourceList } = await this.graphQLService.gql.dataSourceList();
return dataSourceList;
}
async openConnectionAsync(config: ConnectionConfig): Promise<Connection> {
const response = await this.graphQLService.gql.openConnection({ config });
this.connectionsManagerService.addOpenedConnection(response.openConnection);
@@ -153,7 +153,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
private async loadDBSources() {
try {
await this.basicConnectionService.dbSources.load();
await this.basicConnectionService.dbSources.load(null);
await this.connectionsManagerService.loadDriversAsync();
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load database sources');
@@ -0,0 +1,36 @@
/*
* 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 { DBSource } from '@cloudbeaver/core-app';
import { injectable } from '@cloudbeaver/core-di';
import { EPermission, PermissionsService } from '@cloudbeaver/core-root';
import { GraphQLService, CachedDataResource } from '@cloudbeaver/core-sdk';
@injectable()
export class DataSourcesResource extends CachedDataResource<DBSource[], null> {
constructor(
private graphQLService: GraphQLService,
private permissionsService: PermissionsService
) {
super([]);
}
isLoaded() {
return !!this.data.length;
}
protected async loader(key: null): Promise<DBSource[]> {
if (!await this.permissionsService.hasAsync(EPermission.public)) {
this.markUpdated(key);
return [];
}
const { dataSourceList } = await this.graphQLService.gql.dataSourceList();
this.markUpdated(key);
return dataSourceList;
}
}
@@ -10,6 +10,7 @@ import { PluginManifest } from '@cloudbeaver/core-di';
import { BasicConnectionPluginBootstrap } from './BasicConnectionPluginBootstrap';
import { BasicConnectionService } from './BasicConnectionService';
import { DataSourcesResource } from './DataSourcesResource';
import { LocaleService } from './LocaleService';
export const basicConnectionPluginManifest: PluginManifest = {
@@ -19,6 +20,7 @@ export const basicConnectionPluginManifest: PluginManifest = {
providers: [
BasicConnectionService,
DataSourcesResource,
LocaleService,
],
@@ -8,30 +8,19 @@
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import {
CachedResource, GraphQLService, DataTransferProcessorInfo, DataTransferParameters
} from '@cloudbeaver/core-sdk';
import { DataTransferParameters } from '@cloudbeaver/core-sdk';
import { DataExportProcessService } from './DataExportProcessService';
import { DataTransferProcessorsResource } from './DataTransferProcessorsResource';
import { ExportNotification } from './ExportNotification/ExportNotification';
import { IExportContext } from './IExportContext';
type ProcessorsResourceMetadata = {
loaded: boolean;
}
@injectable()
export class DataExportService {
readonly processors = new CachedResource(
new Map(),
this.refreshProcessorsAsync.bind(this),
(_, { loaded }) => loaded
);
constructor(
private graphQLService: GraphQLService,
private notificationService: NotificationService,
private dataExportProcessService: DataExportProcessService,
readonly processors: DataTransferProcessorsResource
) { }
async cancel(exportId: string) {
@@ -62,19 +51,4 @@ export class DataExportService {
this.notificationService.customNotification(() => ExportNotification, taskId);
return taskId;
}
private async refreshProcessorsAsync(
data: Map<string, DataTransferProcessorInfo>,
metadata: ProcessorsResourceMetadata,
): Promise<Map<string, DataTransferProcessorInfo>> {
const { processors } = await this.graphQLService.gql.getDataTransferProcessors();
data.clear();
for (const processor of processors) {
data.set(processor.id, processor);
}
metadata.loaded = true;
return data;
}
}
@@ -0,0 +1,41 @@
/*
* 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 { observable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { GraphQLService, CachedDataResource, DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
@injectable()
export class DataTransferProcessorsResource extends CachedDataResource<Map<string, DataTransferProcessorInfo>, null> {
@observable private loaded = false;
constructor(
private graphQLService: GraphQLService,
) {
super(new Map());
}
isLoaded() {
return this.loaded;
}
protected async loader(key: null) {
const { processors } = await this.graphQLService.gql.getDataTransferProcessors();
this.data.clear();
for (const processor of processors) {
this.data.set(processor.id, processor);
}
this.loaded = true;
this.markUpdated(key);
return this.data;
}
}
@@ -123,7 +123,7 @@ export class DataExportController implements IInitializableController, IDestruct
private async loadProcessors() {
try {
await this.dataExportService.processors.load();
await this.dataExportService.processors.load(null);
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load data export processors');
}
@@ -12,6 +12,7 @@ import { Bootstrap } from './Bootstrap';
import { DataExportMenuService } from './DataExportMenuService';
import { DataExportProcessService } from './DataExportProcessService';
import { DataExportService } from './DataExportService';
import { DataTransferProcessorsResource } from './DataTransferProcessorsResource';
export const manifest: PluginManifest = {
info: {
@@ -22,6 +23,7 @@ export const manifest: PluginManifest = {
DataExportMenuService,
DataExportService,
DataExportProcessService,
DataTransferProcessorsResource,
],
async initialize(services: IServiceInjector) {
@@ -199,7 +199,7 @@ export class TableViewerModel {
}
updateRows(rows: number[]) {
this.agGridModel.actions.updateRows(rows);
this.agGridModel.actions?.updateRows(rows);
}
revertCellValue(rowNumber: number, column: string) {
@@ -9,7 +9,7 @@
import { autorun } from 'mobx';
import { IReactionDisposer } from 'mobx/lib/internal';
import { ITab } from '@cloudbeaver/core-app';
import { ITab, NavigationTabsService } from '@cloudbeaver/core-app';
import { IDestructibleController, IInitializableController, injectable } from '@cloudbeaver/core-di';
import { IObjectViewerTabState } from '../IObjectViewerTabState';
@@ -23,7 +23,10 @@ export class ObjectFoldersController implements IInitializableController, IDestr
private navigationTab!: ITab<IObjectViewerTabState>
private disposer!: IReactionDisposer;
constructor(private objectFoldersService: ObjectFoldersService) { }
constructor(
private objectFoldersService: ObjectFoldersService,
private navigationTabsService: NavigationTabsService
) { }
getTabContainer() {
return this.tabContainer;
@@ -36,7 +39,9 @@ export class ObjectFoldersController implements IInitializableController, IDestr
this.disposer = autorun(() => {
const currentFolderId = tab.handlerState.folderId || null;
this.activateTab(currentFolderId);
if (this.navigationTabsService.currentTabId === tab.id) {
this.activateTab(currentFolderId);
}
});
}
@@ -23,6 +23,7 @@ import {
} from '@cloudbeaver/core-app';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ResourceKey, resourceKeyList, isResourceKeyList } from '@cloudbeaver/core-sdk';
import { IObjectViewerTabContext } from './IObjectViewerTabContext';
import { IObjectViewerTabState } from './IObjectViewerTabState';
@@ -63,7 +64,8 @@ export class ObjectViewerTabService {
registerTabHandler() {
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
this.navNodeManagerService.navNode.onDataUpdate.subscribe(this.updateTabs.bind(this));
this.navNodeManagerService.navNodeInfoResource.onItemAdd.subscribe(this.updateTabs.bind(this));
this.navNodeManagerService.navNodeInfoResource.onItemDelete.subscribe(this.removeTabs.bind(this));
}
objectViewerTabContext = async (
@@ -131,22 +133,47 @@ 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 }],
});
private async updateTabs(key: ResourceKey<string>) {
if (isResourceKeyList(key)) {
for (const objectId of key.list) {
const tab = this.navigationTabsService.findTab(
isObjectViewerTab(tab => tab.handlerState.objectId === objectId)
);
if (!loaded && !loading) {
if (tab && tab.restored && this.navigationTabsService.currentTabId === tab.id) {
await this.navigationTabsService.selectTab(tab.id);
}
}
} else {
const tab = this.navigationTabsService.findTab(
isObjectViewerTab(tab => tab.handlerState.objectId === key)
);
if (tab && tab.restored && this.navigationTabsService.currentTabId === tab.id) {
await this.navigationTabsService.selectTab(tab.id);
}
}
}
private async removeTabs(key: ResourceKey<string>) {
if (isResourceKeyList(key)) {
for (const objectId of key.list) {
const tab = this.navigationTabsService.findTab(
isObjectViewerTab(tab => tab.handlerState.objectId === objectId)
);
if (tab) {
await this.navigationTabsService.closeTab(tab.id, true);
}
}
} else {
const tab = this.navigationTabsService.findTab(
isObjectViewerTab(tab => tab.handlerState.objectId === key)
);
if (tab) {
await this.navigationTabsService.closeTab(tab.id, true);
}
}
}
@@ -203,7 +230,7 @@ export class ObjectViewerTabService {
}
const folderChildren = await this.navNodeManagerService.loadTree(folderId);
await this.dbObjectService.load(folderChildren, folderId);
await this.dbObjectService.loadChildren(folderId, resourceKeyList(folderChildren));
} catch (exception) {
this.notificationService.logException(exception, 'Error in Object Viewer while tab selecting');
}
@@ -244,15 +271,6 @@ export class ObjectViewerTabService {
try {
const { tab, tabInfo, nodeInfo } = await contexts.getContext(this.objectViewerTabContext);
if (nodeInfo.type === NavigationType.closeConnection) {
for (const tab of this.navigationTabsService.findTabs(
isObjectViewerTab(tab => tab.handlerState.objectId.includes(nodeInfo.nodeId))
)) {
await this.navigationTabsService.closeTab(tab.id);
}
return;
}
if (tab) {
if (!tab.handlerState.folderId || (nodeInfo.folderId && tab.handlerState.folderId !== nodeInfo.folderId)) {
tab.handlerState.folderId = nodeInfo.folderId;
@@ -8,6 +8,7 @@
import { NavNodeManagerService, DBObjectService } from '@cloudbeaver/core-app';
import { useService } from '@cloudbeaver/core-di';
import { resourceKeyList } from '@cloudbeaver/core-sdk';
export function useObjectFolder(objectId: string) {
const navNodeManagerService = useService(NavNodeManagerService);
@@ -15,8 +16,8 @@ export function useObjectFolder(objectId: string) {
const children = navNodeManagerService.getTree(objectId) || [];
const isLoading = children.some(navNodeId => !dbObjectService.getDBObject(navNodeId))
&& dbObjectService.dbObject.isDataLoading({ navNodeId: children });
const isLoading = !dbObjectService.isLoaded(resourceKeyList(children))
&& dbObjectService.isDataLoading(resourceKeyList(children));
return { isLoading };
}
@@ -39,11 +39,6 @@ export class SqlEditorBootstrap {
title: 'SQL',
order: 2,
onClick: () => {
console.log(
this.connectionSchemaManagerService.currentConnectionId,
this.connectionSchemaManagerService.currentObjectCatalogId,
this.connectionSchemaManagerService.currentObjectSchemaId
);
this.sqlEditorNavigatorService.openNewEditor(
this.connectionSchemaManagerService.currentConnectionId,
this.connectionSchemaManagerService.currentObjectCatalogId,
@@ -14,9 +14,6 @@ import {
NavigationService,
IContextProvider,
ITabOptions,
NavNodeManagerService,
INodeNavigationData,
NavigationType,
} from '@cloudbeaver/core-app';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
@@ -63,7 +60,6 @@ export class SqlEditorNavigatorService {
private notificationService: NotificationService,
private gql: GraphQLService,
private sqlDialectInfoService: SqlDialectInfoService,
private navNodeManagerService: NavNodeManagerService,
private navigationService: NavigationService
) {
@@ -71,7 +67,7 @@ export class SqlEditorNavigatorService {
null,
this.navigateHandler.bind(this)
);
this.navNodeManagerService.navigator.addHandler(this.nodeNavigationHandler.bind(this));
this.connectionsManagerService.onCloseConnection.subscribe(this.nodeNavigationHandler.bind(this));
}
registerTabHandler() {
@@ -102,18 +98,14 @@ export class SqlEditorNavigatorService {
});
}
private async nodeNavigationHandler(contexts: IContextProvider<INodeNavigationData>) {
private async nodeNavigationHandler(connectionId: string) {
try {
const nodeInfo = await contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
if (nodeInfo.type === NavigationType.closeConnection) {
for (const tab of this.navigationTabsService.findTabs(
isSQLEditorTab(tab => tab.handlerState.connectionId.includes(nodeInfo.nodeId))
)) {
await this.navigationTabsService.closeTab(tab.id);
}
return;
for (const tab of this.navigationTabsService.findTabs(
isSQLEditorTab(tab => tab.handlerState.connectionId.includes(connectionId))
)) {
await this.navigationTabsService.closeTab(tab.id);
}
return;
} catch (exception) {
this.notificationService.logException(exception, 'Error in Object Viewer while processing action with database node');
}
@@ -163,7 +163,7 @@ export class SqlEditorTabService {
try {
await this.gql.gql.sqlContextDestroy({ connectionId, contextId });
} catch (exception) {
this.notificationService.logException(exception, `Failed to destroy SQL-context ${contextId}`);
this.notificationService.logException(exception, `Failed to destroy SQL-context ${contextId}`, true);
}
}