Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CB-5205 fix: connection view switching #2984

Merged
merged 9 commits into from
Oct 15, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ export class ConnectionExecutionContextResource extends CachedMapResource<string
resourceKeyList(
flat(
ResourceKeyUtils.map(key, key =>
this.values.filter(context => {
const connection = this.connectionInfoResource.get(key);
return context.connectionId === key.connectionId && context.projectId === key.projectId && !connection?.connected;
}),
this.values.filter(
context =>
context.connectionId === key.connectionId && context.projectId === key.projectId && !this.connectionInfoResource.isConnected(key),
),
),
).map(context => context.id),
),
Expand Down
40 changes: 30 additions & 10 deletions webapp/packages/core-connections/src/ConnectionInfoResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
CachedMapAllKey,
CachedMapResource,
type CachedResourceIncludeArgs,
type ICachedResourceMetadata,
isResourceAlias,
type ResourceKey,
resourceKeyList,
Expand Down Expand Up @@ -76,8 +77,12 @@ export const DEFAULT_NAVIGATOR_VIEW_SETTINGS: NavigatorSettingsInput = {
showUtilityObjects: false,
};

export interface IConnectionInfoMetadata extends ICachedResourceMetadata {
connecting?: boolean;
}

@injectable()
export class ConnectionInfoResource extends CachedMapResource<IConnectionInfoParams, Connection, ConnectionInfoIncludes> {
export class ConnectionInfoResource extends CachedMapResource<IConnectionInfoParams, Connection, ConnectionInfoIncludes, IConnectionInfoMetadata> {
readonly onConnectionCreate: ISyncExecutor<Connection>;
readonly onConnectionClose: ISyncExecutor<IConnectionInfoParams>;

Expand Down Expand Up @@ -237,6 +242,13 @@ export class ConnectionInfoResource extends CachedMapResource<IConnectionInfoPar
};
}

isConnecting(key: IConnectionInfoParams): boolean;
isConnecting(key: ResourceKeyList<IConnectionInfoParams>): boolean;
isConnecting(key: ResourceKey<IConnectionInfoParams>): boolean;
isConnecting(key: ResourceKey<IConnectionInfoParams>): boolean {
return [this.metadata.get(key)].flat().some(connection => connection?.connecting ?? false);
}

isConnected(key: IConnectionInfoParams): boolean;
isConnected(key: ResourceKeyList<IConnectionInfoParams>): boolean;
isConnected(key: ResourceKey<IConnectionInfoParams>): boolean;
Expand Down Expand Up @@ -390,13 +402,19 @@ export class ConnectionInfoResource extends CachedMapResource<IConnectionInfoPar
const key: IConnectionInfoParams = { projectId: config.projectId, connectionId: config.connectionId };

await this.performUpdate(key, [], async () => {
const { connection } = await this.graphQLService.sdk.initConnection({
...config,
...this.getDefaultIncludes(),
...this.getIncludesMap(key),
});
this.set(createConnectionParam(connection), connection);
this.onDataOutdated.execute(key);
const metadata = this.metadata.get(key);
metadata.connecting = true;
try {
const { connection } = await this.graphQLService.sdk.initConnection({
...config,
...this.getDefaultIncludes(),
...this.getIncludesMap(key),
});
this.set(createConnectionParam(connection), connection);
this.onDataOutdated.execute(key);
} finally {
metadata.connecting = false;
}
});

return this.get(key)!;
Expand Down Expand Up @@ -444,8 +462,10 @@ export class ConnectionInfoResource extends CachedMapResource<IConnectionInfoPar
...this.getIncludesMap(key),
});

this.set(createConnectionParam(connection), connection);
this.onDataOutdated.execute(key);
runInAction(() => {
this.set(createConnectionParam(connection), connection);
this.onDataOutdated.execute(key);
});
});

