From 161b9d479b4475bd228b68150c179676d24626df Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 7 Jul 2020 20:56:34 +0300 Subject: [PATCH 1/2] refactor: data management behavior CB-195 --- .../src/NavigationTree/NavigationTree.tsx | 2 +- .../NavigationTreeNode/useNavigationTree.ts | 6 +- .../NavigationTree/NavigationTreeService.ts | 3 +- .../ConnectionSchemaManagerService.ts | 5 +- .../ConnectionSelectorController.ts | 3 +- .../SettingsMenu/SettingsMenuService.ts | 6 +- webapp/packages/core-app/src/index.ts | 5 + .../ConnectionInfoResource.ts | 30 ++ .../ConnectionsManagerService.ts | 206 ++------------ .../ConnectionsManager/ContainerResource.ts | 42 +++ .../ConnectionsManager/DBDriverResource.ts | 53 ++++ .../ConnectionsManager/useConnectionInfo.ts | 4 +- .../NavigationTabs/NavigationTabsService.ts | 2 +- .../shared/NodesManager/DBObjectService.ts | 160 ++++------- .../NodesManager/NavNodeInfoResource.ts | 56 ++++ .../NodesManager/NavNodeManagerService.ts | 263 +++--------------- .../shared/NodesManager/NavTreeResource.ts | 149 ++++++++++ .../NodesManager/useDatabaseObjectInfo.ts | 6 +- .../src/shared/NodesManager/useNode.ts | 17 +- .../core-app/src/shared/useChildren.ts | 9 +- .../packages/core-bootstrap/src/bootstrap.ts | 1 + .../packages/core-bootstrap/src/manifest.ts | 21 +- .../src/LocalizationService.ts | 4 +- .../core-root/src/PermissionsResource.ts | 43 +++ .../core-root/src/PermissionsService.ts | 42 +-- .../core-root/src/ServerConfigResource.ts | 29 ++ .../packages/core-root/src/ServerService.ts | 20 +- .../packages/core-root/src/SessionResource.ts | 38 +++ .../packages/core-root/src/SessionService.ts | 29 +- webapp/packages/core-root/src/index.ts | 7 +- .../core-sdk/src/CachedDataResource.ts | 29 ++ .../core-sdk/src/CachedMapResource.ts | 153 ++++++++++ .../packages/core-sdk/src/CachedResource.ts | 139 ++++----- webapp/packages/core-sdk/src/index.ts | 2 + .../src/Administration/RolesManagerService.ts | 34 +-- .../src/Administration/RolesResource.ts | 46 +++ .../CreateUserDialogController.ts | 4 +- .../Users/UsersAdministrationController.ts | 4 +- .../src/Administration/UsersManagerService.ts | 35 +-- .../src/Administration/UsersResource.ts | 46 +++ .../src/AuthProviderService.ts | 16 +- .../src/AuthProvidersResource.ts | 35 +++ .../src/AuthenticationService.ts | 2 +- .../src/Dialog/AuthDialogController.ts | 15 +- .../AuthProviderForm/AuthProviderForm.tsx | 2 +- .../plugin-authentication/src/index.ts | 1 + .../plugin-authentication/src/manifest.ts | 6 + .../src/BasicConnectionPluginBootstrap.ts | 8 +- .../src/BasicConnectionService.ts | 20 +- .../ConnectionDialog/ConnectionController.ts | 2 +- .../src/DataSourcesResource.ts | 34 +++ .../src/manifest.ts | 2 + .../src/DataExportService.ts | 32 +-- .../src/DataTransferProcessorsResource.ts | 40 +++ .../src/Dialog/DataExportController.ts | 2 +- .../plugin-data-export/src/manifest.ts | 2 + .../src/TableViewer/TableViewerModel.ts | 2 +- .../ObjectFoldersController.ts | 11 +- .../src/ObjectViewerTabService.ts | 64 +++-- .../src/useObjectFolder.ts | 5 +- .../src/SqlEditorBootstrap.ts | 5 - .../src/SqlEditorNavigatorService.ts | 22 +- .../src/SqlEditorTabService.ts | 2 +- 63 files changed, 1193 insertions(+), 890 deletions(-) create mode 100644 webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionInfoResource.ts create mode 100644 webapp/packages/core-app/src/shared/ConnectionsManager/ContainerResource.ts create mode 100644 webapp/packages/core-app/src/shared/ConnectionsManager/DBDriverResource.ts create mode 100644 webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts create mode 100644 webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts create mode 100644 webapp/packages/core-root/src/PermissionsResource.ts create mode 100644 webapp/packages/core-root/src/ServerConfigResource.ts create mode 100644 webapp/packages/core-root/src/SessionResource.ts create mode 100644 webapp/packages/core-sdk/src/CachedDataResource.ts create mode 100644 webapp/packages/core-sdk/src/CachedMapResource.ts create mode 100644 webapp/packages/plugin-authentication/src/Administration/RolesResource.ts create mode 100644 webapp/packages/plugin-authentication/src/Administration/UsersResource.ts create mode 100644 webapp/packages/plugin-authentication/src/AuthProvidersResource.ts create mode 100644 webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts create mode 100644 webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx index 869a4910ec..8eaaab0864 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx @@ -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'; diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationTree.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationTree.ts index 9d7f6fb03c..b9b0c83ce9 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationTree.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/useNavigationTree.ts @@ -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)) { diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts index 474a40ad75..7191a8df7c 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts @@ -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() diff --git a/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts b/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts index 838bee9132..9ffbf15a6c 100644 --- a/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts +++ b/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService.ts @@ -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); } diff --git a/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelectorController.ts b/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelectorController.ts index 5eabbb47d9..cb756eef15 100644 --- a/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelectorController.ts +++ b/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelectorController.ts @@ -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'; diff --git a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenuService.ts b/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenuService.ts index 5e2b3c4a01..0ea78d8d64 100644 --- a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenuService.ts +++ b/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenuService.ts @@ -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; diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index 496981d484..615e548e32 100644 --- a/webapp/packages/core-app/src/index.ts +++ b/webapp/packages/core-app/src/index.ts @@ -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'; diff --git a/webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionInfoResource.ts b/webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionInfoResource.ts new file mode 100644 index 0000000000..2d0395d3cd --- /dev/null +++ b/webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionInfoResource.ts @@ -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 + +@injectable() +export class ConnectionInfoResource extends CachedMapResource { + constructor(private graphQLService: GraphQLService) { + super(new Map()); + } + + protected async loader(connectionId: string): Promise> { + const { connection } = await this.graphQLService.gql.connectionState({ id: connectionId }); + this.set(connectionId, connection); + + return this.data; + } +} diff --git a/webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionsManagerService.ts b/webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionsManagerService.ts index 2167819cd4..5fbe3194bb 100644 --- a/webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionsManagerService.ts +++ b/webapp/packages/core-app/src/shared/ConnectionsManager/ConnectionsManagerService.ts @@ -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 -export type Connection = Pick -export type ObjectContainer = Pick - -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(() => ({ 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 { @@ -107,28 +49,20 @@ export class ConnectionsManagerService { } async loadConnectionInfoAsync(connectionId: string): Promise { - const connections = await this.connectionInfo.load({ connectionId }); - - return connections.get(connectionId)!; + return this.connectionInfo.load(connectionId); } async refreshConnectionInfoAsync(connectionId: string): Promise { - const connections = await this.connectionInfo.refresh(true, { connectionId }); - - return connections.get(connectionId)!; + return this.connectionInfo.refresh(connectionId); } async loadDriversAsync(): Promise> { - 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 { 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 { - 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, - metadata: {}, - connectionId: string, - catalogId?: string, - ) { - return data.has(connectionId); - } - - private async refreshObjectContainersAsync( - data: Map, - metadata: {}, - update: boolean, - connectionId: string, - catalogId?: string, - ): Promise> { - const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({ - connectionId, - catalogId, - }); - data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]); - - return data; - } - - private async refreshDriversAsync( - data: Map, - metadata: DBDriversMetadata, - update: boolean - ): Promise> { - 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, - metadata: MetadataMap, - load: boolean, - args: ConnectionLoadArgs | ConnectionSetArgs, - ): Promise> { - 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); } } diff --git a/webapp/packages/core-app/src/shared/ConnectionsManager/ContainerResource.ts b/webapp/packages/core-app/src/shared/ConnectionsManager/ContainerResource.ts new file mode 100644 index 0000000000..897e7da4fb --- /dev/null +++ b/webapp/packages/core-app/src/shared/ConnectionsManager/ContainerResource.ts @@ -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 + +type ObjectContainerParams = { + connectionId: string; + catalogId?: string; +} + +@injectable() +export class ContainerResource extends CachedDataResource, ObjectContainerParams> { + constructor(private graphQLService: GraphQLService) { + super(new Map()); + } + + isLoaded({ connectionId }: ObjectContainerParams) { + return this.data.has(connectionId); + } + + protected async loader({ connectionId, catalogId }: ObjectContainerParams): Promise> { + const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({ + connectionId, + catalogId, + }); + this.data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]); + + return this.data; + } +} diff --git a/webapp/packages/core-app/src/shared/ConnectionsManager/DBDriverResource.ts b/webapp/packages/core-app/src/shared/ConnectionsManager/DBDriverResource.ts new file mode 100644 index 0000000000..5460eaf9ba --- /dev/null +++ b/webapp/packages/core-app/src/shared/ConnectionsManager/DBDriverResource.ts @@ -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 { + @observable private loaded = false; + constructor(private graphQLService: GraphQLService) { + super(new Map()); + } + + isLoaded() { + return this.loaded; + } + + protected async loader(param: string): Promise> { + 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; + } +} diff --git a/webapp/packages/core-app/src/shared/ConnectionsManager/useConnectionInfo.ts b/webapp/packages/core-app/src/shared/ConnectionsManager/useConnectionInfo.ts index 2832ff7505..5545014515 100644 --- a/webapp/packages/core-app/src/shared/ConnectionsManager/useConnectionInfo.ts +++ b/webapp/packages/core-app/src/shared/ConnectionsManager/useConnectionInfo.ts @@ -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, diff --git a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts index 8624a139c7..d2aae4d98a 100644 --- a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts +++ b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts @@ -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) { diff --git a/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts b/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts index 729599bbdd..240f595c6f 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts @@ -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(() => ({ 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 { + 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 - async load(navNodeId: string | string[], parentId?: string): Promise { - const dbObject = await this.dbObject.load({ - navNodeId: Array.isArray(navNodeId) ? navNodeId : [navNodeId], - parentId, + async loadChildren(parentId: string, key: ResourceKey) { + 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) { + 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, - metadata: MetadataMap, - load: boolean, - data: IDBObjectParams | IDBObjectValueParams - ) { + private async loadDBObjectInfo(navNodeId: string): Promise { + 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 }; } } diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts new file mode 100644 index 0000000000..797e57800f --- /dev/null +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts @@ -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 +& { object?: Pick }; + +export const ROOT_NODE_PATH = ''; + +@injectable() +export class NavNodeInfoResource extends CachedMapResource { + constructor(private graphQLService: GraphQLService) { + super(new Map()); + } + + protected async loader(key: ResourceKey) { + 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 { + const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({ + nodePath, + }); + + return this.navNodeInfoToNavNode(navNodeInfo); + } +} diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts index 0664f79350..3b0c54da07 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts @@ -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(() => ({ 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(() => ({ loaded: false, loading: false })), - (_, metadata, parentId) => metadata.get(parentId).loading - ) readonly navigator!: INavigator; constructor( private graphQLService: GraphQLService, private navigationService: NavigationService, private permissionsService: PermissionsService, - private dbObjectService: DBObjectService, + readonly navTree: NavTreeResource, + readonly navNodeInfoResource: NavNodeInfoResource ) { this.navigator = this.navigationService.createNavigator( @@ -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 async loadNode(...nodes: NavNodeKey[]): Promise 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) { } - - private async loadNavNodeInfo( - navNode: Map, - metadata: MetadataMap, - 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, - metadata: MetadataMap, - 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; - } } diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts new file mode 100644 index 0000000000..a5e005c571 --- /dev/null +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -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 { + 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, value: string[][]): void; + set(keyObject: ResourceKey, 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): void; + delete(key: ResourceKey): void; + delete(key: ResourceKey) { + 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) { + 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) { + 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 }; + } +} diff --git a/webapp/packages/core-app/src/shared/NodesManager/useDatabaseObjectInfo.ts b/webapp/packages/core-app/src/shared/NodesManager/useDatabaseObjectInfo.ts index b563bbd53f..cc15dd5db2 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/useDatabaseObjectInfo.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/useDatabaseObjectInfo.ts @@ -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 }; } diff --git a/webapp/packages/core-app/src/shared/NodesManager/useNode.ts b/webapp/packages/core-app/src/shared/NodesManager/useNode.ts index 96a5bc2b73..a0f103dee9 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/useNode.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/useNode.ts @@ -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, }; } diff --git a/webapp/packages/core-app/src/shared/useChildren.ts b/webapp/packages/core-app/src/shared/useChildren.ts index 6576e3dbb3..ce1f94ceda 100644 --- a/webapp/packages/core-app/src/shared/useChildren.ts +++ b/webapp/packages/core-app/src/shared/useChildren.ts @@ -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, }; } diff --git a/webapp/packages/core-bootstrap/src/bootstrap.ts b/webapp/packages/core-bootstrap/src/bootstrap.ts index fdaba5c72c..3eac5e81b2 100644 --- a/webapp/packages/core-bootstrap/src/bootstrap.ts +++ b/webapp/packages/core-bootstrap/src/bootstrap.ts @@ -25,5 +25,6 @@ export async function bootstrap(plugins: PluginManifest[]) { } catch (e) { console.log(e); showErrorPage(); + throw e; } } diff --git a/webapp/packages/core-bootstrap/src/manifest.ts b/webapp/packages/core-bootstrap/src/manifest.ts index bd80117f9f..71a9ee694e 100644 --- a/webapp/packages/core-bootstrap/src/manifest.ts +++ b/webapp/packages/core-bootstrap/src/manifest.ts @@ -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, diff --git a/webapp/packages/core-localization/src/LocalizationService.ts b/webapp/packages/core-localization/src/LocalizationService.ts index c76b605897..9f27570e09 100644 --- a/webapp/packages/core-localization/src/LocalizationService.ts +++ b/webapp/packages/core-localization/src/LocalizationService.ts @@ -70,7 +70,7 @@ export class LocalizationService extends Bootstrap { } async load(): Promise { - 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'); diff --git a/webapp/packages/core-root/src/PermissionsResource.ts b/webapp/packages/core-root/src/PermissionsResource.ts new file mode 100644 index 0000000000..a5624dff16 --- /dev/null +++ b/webapp/packages/core-root/src/PermissionsResource.ts @@ -0,0 +1,43 @@ +/* + * 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, 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(): Promise> { + const { permissions } = await this.graphQLService.gql.sessionPermissions(); + + this.data.clear(); + for (const permission of permissions) { + this.data.add(permission); + } + this.loaded = true; + + return this.data; + } +} diff --git a/webapp/packages/core-root/src/PermissionsService.ts b/webapp/packages/core-root/src/PermissionsService.ts index cbab279686..6ea7e28fb2 100644 --- a/webapp/packages/core-root/src/PermissionsService.ts +++ b/webapp/packages/core-root/src/PermissionsService.ts @@ -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; private updateSubject: Subject; - 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 { - 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, - metadata: PermissionsMetadata - ): Promise> { - const { permissions } = await this.graphQLService.gql.sessionPermissions(); - - data.clear(); - for (const permission of permissions) { - data.set(permission, permission); - } - metadata.loaded = true; - - return data; - } } diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts new file mode 100644 index 0000000000..2d0523170f --- /dev/null +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -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 { GraphQLService, CachedDataResource, ServerConfig } from '@cloudbeaver/core-sdk'; + +@injectable() +export class ServerConfigResource extends CachedDataResource { + constructor( + private graphQLService: GraphQLService, + ) { + super(null); + } + + isLoaded() { + return !!this.data; + } + + protected async loader(): Promise { + const { serverConfig } = await this.graphQLService.gql.serverConfig(); + + return serverConfig; + } +} diff --git a/webapp/packages/core-root/src/ServerService.ts b/webapp/packages/core-root/src/ServerService.ts index 988b71e9e7..64b37145c2 100644 --- a/webapp/packages/core-root/src/ServerService.ts +++ b/webapp/packages/core-root/src/ServerService.ts @@ -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 { - 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; } } diff --git a/webapp/packages/core-root/src/SessionResource.ts b/webapp/packages/core-root/src/SessionResource.ts new file mode 100644 index 0000000000..5a29f87fa1 --- /dev/null +++ b/webapp/packages/core-root/src/SessionResource.ts @@ -0,0 +1,38 @@ +/* + * 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 & { + connections: Array>; +}; + +@injectable() +export class SessionResource extends CachedDataResource { + constructor( + private graphQLService: GraphQLService, + ) { + super(null); + } + + isLoaded() { + return !!this.data; + } + + protected async loader(): Promise { + const { session } = await this.graphQLService.gql.openSession(); + + return session; + } +} diff --git a/webapp/packages/core-root/src/SessionService.ts b/webapp/packages/core-root/src/SessionService.ts index ccabf091b8..8e60bd07f5 100644 --- a/webapp/packages/core-root/src/SessionService.ts +++ b/webapp/packages/core-root/src/SessionService.ts @@ -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 & { - connections: Array>; -}; - @injectable() export class SessionService { - readonly session = new CachedResource(undefined, this.refreshSessionStateAsync.bind(this), data => !!data); readonly settings = new SessionSettingsService('session_settings'); - readonly onUpdate: Observable; - private updateSubject: Subject; - - 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 { - const { session } = await this.graphQLService.gql.openSession(); - - this.updateSubject.next(); - return session; + await this.session.refresh(null); } } diff --git a/webapp/packages/core-root/src/index.ts b/webapp/packages/core-root/src/index.ts index bf4cd53b0e..62a7479682 100644 --- a/webapp/packages/core-root/src/index.ts +++ b/webapp/packages/core-root/src/index.ts @@ -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'; diff --git a/webapp/packages/core-sdk/src/CachedDataResource.ts b/webapp/packages/core-sdk/src/CachedDataResource.ts new file mode 100644 index 0000000000..a5c283d394 --- /dev/null +++ b/webapp/packages/core-sdk/src/CachedDataResource.ts @@ -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 { + + async refresh(param: TParam): Promise { + this.markOutdated(param); + await this.loadData(param); + return this.data; + } + + async load(param: TParam): Promise { + await this.loadData(param); + return this.data; + } +} diff --git a/webapp/packages/core-sdk/src/CachedMapResource.ts b/webapp/packages/core-sdk/src/CachedMapResource.ts new file mode 100644 index 0000000000..f73b124ff8 --- /dev/null +++ b/webapp/packages/core-sdk/src/CachedMapResource.ts @@ -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 = { + list: TKey[]; + [RESOURCE_KEY_LIST]: true; +} + +export type ResourceKey = TKey | ResourceKeyList; + +@injectable() +export abstract class CachedMapResource extends CachedResource< + Map, + ResourceKey +> +{ + readonly onItemAdd: Observable>; + readonly onItemDelete: Observable>; + + protected itemAddSubject: Subject>; + protected itemDeleteSubject: Subject>; + + constructor(defaultValue?: Map) { + 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): boolean { + if (isResourceKeyList(key)) { + return key.list.some(key => this.outdated.has(key)); + } + return this.outdated.has(key); + } + + markOutdated(key: ResourceKey): 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): void { + if (isResourceKeyList(key)) { + for (const itemKey of key.list) { + this.outdated.delete(itemKey); + } + } else { + this.outdated.delete(key); + } + } + + isLoaded(key: ResourceKey): boolean { + if (isResourceKeyList(key)) { + return key.list.every(key => this.data.has(key)); + } + return this.data.has(key); + } + + isDataLoading(key: ResourceKey): 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): Array; + get(key: ResourceKey): Array| TValue | undefined; + get(key: ResourceKey): Array| 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, value: TValue[]): void; + set(key: ResourceKey, 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): void; + delete(key: ResourceKey): void; + delete(key: ResourceKey) { + 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; + async refresh(key: ResourceKeyList): Promise>; + async refresh(key: ResourceKey): Promise| TValue>; + async refresh(key: ResourceKey): Promise| TValue> { + this.markOutdated(key); + await this.loadData(key); + return this.get(key) as Array| TValue; + } + + async load(key: TKey): Promise; + async load(key: ResourceKeyList): Promise>; + async load(key: ResourceKey): Promise| TValue>; + async load(key: ResourceKey): Promise| TValue> { + await this.loadData(key); + return this.get(key) as Array| TValue; + } +} + +export function isResourceKeyList(data: any): data is ResourceKeyList { + return data && typeof data === 'object' && RESOURCE_KEY_LIST in data; +} + +export function resourceKeyList(list: T[]): ResourceKeyList { + return { [RESOURCE_KEY_LIST]: true, list }; +} diff --git a/webapp/packages/core-sdk/src/CachedResource.ts b/webapp/packages/core-sdk/src/CachedResource.ts index d211c6b7ff..ad8761df7a 100644 --- a/webapp/packages/core-sdk/src/CachedResource.ts +++ b/webapp/packages/core-sdk/src/CachedResource.ts @@ -9,117 +9,98 @@ import { observable } from 'mobx'; import { Subject, Observable } from 'rxjs'; -export type Loader = ( - current: TData, - metadata: TMetadata, - update: boolean, - ...args: TArgs -) => Promise +import { injectable } from '@cloudbeaver/core-di'; -export type IsLoaded = ( - current: TData, - metadata: TMetadata, - ...args: TArgs -) => boolean - -export type IsLoading = ( - current: TData, - metadata: TMetadata, - ...args: TArgs -) => boolean - -export class CachedResource { +@injectable() +export abstract class CachedResource< + TData, + TParam, +> { @observable data: TData; - @observable private loading = false; - @observable private metadata: TMetadata; - readonly onDataUpdate: Observable; - private dataSubject: Subject; - private loader: Loader; - private isLoadedCheck: IsLoaded; - private isLoadingCheck?: IsLoading; - private activePromise: Promise | null = null; + readonly onDataOutdated: Observable; - constructor( - defaultValue: TData, - loader: Loader, - isLoadedCheck: IsLoaded, - metadata?: TMetadata, - isLoadingCheck?: IsLoading, - ) { + @observable protected outdated = new Set(); + @observable protected loading = false; + protected outdatedSubject: Subject; + protected dataSubject: Subject; + @observable protected activePromiseParam: TParam | null = null; + private activePromise: Promise | 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(); + 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 { + this.markOutdated(param); + await this.loadData(param); + return this.data; + } + + async load(param: TParam): Promise { + await this.loadData(param); + return this.data; + } + + protected abstract loader(param: TParam): Promise; + + protected async loadData(param: TParam) { + if (this.isLoaded(param) && !this.isOutdated(param)) { + return; } - return this.loading; - } - async refresh(load = false, ...args: TArgs): Promise { - return this.loadData(load, true, args); - } - - async load(...args: TArgs): Promise { - return this.loadData(true, false, args); - } - - async refreshUnblocked(load = false, ...args: TArgs): Promise { - return this.loadData(load, true, args, true); - } - - async loadUnblocked(...args: TArgs): Promise { - 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) { + 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 { + 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; diff --git a/webapp/packages/core-sdk/src/index.ts b/webapp/packages/core-sdk/src/index.ts index 2b9ebec5ed..5dd82df9cd 100644 --- a/webapp/packages/core-sdk/src/index.ts +++ b/webapp/packages/core-sdk/src/index.ts @@ -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'; diff --git a/webapp/packages/plugin-authentication/src/Administration/RolesManagerService.ts b/webapp/packages/plugin-authentication/src/Administration/RolesManagerService.ts index 3396836145..258be67b8e 100644 --- a/webapp/packages/plugin-authentication/src/Administration/RolesManagerService.ts +++ b/webapp/packages/plugin-authentication/src/Administration/RolesManagerService.ts @@ -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 { - 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; - } } diff --git a/webapp/packages/plugin-authentication/src/Administration/RolesResource.ts b/webapp/packages/plugin-authentication/src/Administration/RolesResource.ts new file mode 100644 index 0000000000..8d7bc28cb6 --- /dev/null +++ b/webapp/packages/plugin-authentication/src/Administration/RolesResource.ts @@ -0,0 +1,46 @@ +/* + * 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 { + 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 { + const { roles } = await this.graphQLService.gql.getRolesList({ roleId }); + + if (!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[]); + } + + return this.data; + } +} diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/CreateUserDialog/CreateUserDialogController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/CreateUserDialog/CreateUserDialogController.ts index f2707cbfcd..6f1748ecbb 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/CreateUserDialog/CreateUserDialogController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/CreateUserDialog/CreateUserDialogController.ts @@ -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'); } diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts index 89bdba53f9..eb012888c8 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts @@ -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'); diff --git a/webapp/packages/plugin-authentication/src/Administration/UsersManagerService.ts b/webapp/packages/plugin-authentication/src/Administration/UsersManagerService.ts index 29b14a122b..bdc85a0e95 100644 --- a/webapp/packages/plugin-authentication/src/Administration/UsersManagerService.ts +++ b/webapp/packages/plugin-authentication/src/Administration/UsersManagerService.ts @@ -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 { - 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; - } } diff --git a/webapp/packages/plugin-authentication/src/Administration/UsersResource.ts b/webapp/packages/plugin-authentication/src/Administration/UsersResource.ts new file mode 100644 index 0000000000..fdc9770eb3 --- /dev/null +++ b/webapp/packages/plugin-authentication/src/Administration/UsersResource.ts @@ -0,0 +1,46 @@ +/* + * 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 { + 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 { + const { users } = await this.graphQLService.gql.getUsersList({ userId }); + + if (!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[]); + } + + return this.data; + } +} diff --git a/webapp/packages/plugin-authentication/src/AuthProviderService.ts b/webapp/packages/plugin-authentication/src/AuthProviderService.ts index e5c894e455..ac17b77178 100644 --- a/webapp/packages/plugin-authentication/src/AuthProviderService.ts +++ b/webapp/packages/plugin-authentication/src/AuthProviderService.ts @@ -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 +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) { - 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 { - const { providers } = await this.graphQLService.gql.getAuthProviders(); - - return providers; - } } diff --git a/webapp/packages/plugin-authentication/src/AuthProvidersResource.ts b/webapp/packages/plugin-authentication/src/AuthProvidersResource.ts new file mode 100644 index 0000000000..c6e68ea380 --- /dev/null +++ b/webapp/packages/plugin-authentication/src/AuthProvidersResource.ts @@ -0,0 +1,35 @@ +/* + * 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 + +@injectable() +export class AuthProvidersResource extends CachedDataResource { + constructor( + private graphQLService: GraphQLService, + ) { + super([]); + } + + isLoaded() { + return !!this.data.length; + } + + protected async loader(): Promise { + const { providers } = await this.graphQLService.gql.getAuthProviders(); + + return providers; + } +} diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index 2779c96f30..9805d8616e 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -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'); } diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts index 9502fc7b9e..6b59c1835d 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts @@ -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]; } diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx index 15b6a07ccc..d8a0585673 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx @@ -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 = { diff --git a/webapp/packages/plugin-authentication/src/index.ts b/webapp/packages/plugin-authentication/src/index.ts index 86b1aba5f9..935afac647 100644 --- a/webapp/packages/plugin-authentication/src/index.ts +++ b/webapp/packages/plugin-authentication/src/index.ts @@ -5,4 +5,5 @@ export default manifest; export * from './AuthenticationService'; export * from './AuthInfoService'; export * from './AuthProviderService'; +export * from './AuthProvidersResource'; export * from './Dialog/AuthDialogService'; diff --git a/webapp/packages/plugin-authentication/src/manifest.ts b/webapp/packages/plugin-authentication/src/manifest.ts index f5c16e575a..9ac0466371 100644 --- a/webapp/packages/plugin-authentication/src/manifest.ts +++ b/webapp/packages/plugin-authentication/src/manifest.ts @@ -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) { diff --git a/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionPluginBootstrap.ts index 74af8eb19c..936dd861ec 100644 --- a/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionPluginBootstrap.ts @@ -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'); } diff --git a/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionService.ts b/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionService.ts index 66605d1efe..c6cdb7eb21 100644 --- a/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionService.ts +++ b/webapp/packages/plugin-connection-preconfigured/src/BasicConnectionService.ts @@ -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 { - if (!await this.permissionsService.hasAsync(EPermission.public)) { - return []; - } - const { dataSourceList } = await this.graphQLService.gql.dataSourceList(); - return dataSourceList; - } - async openConnectionAsync(config: ConnectionConfig): Promise { const response = await this.graphQLService.gql.openConnection({ config }); this.connectionsManagerService.addOpenedConnection(response.openConnection); diff --git a/webapp/packages/plugin-connection-preconfigured/src/ConnectionDialog/ConnectionController.ts b/webapp/packages/plugin-connection-preconfigured/src/ConnectionDialog/ConnectionController.ts index 42ccbbdd08..f76a4ad129 100644 --- a/webapp/packages/plugin-connection-preconfigured/src/ConnectionDialog/ConnectionController.ts +++ b/webapp/packages/plugin-connection-preconfigured/src/ConnectionDialog/ConnectionController.ts @@ -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'); diff --git a/webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts b/webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts new file mode 100644 index 0000000000..b4273761b4 --- /dev/null +++ b/webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts @@ -0,0 +1,34 @@ +/* + * 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 { + constructor( + private graphQLService: GraphQLService, + private permissionsService: PermissionsService + ) { + super([]); + } + + isLoaded() { + return !!this.data.length; + } + + protected async loader(): Promise { + if (!await this.permissionsService.hasAsync(EPermission.public)) { + return []; + } + const { dataSourceList } = await this.graphQLService.gql.dataSourceList(); + return dataSourceList; + } +} diff --git a/webapp/packages/plugin-connection-preconfigured/src/manifest.ts b/webapp/packages/plugin-connection-preconfigured/src/manifest.ts index 1237827819..291cb4d600 100644 --- a/webapp/packages/plugin-connection-preconfigured/src/manifest.ts +++ b/webapp/packages/plugin-connection-preconfigured/src/manifest.ts @@ -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, ], diff --git a/webapp/packages/plugin-data-export/src/DataExportService.ts b/webapp/packages/plugin-data-export/src/DataExportService.ts index 4f22b6f5c6..e6a8945c71 100644 --- a/webapp/packages/plugin-data-export/src/DataExportService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportService.ts @@ -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, - metadata: ProcessorsResourceMetadata, - ): Promise> { - const { processors } = await this.graphQLService.gql.getDataTransferProcessors(); - - data.clear(); - - for (const processor of processors) { - data.set(processor.id, processor); - } - metadata.loaded = true; - return data; - } } diff --git a/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts new file mode 100644 index 0000000000..9d93f9c253 --- /dev/null +++ b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts @@ -0,0 +1,40 @@ +/* + * 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, null> { + @observable private loaded = false; + + constructor( + private graphQLService: GraphQLService, + ) { + super(new Map()); + } + + isLoaded() { + return this.loaded; + } + + protected async loader() { + const { processors } = await this.graphQLService.gql.getDataTransferProcessors(); + + this.data.clear(); + + for (const processor of processors) { + this.data.set(processor.id, processor); + } + this.loaded = true; + + return this.data; + } +} diff --git a/webapp/packages/plugin-data-export/src/Dialog/DataExportController.ts b/webapp/packages/plugin-data-export/src/Dialog/DataExportController.ts index 6e098c7ece..a44f3d1f41 100644 --- a/webapp/packages/plugin-data-export/src/Dialog/DataExportController.ts +++ b/webapp/packages/plugin-data-export/src/Dialog/DataExportController.ts @@ -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'); } diff --git a/webapp/packages/plugin-data-export/src/manifest.ts b/webapp/packages/plugin-data-export/src/manifest.ts index c6653df0b4..e423d44549 100644 --- a/webapp/packages/plugin-data-export/src/manifest.ts +++ b/webapp/packages/plugin-data-export/src/manifest.ts @@ -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) { diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts index 43401b811e..0656c5e806 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts @@ -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) { diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFoldersController.ts b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFoldersController.ts index 84f6212b0f..7fa2c32a3e 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFoldersController.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFoldersController.ts @@ -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 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); + } }); } diff --git a/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts b/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts index 3c6d54752c..a45f597d00 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts @@ -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) { - 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) { + 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) { + 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; diff --git a/webapp/packages/plugin-object-viewer/src/useObjectFolder.ts b/webapp/packages/plugin-object-viewer/src/useObjectFolder.ts index d9b4044901..78e5bb71cd 100644 --- a/webapp/packages/plugin-object-viewer/src/useObjectFolder.ts +++ b/webapp/packages/plugin-object-viewer/src/useObjectFolder.ts @@ -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 }; } diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts index a617a73922..548c1f9137 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts @@ -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, diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts index b3691cdfc5..d0a2e83a72 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts @@ -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) { + 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'); } diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts index 0f99c1e23f..6f901e599c 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts @@ -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); } } From ea5842d2ebbacb3827526d45927dc7571ddb3bf6 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 8 Jul 2020 12:53:47 +0300 Subject: [PATCH 2/2] fix: infinity data refreshes --- webapp/packages/core-root/src/PermissionsResource.ts | 3 ++- webapp/packages/core-root/src/ServerConfigResource.ts | 3 ++- webapp/packages/core-root/src/SessionResource.ts | 3 ++- .../plugin-authentication/src/Administration/RolesResource.ts | 3 +++ .../plugin-authentication/src/Administration/UsersResource.ts | 3 +++ .../plugin-authentication/src/AuthProvidersResource.ts | 3 ++- .../src/DataSourcesResource.ts | 4 +++- .../plugin-data-export/src/DataTransferProcessorsResource.ts | 3 ++- 8 files changed, 19 insertions(+), 6 deletions(-) diff --git a/webapp/packages/core-root/src/PermissionsResource.ts b/webapp/packages/core-root/src/PermissionsResource.ts index a5624dff16..2c21a40285 100644 --- a/webapp/packages/core-root/src/PermissionsResource.ts +++ b/webapp/packages/core-root/src/PermissionsResource.ts @@ -29,13 +29,14 @@ export class PermissionsResource extends CachedDataResource, null> { return this.data.has(id); } - protected async loader(): Promise> { + protected async loader(key: null): Promise> { 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; diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index 2d0523170f..4c57c831d7 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -21,9 +21,10 @@ export class ServerConfigResource extends CachedDataResource { + protected async loader(key: null): Promise { const { serverConfig } = await this.graphQLService.gql.serverConfig(); + this.markUpdated(key); return serverConfig; } } diff --git a/webapp/packages/core-root/src/SessionResource.ts b/webapp/packages/core-root/src/SessionResource.ts index 5a29f87fa1..6adb43489f 100644 --- a/webapp/packages/core-root/src/SessionResource.ts +++ b/webapp/packages/core-root/src/SessionResource.ts @@ -30,9 +30,10 @@ export class SessionResource extends CachedDataResource { + protected async loader(key: null): Promise { const { session } = await this.graphQLService.gql.openSession(); + this.markUpdated(key); return session; } } diff --git a/webapp/packages/plugin-authentication/src/Administration/RolesResource.ts b/webapp/packages/plugin-authentication/src/Administration/RolesResource.ts index 8d7bc28cb6..3aa2f65c7f 100644 --- a/webapp/packages/plugin-authentication/src/Administration/RolesResource.ts +++ b/webapp/packages/plugin-authentication/src/Administration/RolesResource.ts @@ -31,6 +31,7 @@ export class RolesResource extends CachedDataResource { + protected async loader(key: null): Promise { const { providers } = await this.graphQLService.gql.getAuthProviders(); + this.markUpdated(key); return providers; } } diff --git a/webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts b/webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts index b4273761b4..7a0a538d14 100644 --- a/webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts +++ b/webapp/packages/plugin-connection-preconfigured/src/DataSourcesResource.ts @@ -24,11 +24,13 @@ export class DataSourcesResource extends CachedDataResource { return !!this.data.length; } - protected async loader(): Promise { + protected async loader(key: null): Promise { if (!await this.permissionsService.hasAsync(EPermission.public)) { + this.markUpdated(key); return []; } const { dataSourceList } = await this.graphQLService.gql.dataSourceList(); + this.markUpdated(key); return dataSourceList; } } diff --git a/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts index 9d93f9c253..f00f87f03f 100644 --- a/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts +++ b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts @@ -25,7 +25,7 @@ export class DataTransferProcessorsResource extends CachedDataResource