From 0849a04f3cf196f34564dbae3c94ee5ff2b02ebf Mon Sep 17 00:00:00 2001 From: Wroud Date: Thu, 10 Feb 2022 12:19:44 +0300 Subject: [PATCH] fix: CB-1609 unexpected requests --- .../ConnectionSelector/ConnectionSelector.tsx | 19 ++-- .../shared/NodesManager/NodeManagerUtils.ts | 9 -- .../SqlGenerators/GeneratedSqlDialog.tsx | 27 ++++-- .../core-blocks/src/useDataResource.ts | 4 +- .../packages/core-bootstrap/src/manifest.ts | 22 +---- .../src/ConnectionDialectResource.ts | 89 +++++++++++++++++++ .../src/ConnectionInfoResource.ts | 11 ++- .../core-connections/src/ContainerResource.ts | 2 + webapp/packages/core-connections/src/index.ts | 2 + .../packages/core-connections/src/manifest.ts | 40 +++++++++ .../core-executor/src/ExecutorInterrupter.ts | 6 +- .../src/Resource/CachedMapResource.ts | 12 +-- .../src/ScriptPreview/ScriptPreviewDialog.tsx | 8 +- .../src/SqlDialectInfoService.ts | 33 ++----- .../src/SqlEditor/SqlEditorController.ts | 22 +++-- 15 files changed, 211 insertions(+), 95 deletions(-) create mode 100644 webapp/packages/core-connections/src/ConnectionDialectResource.ts create mode 100644 webapp/packages/core-connections/src/manifest.ts diff --git a/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelector.tsx b/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelector.tsx index 98e115bad5..9f22c216a3 100644 --- a/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelector.tsx +++ b/webapp/packages/core-app/src/TopNavBar/ConnectionSchemaManager/ConnectionSelector/ConnectionSelector.tsx @@ -87,8 +87,8 @@ export const ConnectionSelector = observer(function ConnectionSelector() { const drivers = useMapResource(ConnectionSelector, DBDriverResource, CachedMapAllKey, { isActive: () => isEnabled, }); - - useMapResource(ConnectionSelector, ConnectionInfoResource, CachedMapAllKey, { + + const connectionInfo = useMapResource(ConnectionSelector, ConnectionInfoResource, CachedMapAllKey, { isActive: () => isEnabled, }); @@ -96,12 +96,13 @@ export const ConnectionSelector = observer(function ConnectionSelector() { const driver = drivers.resource.get(connection?.driverId || ''); const contextsActive = getComputed(() => ( - isEnabled + isEnabled + && !connectionInfo.isOutdated() && connection?.connected === true && !!connectionSelectorService.activeConnectionId )); - useDataResource(ConnectionSelector, ContainerResource, { + useDataResource(ConnectionSelector, ContainerResource, { connectionId: connectionSelectorService.activeConnectionId!, catalogId: connectionSelectorService.activeObjectCatalogId, }, { @@ -152,14 +153,14 @@ export const ConnectionSelector = observer(function ConnectionSelector() { return styled(style)( - - - - >(functi const sqlDialectInfoService = useService(SqlDialectInfoService); const sqlGeneratorsResource = useService(SqlGeneratorsResource); - const connectionId = NodeManagerUtils.nodeIdToConnectionId(payload.pathId); + const connectionInfoResource = useService(ConnectionInfoResource); + const connection = connectionInfoResource.getConnectionForNode(payload.pathId); const state = useObservableRef(() => ({ query: '', loading: true, error: new GQLErrorCatcher(), get dialect(): SqlDialectInfo | undefined { - return this.sqlDialectInfoService.getDialectInfo(this.connectionId); + if (!this.connection?.connected) { + return; + } + + return this.sqlDialectInfoService.getDialectInfo(this.connection.id); }, async load() { this.error.clear(); @@ -96,21 +101,27 @@ export const GeneratedSqlDialog = observer>(functi }), { query: observable.ref, loading: observable.ref, - connectionId: observable.ref, + connection: observable.ref, dialect: computed, - }, { connectionId, sqlDialectInfoService }); + }, { connection, sqlDialectInfoService }); const error = useErrorDetails(state.error.exception); useEffect(() => { state.load(); + }, []); - sqlDialectInfoService.loadSqlDialectInfo(connectionId) + useEffect(() => { + if (!connection) { + return; + } + + sqlDialectInfoService.loadSqlDialectInfo(connection.id) .catch(exception => { console.error(exception); - console.warn(`Can't get dialect for connection: '${connectionId}'. Default dialect will be used`); + console.warn(`Can't get dialect for connection: '${connection.id}'. Default dialect will be used`); }); - }, []); + }); return styled(style)( { + constructor( + private readonly graphQLService: GraphQLService, + connectionInfoResource: ConnectionInfoResource, + ) { + super(); + this.sync(connectionInfoResource); + this.addAlias(CachedMapAllKey, () => resourceKeyList(connectionInfoResource.keys)); + this.before(ExecutorInterrupter.interrupter(key => !connectionInfoResource.isConnected(key))); + } + + async formatScript(context: IConnectionExecutionContextInfo, query: string): Promise { + const result = await this.graphQLService.sdk.formatSqlQuery({ + connectionId: context.connectionId, + contextId: context.id, + query, + }); + + return result.query; + } + + async loadAll(): Promise> { + await this.load(CachedMapAllKey); + return this.data; + } + + protected async loader(key: ResourceKey, includes: string[]): Promise> { + const all = ResourceKeyUtils.includes(key, CachedMapAllKey); + key = this.transformParam(key); + + const dialects: Map = new Map(); + + await ResourceKeyUtils.forEachAsync(key, async key => { + const connectionId = key; + + const { dialect } = await this.graphQLService.sdk.querySqlDialectInfo({ + connectionId, + ...this.getIncludesMap(connectionId, includes), + }); + + if (!dialect) { + throw new Error('Dialect not found'); + } + + dialects.set(connectionId, dialect); + }); + + runInAction(() => { + if (all) { + this.resetIncludes(); + this.data.clear(); + } + + for (const [connectionId, dialect] of dialects) { + this.dataSet(connectionId, dialect); + } + }); + + return this.data; + } +} diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 24c12a6d60..de2097fad3 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -52,9 +52,9 @@ export class ConnectionInfoResource extends CachedMapResource; private sessionUpdate: boolean; - private nodeIdMap: Map; + private readonly nodeIdMap: Map; constructor( - private graphQLService: GraphQLService, + private readonly graphQLService: GraphQLService, sessionDataResource: SessionDataResource, permissionsResource: PermissionsResource ) { @@ -89,6 +89,13 @@ export class ConnectionInfoResource extends CachedMapResource): boolean; + isConnected(key: ResourceKey): boolean; + isConnected(key: ResourceKey): boolean { + return ResourceKeyUtils.every(key, connectionId => this.get(connectionId)?.connected ?? false); + } + getConnectionForNode(nodeId: string): Connection | undefined { const indexOfConnectionPart = nodeId.indexOf('/', 11); const connectionPart = nodeId.slice( diff --git a/webapp/packages/core-connections/src/ContainerResource.ts b/webapp/packages/core-connections/src/ContainerResource.ts index afdb40fc37..8397e983c1 100644 --- a/webapp/packages/core-connections/src/ContainerResource.ts +++ b/webapp/packages/core-connections/src/ContainerResource.ts @@ -9,6 +9,7 @@ import { observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; +import { ExecutorInterrupter } from '@cloudbeaver/core-executor'; import { GraphQLService, CachedDataResource, @@ -70,6 +71,7 @@ string })); this.preloadResource(connectionInfoResource, () => CachedMapAllKey); + this.before(ExecutorInterrupter.interrupter(key => !connectionInfoResource.isConnected(key.connectionId))); this.connectionInfoResource.onItemDelete.addHandler( key => ResourceKeyUtils.forEach(key, key => this.data.delete(key)) diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 22dedce7e7..8a82247aae 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -7,6 +7,7 @@ export * from './ConnectionExecutionContext/IConnectionExecutionContextInfo'; export * from './extensions/IConnectionProvider'; export * from './extensions/IConnectionSetter'; export * from './ConnectionsManagerService'; +export * from './ConnectionDialectResource'; export * from './ConnectionInfoResource'; export * from './EConnectionFeature'; export * from './ContainerResource'; @@ -20,3 +21,4 @@ export * from './NetworkHandlerResource'; export * from './useConnectionInfo'; export * from './useDBDriver'; export * from './USER_NAME_PROPERTY_ID'; +export { manifest as coreConnectionsManifest } from './manifest'; diff --git a/webapp/packages/core-connections/src/manifest.ts b/webapp/packages/core-connections/src/manifest.ts new file mode 100644 index 0000000000..5747016d08 --- /dev/null +++ b/webapp/packages/core-connections/src/manifest.ts @@ -0,0 +1,40 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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 type { PluginManifest } from '@cloudbeaver/core-di'; + +import { ConnectionDialectResource } from './ConnectionDialectResource'; +import { ConnectionExecutionContextResource } from './ConnectionExecutionContext/ConnectionExecutionContextResource'; +import { ConnectionExecutionContextService } from './ConnectionExecutionContext/ConnectionExecutionContextService'; +import { ConnectionInfoResource } from './ConnectionInfoResource'; +import { ConnectionsLocaleService } from './ConnectionsLocaleService'; +import { ConnectionsManagerService } from './ConnectionsManagerService'; +import { ContainerResource } from './ContainerResource'; +import { DatabaseAuthModelsResource } from './DatabaseAuthModelsResource'; +import { DBDriverResource } from './DBDriverResource'; +import { NetworkHandlerResource } from './NetworkHandlerResource'; + + +export const manifest: PluginManifest = { + info: { + name: 'Core Connections', + }, + + providers: [ + ConnectionExecutionContextResource, + ConnectionExecutionContextService, + ConnectionsManagerService, + ConnectionInfoResource, + ContainerResource, + ConnectionsLocaleService, + DatabaseAuthModelsResource, + DBDriverResource, + NetworkHandlerResource, + ConnectionDialectResource, + ], +}; diff --git a/webapp/packages/core-executor/src/ExecutorInterrupter.ts b/webapp/packages/core-executor/src/ExecutorInterrupter.ts index 7bea9d77f8..2ef6aadafe 100644 --- a/webapp/packages/core-executor/src/ExecutorInterrupter.ts +++ b/webapp/packages/core-executor/src/ExecutorInterrupter.ts @@ -35,11 +35,11 @@ export const ExecutorInterrupter = { }; }, - interrupter(flag: () => Promise | boolean) { - return async (data: any, contexts: IExecutionContextProvider): Promise => { + interrupter(flag: (data: T) => Promise | boolean) { + return async (data: T, contexts: IExecutionContextProvider): Promise => { const interrupt = contexts.getContext(ExecutorInterrupter.interruptContext); - if ((await flag())) { + if ((await flag(data))) { interrupt.interrupt(); } }; diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts index ec42a85fd1..506c8faee5 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts @@ -134,8 +134,8 @@ export abstract class CachedMapResource< getException(key: TKey): Error | null; getException(key: ResourceKeyList): Array; - getException(key: ResourceKey): Array| Error | null; - getException(key: ResourceKey): Array| Error | null { + getException(key: ResourceKey): Array | Error | null; + getException(key: ResourceKey): Array | Error | null { key = this.transformParam(key); return ResourceKeyUtils.map(key, key => this.metadata.get(key).exception); } @@ -201,11 +201,11 @@ export abstract class CachedMapResource< ( (key === undefined ? this.scheduler.executing : this.scheduler.isExecuting(key)) ) && !this.outdateWaitList.some(param => this.includes(key!, param))) { - this.outdateWaitList.push(key as ResourceKey); + this.outdateWaitList.push(key!); return; } - this.markOutdatedSync(key as ResourceKey); + this.markOutdatedSync(key!); } cleanError(): void @@ -267,8 +267,8 @@ export abstract class CachedMapResource< get(key: TKey): TValue | undefined; get(key: ResourceKeyList): Array; - get(key: ResourceKey): Array| TValue | undefined; - get(key: ResourceKey): Array| TValue | undefined { + get(key: ResourceKey): Array | TValue | undefined; + get(key: ResourceKey): Array | TValue | undefined { key = this.transformParam(key); return ResourceKeyUtils.map(key, key => this.data.get(key)); } diff --git a/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx b/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx index abcd232d52..f5d96628b4 100644 --- a/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx +++ b/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx @@ -12,6 +12,7 @@ import { useEffect } from 'react'; import styled, { css } from 'reshadow'; import { Button, useClipboard, useObservableRef } from '@cloudbeaver/core-blocks'; +import { ConnectionExecutionContextService } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { CommonDialogWrapper, DialogComponentProps } from '@cloudbeaver/core-dialogs'; import { useTranslate } from '@cloudbeaver/core-localization'; @@ -19,7 +20,6 @@ import type { SqlDialectInfo } from '@cloudbeaver/core-sdk'; import { SQLCodeEditorLoader, SqlDialectInfoService } from '@cloudbeaver/plugin-sql-editor'; import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseDataResult } from '../DatabaseDataModel/IDatabaseDataResult'; export const dialogStyle = css` footer { @@ -46,7 +46,7 @@ const styles = css` interface Payload { script: string; - model: IDatabaseDataModel; + model: IDatabaseDataModel; } export const ScriptPreviewDialog = observer>(function ScriptPreviewDialog({ @@ -56,8 +56,10 @@ export const ScriptPreviewDialog = observer>(funct const translate = useTranslate(); const copy = useClipboard(); + const connectionExecutionContextService = useService(ConnectionExecutionContextService); const sqlDialectInfoService = useService(SqlDialectInfoService); - const connectionId = payload.model.source.executionContext?.context?.connectionId; + const context = connectionExecutionContextService.get(payload.model.source.executionContext?.context?.id ?? ''); + const connectionId = context?.context?.connectionId; const dialect = useObservableRef(() => ({ get dialect(): SqlDialectInfo | undefined { diff --git a/webapp/packages/plugin-sql-editor/src/SqlDialectInfoService.ts b/webapp/packages/plugin-sql-editor/src/SqlDialectInfoService.ts index 25ad1d5bcc..ce6dfa9038 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDialectInfoService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDialectInfoService.ts @@ -6,35 +6,21 @@ * you may not use this file except in compliance with the License. */ -import { observable, makeObservable } from 'mobx'; - -import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; +import { ConnectionDialectResource, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { GraphQLService, SqlDialectInfo } from '@cloudbeaver/core-sdk'; +import type { SqlDialectInfo } from '@cloudbeaver/core-sdk'; @injectable() export class SqlDialectInfoService { - private dialectInfo = new Map(); - constructor( - private graphQLService: GraphQLService, - private notificationService: NotificationService - ) { - makeObservable(this, { - dialectInfo: observable, - }); - } + private readonly connectionDialectResource: ConnectionDialectResource, + private readonly notificationService: NotificationService + ) { } async formatScript(context: IConnectionExecutionContextInfo, query: string): Promise { try { - const result = await this.graphQLService.sdk.formatSqlQuery({ - connectionId: context.connectionId, - contextId: context.id, - query, - }); - - return result.query; + return await this.connectionDialectResource.formatScript(context, query); } catch (error) { this.notificationService.logException(error, 'Failed to format script'); } @@ -42,14 +28,13 @@ export class SqlDialectInfoService { } getDialectInfo(connectionId: string): SqlDialectInfo | undefined { - return this.dialectInfo.get(connectionId); + return this.connectionDialectResource.get(connectionId); } async loadSqlDialectInfo(connectionId: string): Promise { - if (!this.dialectInfo.has(connectionId)) { + if (!this.connectionDialectResource.has(connectionId)) { try { - const result = await this.graphQLService.sdk.querySqlDialectInfo({ connectionId }); - this.dialectInfo.set(connectionId, result.dialect!); + return this.connectionDialectResource.load(connectionId); } catch (error) { this.notificationService.logException(error, 'Failed to load SqlDialectInfo'); } diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.ts b/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.ts index 94bfe5d80a..a9fe893b4d 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.ts @@ -151,12 +151,16 @@ export class SqlEditorController implements IInitializableController, IDestructi this.reactionDisposer = autorun(() => { if (this.state.executionContext) { - this.sqlDialectInfoService - .loadSqlDialectInfo(this.state.executionContext.connectionId) - .then(async dialect => { - this.parser.setDialect(dialect || null); - await this.updateParserScriptsThrottle(); - }); + const context = this.connectionExecutionContextService.get(this.state.executionContext.id); + + if (context) { + this.sqlDialectInfoService + .loadSqlDialectInfo(this.state.executionContext.connectionId) + .then(async dialect => { + this.parser.setDialect(dialect || null); + await this.updateParserScriptsThrottle(); + }); + } } }); } @@ -297,8 +301,8 @@ export class SqlEditorController implements IInitializableController, IDestructi private async updateParserScripts() { const connectionId = this.state.executionContext?.connectionId; const script = this.parser.actualScript; - - if (!connectionId) { + + if (!connectionId) { return; } @@ -307,7 +311,7 @@ export class SqlEditorController implements IInitializableController, IDestructi connectionId, script ); - + if (this.parser.actualScript === script) { this.parser.setQueries(queries); this.highlightActiveQuery();