return this.get(key)!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type ConnectionTools = DatabaseConnectionToolsFragment;
export class ConnectionToolsResource extends CachedMapResource<IConnectionInfoParams, ConnectionTools> {
constructor(
private readonly graphQLService: GraphQLService,
connectionInfoResource: ConnectionInfoResource,
private readonly connectionInfoResource: ConnectionInfoResource,
) {
super();

Expand All @@ -44,6 +44,10 @@ export class ConnectionToolsResource extends CachedMapResource<IConnectionInfoPa
const projectId = key.projectId;
const connectionId = key.connectionId;

if (!this.connectionInfoResource.isConnected(key)) {
throw new Error(`Connection is not connected (${projectId}, ${connectionId})`);
}

const { connections } = await this.graphQLService.sdk.getConnectionsTools({
projectId,
connectionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class ConnectionsManagerService {
return;
}
const contexts = await this.onDisconnect.execute({
connections: [createConnectionParam(connection)],
connections: [key],
state: 'before',
});

Expand Down
27 changes: 26 additions & 1 deletion webapp/packages/core-connections/src/ContainerResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { AppAuthService } from '@cloudbeaver/core-authentication';
import { injectable } from '@cloudbeaver/core-di';
import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
import { NavTreeResource } from '@cloudbeaver/core-navigation-tree';
import { CachedMapResource, isResourceAlias, type ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { GraphQLService, type NavNodeInfoFragment } from '@cloudbeaver/core-sdk';
import { isNull } from '@cloudbeaver/core-utils';
Expand Down Expand Up @@ -40,12 +41,36 @@ interface ObjectContainerParams {
export class ContainerResource extends CachedMapResource<ObjectContainerParams, IStructContainers> {
constructor(
private readonly graphQLService: GraphQLService,
private readonly navTreeResource: NavTreeResource,
private readonly connectionInfoResource: ConnectionInfoResource,
appAuthService: AppAuthService,
) {
super();

appAuthService.requireAuthentication(this);
this.preloadResource(navTreeResource, key => {
if (isResourceAlias(key)) {
return '';
}
return ResourceKeyUtils.mapKey(
key,
key => this.connectionInfoResource.get(createConnectionParam(key.projectId, key.connectionId))?.nodePath || '',
);
});
this.navTreeResource.onDataOutdated.addHandler(key => {
ResourceKeyUtils.forEach(key, key => {
if (isResourceAlias(key)) {
return;
}
const connection = this.connectionInfoResource.getConnectionForNode(key);

if (!connection) {
return;
}

this.markOutdated(resourceKeyList(this.keys.filter(key => key.projectId === connection.projectId && key.connectionId === connection.id)));
});
});
this.preloadResource(connectionInfoResource, () => ConnectionInfoActiveProjectKey);
this.before(
ExecutorInterrupter.interrupter(key => {
Expand All @@ -61,7 +86,7 @@ export class ContainerResource extends CachedMapResource<ObjectContainerParams,
);
this.connectionInfoResource.onItemUpdate.addHandler(key =>
ResourceKeyUtils.forEach(key, key => {
if (!this.connectionInfoResource.get(key)?.connected) {
if (!this.connectionInfoResource.isConnected(key)) {
this.delete({ projectId: key.projectId, connectionId: key.connectionId });
}
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 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 { createExtension, type IExtension, isExtension } from '@cloudbeaver/core-extensions';
import type { ILoadableState } from '@cloudbeaver/core-utils';

const objectLoaderProviderSymbol = Symbol('@extension/ObjectLoaderProvider');

export type IObjectLoaderProvider<T = never> = (context: T) => ILoadableState[];

export function objectLoaderProvider<T>(provider: IObjectLoaderProvider<T>) {
return createExtension<T>(provider, objectLoaderProviderSymbol);
}

export function isObjectLoaderProvider<T>(obj: IExtension<T>): obj is IObjectLoaderProvider<T> & IExtension<T> {
return isExtension(obj, objectLoaderProviderSymbol);
}
1 change: 1 addition & 0 deletions webapp/packages/core-connections/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './extensions/IObjectCatalogProvider.js';
export * from './extensions/IObjectCatalogSetter.js';
export * from './extensions/IObjectSchemaProvider.js';
export * from './extensions/IObjectSchemaSetter.js';
export * from './extensions/IObjectLoaderProvider.js';
export * from './extensions/IExecutionContextProvider.js';
export * from './NavTree/ConnectionNavNodeService.js';
export * from './NavTree/NavNodeExtensionsService.js';
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default [
['ui_stepper_next', 'Next'],
['ui_stepper_finish', 'Finish'],
['ui_load_more', 'Load more'],
['ui_processing_connecting', 'Connecting...'],
['ui_processing_loading', 'Loading...'],
['ui_processing_cancel', 'Cancel'],
['ui_processing_canceling', 'Cancelling...'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default [
['ui_stepper_next', 'Suivant'],
['ui_stepper_finish', 'Terminer'],
['ui_load_more', 'Charger plus'],
['ui_processing_connecting', 'Connexion en cours...'],
['ui_processing_loading', 'Chargement...'],
['ui_processing_cancel', 'Annuler'],
['ui_processing_canceling', 'Annulation...'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default [
['ui_stepper_next', 'Avanti'],
['ui_stepper_finish', 'Termina'],
['ui_load_more', 'Load more'],
['ui_processing_connecting', 'Collegamento...'],
['ui_processing_loading', 'Caricamento...'],
['ui_processing_cancel', 'Annulla'],
['ui_processing_canceling', 'Annullamento...'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default [
['ui_dark_theme', 'Темная'],
['ui_stepper_back', 'Назад'],
['ui_load_more', 'Загрузить ещё'],
['ui_processing_connecting', 'Подключение...'],
['ui_processing_loading', 'Загрузка...'],
['ui_processing_cancel', 'Отменить'],
['ui_processing_canceling', 'Отмена...'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default [
['ui_stepper_next', '下一步'],
['ui_stepper_finish', '完成'],
['ui_load_more', 'Load more'],
['ui_processing_connecting', '连接中...'],
['ui_processing_loading', '加载中...'],
['ui_processing_cancel', '取消'],
['ui_processing_canceling', '取消中...'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,16 @@ export class NavTreeResource extends CachedMapResource<string, string[], Record<
}

async refreshTree(navNodeId: string, silent = false): Promise<void> {
await this.graphQLService.sdk.navRefreshNode({
nodePath: navNodeId,
});
this.performUpdate(navNodeId, [], async () => {
await this.graphQLService.sdk.navRefreshNode({
nodePath: navNodeId,
});

if (!silent) {
this.markTreeOutdated(navNodeId);
}
await this.onNodeRefresh.execute(navNodeId);
if (!silent) {
this.markOutdated(navNodeId);
}
await this.onNodeRefresh.execute(navNodeId);
});
}

markTreeOutdated(navNodeId: ResourceKeySimple<string>): void {
Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(

const menu = useRef<IMenuState>();

useAutoLoad({ name: `${ContextMenu.name}(${menuData.menu.id})` }, menuData.loaders, !lazy, menuVisible);
useAutoLoad({ name: `${ContextMenu.name}(${menuData.menu.id})` }, menuData.loaders, !lazy, menuVisible, true);

const handlers = useObjectRef(
() => ({
Expand Down
4 changes: 2 additions & 2 deletions webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const MenuBar = observer<IMenuBarProps, HTMLDivElement>(
const mergedRef = useMergeRefs(ref, refNav);
const styles = useS(style);
const items = menu.items;
useAutoLoad(MenuBar, menu.loaders);
useAutoLoad(MenuBar, menu.loaders, true, false, true);

if (!items.length) {
return null;
Expand All @@ -67,7 +67,7 @@ export const MenuBar = observer<IMenuBarProps, HTMLDivElement>(
return (
<SContext registry={styleRegistry}>
<div ref={mergedRef} className={s(styles, { menuBar: true }, className)} tabIndex={0} {...props}>
<Loader suspense small>
<Loader suspense small inline>
{items.map(item => (
<MenuBarElement key={item.id} item={item} menuData={menu} nestedMenuSettings={nestedMenuSettings} rtl={rtl} />
))}
Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/core-ui/src/ContextMenu/SubMenuElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(

const handler = subMenuData.handler;
const hidden = getComputed(() => handler?.isHidden?.(subMenuData.context));
useAutoLoad(SubMenuElement, subMenuData.loaders, !hidden, visible);
useAutoLoad(SubMenuElement, subMenuData.loaders, !hidden, visible, true);

const handlers = useObjectRef(
() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const ConnectionDialogFooter = observer<Props>(function ConnectionDialogF
{translate('ui_stepper_back')}
</Button>
<Button type="button" mod={['unelevated']} disabled={isConnecting} onClick={onConnect}>
{isConnecting ? translate('plugin_connection_template_connecting') : translate('connections_connection_connect')}
{isConnecting ? translate('ui_processing_connecting') : translate('connections_connection_connect')}
</Button>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* you may not use this file except in compliance with the License.
*/
export default [
['plugin_connection_template_connecting', 'Connecting...'],
['plugin_connection_template_connecting_message', 'Connecting to database...'],
['plugin_connection_template_connect_success', 'Connection is established'],
['plugin_connection_template_action_connection_template_label', 'From a Template'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* you may not use this file except in compliance with the License.
*/
export default [
['plugin_connection_template_connecting', 'Connexion en cours...'],
['plugin_connection_template_connecting_message', 'Connexion à la base de données en cours...'],
['plugin_connection_template_connect_success', 'Connexion établie'],
['plugin_connection_template_action_connection_template_label', "À partir d'un modèle"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* you may not use this file except in compliance with the License.
*/
export default [
['plugin_connection_template_connecting', 'Collegamento...'],
['plugin_connection_template_connecting_message', 'Collegamento al database...'],
['plugin_connection_template_connect_success', 'Connection is established'],
['plugin_connection_template_action_connection_template_label', 'Dal Template'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* you may not use this file except in compliance with the License.
*/
export default [
['plugin_connection_template_connecting', 'Подключение...'],
['plugin_connection_template_connecting_message', 'Подключение к базе...'],
['plugin_connection_template_connect_success', 'Подключение установлено'],
['plugin_connection_template_action_connection_template_label', 'Из шаблона'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* you may not use this file except in compliance with the License.
*/
export default [
['plugin_connection_template_connecting', '连接中...'],
['plugin_connection_template_connecting_message', '连接数据库...'],
['plugin_connection_template_connect_success', '已建立连接'],
['plugin_connection_template_action_connection_template_label', '从模板创建'],
Expand Down
17 changes: 6 additions & 11 deletions webapp/packages/plugin-connections/src/ConnectionShield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { type PropsWithChildren, useState } from 'react';
import { type PropsWithChildren } from 'react';

import { Button, Loader, TextPlaceholder, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import { Button, getComputed, Loader, TextPlaceholder, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource, ConnectionsManagerService, type IConnectionInfoParams } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
Expand All @@ -23,28 +23,23 @@ export const ConnectionShield = observer<PropsWithChildren<IConnectionShieldProp
const notificationService = useService(NotificationService);

const connection = useResource(ConnectionShield, ConnectionInfoResource, connectionKey);

const [connecting, setConnecting] = useState(false);
const connecting = getComputed(() => connectionKey && connection.resource.isConnecting(connectionKey));

async function handleConnect() {
if (connecting || !connection.data || !connectionKey) {
return;
}

setConnecting(true);

try {
await connectionsManagerService.requireConnection(connectionKey);
} catch (exception: any) {
notificationService.logException(exception);
} finally {
setConnecting(false);
}
}

if (connection.data && !connection.data.connected) {
if (connecting || connection.isLoading()) {
return <Loader />;
if (getComputed(() => connection.data && !connection.data.connected)) {
if (connecting) {
return <Loader message="ui_processing_connecting" />;
}

return (
Expand Down
Loading