fix: CB-1609 unexpected requests

This commit is contained in:
Wroud
2022-02-10 12:19:44 +03:00
parent 18cdb63d6a
commit 0849a04f3c
15 changed files with 211 additions and 95 deletions
@@ -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)(
<connection-selector {...use({ isVisible })}>
<ContextMenu
<ContextMenu
menu={connectionsMenu}
placement="bottom-end"
style={[menuStyles, connectionMenu, topMenuStyles, removeDisableEffect]}
disclosure
modal
>
<TopNavButton
<TopNavButton
title={connection?.name || 'app_topnavbar_connection_schema_manager_not_selected'}
icon={driver?.icon}
style={[menuStyles, connectionMenu, removeDisableEffect]}
@@ -167,19 +168,19 @@ export const ConnectionSelector = observer(function ConnectionSelector() {
secondary
/>
</ContextMenu>
<ContextMenu
<ContextMenu
menu={dataContainerMenu}
placement="bottom-end"
style={[menuStyles, topMenuStyles]}
disclosure
modal
>
<TopNavButton
<TopNavButton
title={objectContainerName}
icon={objectContainerIcon}
style={[menuStyles, removeDisableEffect]}
menu={
connectionSelectorService.isObjectCatalogChangeable
connectionSelectorService.isObjectCatalogChangeable
|| connectionSelectorService.isObjectSchemaChangeable
}
secondary
@@ -30,15 +30,6 @@ export const NodeManagerUtils = {
return `database://${connectionId}`;
},
nodeIdToConnectionId(nodeId: string): string {
const matches = nodeId.match(/database:\/\/(.*?)(|\/.*)$/);
if (!matches) {
throw new Error('Not database object');
}
return matches[1];
},
isDatabaseObject(objectId: string): boolean {
return /^database:\/\//.test(objectId);
},
@@ -12,6 +12,7 @@ import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import { Button, ErrorMessage, Loader, useClipboard, useErrorDetails, useObservableRef } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource } 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 { GQLErrorCatcher, SqlDialectInfo } from '@cloudbeaver/core-sdk';
import { composes, useStyles } from '@cloudbeaver/core-theming';
import { SQLCodeEditorLoader, SqlDialectInfoService } from '@cloudbeaver/plugin-sql-editor';
import { NodeManagerUtils } from '../NodesManager/NodeManagerUtils';
import { SqlGeneratorsResource } from './SqlGeneratorsResource';
const styles = composes(
@@ -73,14 +73,19 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(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<DialogComponentProps<Payload>>(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)(
<CommonDialogWrapper
@@ -99,14 +99,14 @@ export function useDataResource<
async [loadFunctionName](refresh?: boolean) {
const { key, includes, loading, resource, actions, prevData } = this;
if (loading) {
if (loading || actions?.active === false) {
return;
}
try {
const active = await actions?.isActive?.(resource);
if (active === false || actions?.active === false) {
if (active === false) {
return;
}
+2 -20
View File
@@ -45,17 +45,7 @@ import {
} from '@cloudbeaver/core-app';
import { coreAuthenticationManifest } from '@cloudbeaver/core-authentication';
import { BlocksLocaleService } from '@cloudbeaver/core-blocks';
import {
ConnectionExecutionContextResource,
ConnectionExecutionContextService,
ConnectionsManagerService,
ConnectionInfoResource,
ContainerResource,
DBDriverResource,
NetworkHandlerResource,
DatabaseAuthModelsResource,
ConnectionsLocaleService,
} from '@cloudbeaver/core-connections';
import { coreConnectionsManifest } from '@cloudbeaver/core-connections';
import type { PluginManifest } from '@cloudbeaver/core-di';
import { CommonDialogService, ContextMenuService } from '@cloudbeaver/core-dialogs';
import { NotificationService, ExceptionsCatcherService, EventsSettingsService } from '@cloudbeaver/core-events';
@@ -97,19 +87,11 @@ export const coreManifests: PluginManifest[] = [
CommonDialogService,
SessionExpireWarningDialogService,
SessionExpiredDialogService,
ConnectionsLocaleService,
ConnectionDialogsService,
ConnectionSchemaManagerBootstrap,
ConnectionSchemaManagerService,
ConnectionInfoResource,
BlocksLocaleService,
AppLocaleService,
ContainerResource,
DBDriverResource,
NetworkHandlerResource,
ConnectionExecutionContextResource,
ConnectionExecutionContextService,
ConnectionsManagerService,
ScreenService,
AppScreenService,
ContextMenuService,
@@ -122,7 +104,6 @@ export const coreManifests: PluginManifest[] = [
MainMenuService,
TopNavService,
NavigationTabsService,
DatabaseAuthModelsResource,
NavNodeContextMenuService,
NavigationTreeService,
NavNodeManagerService,
@@ -147,4 +128,5 @@ export const coreManifests: PluginManifest[] = [
coreVersionManifest,
coreVersionUpdateManifest,
coreRootManifest,
coreConnectionsManifest,
];
@@ -0,0 +1,89 @@
/*
* 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 { runInAction } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
import {
GraphQLService,
CachedMapResource,
ResourceKey,
ResourceKeyUtils,
CachedMapAllKey,
resourceKeyList,
SqlDialectInfo
} from '@cloudbeaver/core-sdk';
import type { IConnectionExecutionContextInfo } from './ConnectionExecutionContext/IConnectionExecutionContextInfo';
import { ConnectionInfoResource } from './ConnectionInfoResource';
export type ConnectionDialect = SqlDialectInfo;
@injectable()
export class ConnectionDialectResource extends CachedMapResource<string, ConnectionDialect> {
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<string> {
const result = await this.graphQLService.sdk.formatSqlQuery({
connectionId: context.connectionId,
contextId: context.id,
query,
});
return result.query;
}
async loadAll(): Promise<Map<string, ConnectionDialect>> {
await this.load(CachedMapAllKey);
return this.data;
}
protected async loader(key: ResourceKey<string>, includes: string[]): Promise<Map<string, ConnectionDialect>> {
const all = ResourceKeyUtils.includes(key, CachedMapAllKey);
key = this.transformParam(key);
const dialects: Map<string, ConnectionDialect> = 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;
}
}
@@ -52,9 +52,9 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
readonly onConnectionClose: ISyncExecutor<Connection>;
private sessionUpdate: boolean;
private nodeIdMap: Map<string, string>;
private readonly nodeIdMap: Map<string, string>;
constructor(
private graphQLService: GraphQLService,
private readonly graphQLService: GraphQLService,
sessionDataResource: SessionDataResource,
permissionsResource: PermissionsResource
) {
@@ -89,6 +89,13 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
});
}
isConnected(key: string): boolean;
isConnected(key: ResourceKeyList<string>): boolean;
isConnected(key: ResourceKey<string>): boolean;
isConnected(key: ResourceKey<string>): 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(
@@ -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))
@@ -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';
@@ -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,
],
};
@@ -35,11 +35,11 @@ export const ExecutorInterrupter = {
};
},
interrupter(flag: () => Promise<boolean> | boolean) {
return async (data: any, contexts: IExecutionContextProvider<any>): Promise<void> => {
interrupter<T>(flag: (data: T) => Promise<boolean> | boolean) {
return async (data: T, contexts: IExecutionContextProvider<T>): Promise<void> => {
const interrupt = contexts.getContext(ExecutorInterrupter.interruptContext);
if ((await flag())) {
if ((await flag(data))) {
interrupt.interrupt();
}
};
@@ -134,8 +134,8 @@ export abstract class CachedMapResource<
getException(key: TKey): Error | null;
getException(key: ResourceKeyList<TKey>): Array<Error | null>;
getException(key: ResourceKey<TKey>): Array<Error | null>| Error | null;
getException(key: ResourceKey<TKey>): Array<Error | null>| Error | null {
getException(key: ResourceKey<TKey>): Array<Error | null> | Error | null;
getException(key: ResourceKey<TKey>): Array<Error | null> | 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<TKey>);
this.outdateWaitList.push(key!);
return;
}
this.markOutdatedSync(key as ResourceKey<TKey>);
this.markOutdatedSync(key!);
}
cleanError(): void
@@ -267,8 +267,8 @@ export abstract class CachedMapResource<
get(key: TKey): TValue | undefined;
get(key: ResourceKeyList<TKey>): Array<TValue | undefined>;
get(key: ResourceKey<TKey>): Array<TValue | undefined>| TValue | undefined;
get(key: ResourceKey<TKey>): Array<TValue | undefined>| TValue | undefined {
get(key: ResourceKey<TKey>): Array<TValue | undefined> | TValue | undefined;
get(key: ResourceKey<TKey>): Array<TValue | undefined> | TValue | undefined {
key = this.transformParam(key);
return ResourceKeyUtils.map(key, key => this.data.get(key));
}
@@ -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<any, IDatabaseDataResult>;
model: IDatabaseDataModel;
}
export const ScriptPreviewDialog = observer<DialogComponentProps<Payload>>(function ScriptPreviewDialog({
@@ -56,8 +56,10 @@ export const ScriptPreviewDialog = observer<DialogComponentProps<Payload>>(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 {
@@ -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<string, SqlDialectInfo>();
constructor(
private graphQLService: GraphQLService,
private notificationService: NotificationService
) {
makeObservable<SqlDialectInfoService, 'dialectInfo'>(this, {
dialectInfo: observable,
});
}
private readonly connectionDialectResource: ConnectionDialectResource,
private readonly notificationService: NotificationService
) { }
async formatScript(context: IConnectionExecutionContextInfo, query: string): Promise<string> {
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<SqlDialectInfo | undefined> {
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');
}
@@ -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();