-
+ const title = translate(rest.title);
+ const Submenu = submenu;
+ return (
+
+
+ {Submenu && (
+
+
+
)}
-
- );
- }),
+ );
+ }),
+ ),
);
diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItemLoader.ts b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItemLoader.ts
new file mode 100644
index 0000000000..1d9412db7c
--- /dev/null
+++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItemLoader.ts
@@ -0,0 +1,11 @@
+/*
+ * 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.
+ */
+
+// TODO: importLazyComponent currently not working with components registry
+// eslint-disable-next-line @cloudbeaver/no-sync-component-import
+export { MenuBarItem, type MenuBarItemProps } from './MenuBarItem';
diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarLazy.ts b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarLazy.ts
index 4a7d8280fb..889a052ee6 100644
--- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarLazy.ts
+++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarLazy.ts
@@ -5,6 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-import { importLazyComponent } from '@cloudbeaver/core-blocks';
-export const MenuBar = importLazyComponent(() => import('./MenuBar').then(m => m.MenuBar));
+// TODO: importLazyComponent currently not working with components registry
+// eslint-disable-next-line @cloudbeaver/no-sync-component-import
+export { MenuBar, MenuBarAction, type IMenuBarActionProps } from './MenuBar';
diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx
index 1fb08a9ee9..f3e18fdc11 100644
--- a/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx
+++ b/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx
@@ -12,6 +12,7 @@ import { Checkbox, MenuItem, MenuItemCheckbox, MenuItemElement, MenuSeparator, u
import {
IMenuData,
IMenuItem,
+ isMenuCustomItem,
MenuActionItem,
MenuBaseItem,
MenuCheckboxItem,
@@ -44,10 +45,10 @@ export const MenuItemRenderer = observer
(function MenuIt
[item, onItemClose],
);
- if (item instanceof MenuCustomItem) {
+ if (isMenuCustomItem(item)) {
const CustomMenuItem = item.getComponent();
- return ;
+ return ;
}
if (item instanceof MenuSubMenuItem) {
diff --git a/webapp/packages/core-ui/src/index.ts b/webapp/packages/core-ui/src/index.ts
index 4a61fed2d3..ce38557a44 100644
--- a/webapp/packages/core-ui/src/index.ts
+++ b/webapp/packages/core-ui/src/index.ts
@@ -11,6 +11,7 @@ export * from './Clipboard/ClipboardService';
export * from './ContextMenu/ContextMenuLazy';
export * from './ContextMenu/IContextMenuItemProps';
export * from './ContextMenu/MenuBar/MenuBarLazy';
+export * from './ContextMenu/MenuBar/MenuBarItemLoader';
export { default as MenuBarStyles } from './ContextMenu/MenuBar/MenuBar.module.css';
export { default as MenuBarItemStyles } from './ContextMenu/MenuBar/MenuBarItem.module.css';
diff --git a/webapp/packages/core-utils/package.json b/webapp/packages/core-utils/package.json
index 0f154af470..7d5d4cc25c 100644
--- a/webapp/packages/core-utils/package.json
+++ b/webapp/packages/core-utils/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"test": "core-cli-test",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
diff --git a/webapp/packages/core-utils/src/LoadingError.ts b/webapp/packages/core-utils/src/LoadingError.ts
index 453a69f143..5444d951ab 100644
--- a/webapp/packages/core-utils/src/LoadingError.ts
+++ b/webapp/packages/core-utils/src/LoadingError.ts
@@ -7,7 +7,11 @@
*/
export class LoadingError extends Error {
- constructor(private readonly onRefresh: () => void, message?: string, options?: ErrorOptions) {
+ constructor(
+ private readonly onRefresh: () => void,
+ message?: string,
+ options?: ErrorOptions,
+ ) {
super(message, options);
this.name = 'Loading Error';
this.refresh = this.refresh.bind(this);
diff --git a/webapp/packages/core-utils/src/MetadataMap.ts b/webapp/packages/core-utils/src/MetadataMap.ts
index 1ab435c3f0..40da01b342 100644
--- a/webapp/packages/core-utils/src/MetadataMap.ts
+++ b/webapp/packages/core-utils/src/MetadataMap.ts
@@ -30,6 +30,7 @@ export class MetadataMap implements Map {
makeAutoObservable(this, {
sync: action,
+ unSync: action,
});
}
@@ -42,6 +43,10 @@ export class MetadataMap implements Map {
}
sync(entities: Array<[TKey, TValue]>): void {
+ if (this.syncData === entities) {
+ return;
+ }
+
this.temp.clear();
this.data.clear();
for (const [key, value] of entities) {
@@ -50,6 +55,10 @@ export class MetadataMap implements Map {
this.syncData = entities;
}
+ unSync(): void {
+ this.syncData = null;
+ }
+
forEach(callbackfn: (value: TValue, key: TKey, map: Map) => void, thisArg?: any): void {
this.temp.forEach(callbackfn, thisArg);
}
diff --git a/webapp/packages/core-utils/src/TempMap.ts b/webapp/packages/core-utils/src/TempMap.ts
index 144d3c29ff..d914e16747 100644
--- a/webapp/packages/core-utils/src/TempMap.ts
+++ b/webapp/packages/core-utils/src/TempMap.ts
@@ -30,7 +30,10 @@ export class TempMap implements Map {
private readonly valuesTemp: ICachedValueObject;
private readonly entriesTemp: ICachedValueObject<[TKey, TValue][]>;
- constructor(private readonly target: Map, private readonly onSync?: () => void) {
+ constructor(
+ private readonly target: Map,
+ private readonly onSync?: () => void,
+ ) {
this.temp = new Map();
this.flushTask = null;
this.deleted = new Map();
diff --git a/webapp/packages/core-version-update/package.json b/webapp/packages/core-version-update/package.json
index 0e935c87d5..f2d6fae50d 100644
--- a/webapp/packages/core-version-update/package.json
+++ b/webapp/packages/core-version-update/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/core-version/package.json b/webapp/packages/core-version/package.json
index a1218f5a75..1d75a6cf14 100644
--- a/webapp/packages/core-version/package.json
+++ b/webapp/packages/core-version/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/core-view/package.json b/webapp/packages/core-view/package.json
index 214db23481..6b70d0be31 100644
--- a/webapp/packages/core-view/package.json
+++ b/webapp/packages/core-view/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/core-view/src/Menu/IMenu.ts b/webapp/packages/core-view/src/Menu/IMenu.ts
index 9354d4225d..734c8db974 100644
--- a/webapp/packages/core-view/src/Menu/IMenu.ts
+++ b/webapp/packages/core-view/src/Menu/IMenu.ts
@@ -5,10 +5,15 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import type { IAction } from '../Action/IAction';
export interface IMenu {
id: string;
label: string;
icon?: string;
tooltip?: string;
+ /**
+ * experimental, can be changed
+ */
+ action?: IAction;
}
diff --git a/webapp/packages/core-view/src/Menu/MenuItem/IMenuCustomItem.ts b/webapp/packages/core-view/src/Menu/MenuItem/IMenuCustomItem.ts
index 3951604848..e67efbdbf1 100644
--- a/webapp/packages/core-view/src/Menu/MenuItem/IMenuCustomItem.ts
+++ b/webapp/packages/core-view/src/Menu/MenuItem/IMenuCustomItem.ts
@@ -5,6 +5,8 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { IDataContext } from '@cloudbeaver/core-data-context';
+
import type { IMenuItem } from './IMenuItem';
interface IMenuCustomItemCommonProperties {
@@ -17,6 +19,7 @@ export type ICustomMenuItemComponent = React.FC {
item: IMenuCustomItem;
+ context: IDataContext;
onClick?: (keepMenuOpen: boolean) => void;
className?: string;
}
diff --git a/webapp/packages/core-view/src/Menu/MenuItem/IMenuSubMenuItem.ts b/webapp/packages/core-view/src/Menu/MenuItem/IMenuSubMenuItem.ts
index 5c5b6e4b95..b80ba2fe15 100644
--- a/webapp/packages/core-view/src/Menu/MenuItem/IMenuSubMenuItem.ts
+++ b/webapp/packages/core-view/src/Menu/MenuItem/IMenuSubMenuItem.ts
@@ -6,6 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import type { IMenu } from '../IMenu';
+import type { IMenuActionItem } from './IMenuActionItem';
import type { IMenuItem, IMenuItemEvents } from './IMenuItem';
export interface IMenuSubMenuEvents extends IMenuItemEvents {
@@ -17,6 +18,11 @@ export interface IMenuSubMenuItemProperties {
label?: string;
icon?: string;
tooltip?: string;
+
+ /**
+ * experimental, can be changed
+ */
+ readonly action?: IMenuActionItem;
getExtraProps?: () => TExtraProps;
iconComponent?: () => MenuSubMenuItemIconComponent;
}
diff --git a/webapp/packages/core-view/src/Menu/MenuItem/MenuCustomItem.ts b/webapp/packages/core-view/src/Menu/MenuItem/MenuCustomItem.ts
index ead28c5e32..1986c1100d 100644
--- a/webapp/packages/core-view/src/Menu/MenuItem/MenuCustomItem.ts
+++ b/webapp/packages/core-view/src/Menu/MenuItem/MenuCustomItem.ts
@@ -32,3 +32,7 @@ export class MenuCustomItem extends MenuItem implements I
this.getExtraProps = getters?.getExtraProps;
}
}
+
+export function isMenuCustomItem(obj: any): obj is MenuCustomItem {
+ return obj && obj instanceof MenuCustomItem;
+}
diff --git a/webapp/packages/core-view/src/Menu/MenuItem/MenuSubMenuItem.ts b/webapp/packages/core-view/src/Menu/MenuItem/MenuSubMenuItem.ts
index e5dcc2ef65..83ebfb2f6c 100644
--- a/webapp/packages/core-view/src/Menu/MenuItem/MenuSubMenuItem.ts
+++ b/webapp/packages/core-view/src/Menu/MenuItem/MenuSubMenuItem.ts
@@ -6,6 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import type { IMenu } from '../IMenu';
+import type { IMenuActionItem } from './IMenuActionItem';
import type { IMenuSubMenuEvents, IMenuSubMenuItem, IMenuSubMenuItemOptions, MenuSubMenuItemIconComponent } from './IMenuSubMenuItem';
import { MenuItem } from './MenuItem';
@@ -14,6 +15,11 @@ export class MenuSubMenuItem extends MenuItem implements
readonly label?: string;
readonly icon?: string;
readonly tooltip?: string;
+
+ /**
+ * experimental, can be changed
+ */
+ readonly action?: IMenuActionItem;
readonly events?: IMenuSubMenuEvents;
readonly hidden: boolean;
readonly getExtraProps?: () => TExtraProps;
@@ -25,6 +31,7 @@ export class MenuSubMenuItem extends MenuItem implements
this.label = options.label;
this.icon = options.icon;
this.tooltip = options.tooltip;
+ this.action = options.action;
this.getExtraProps = options.getExtraProps;
this.iconComponent = options.iconComponent;
this.hidden = false;
diff --git a/webapp/packages/core-view/src/Menu/MenuService.ts b/webapp/packages/core-view/src/Menu/MenuService.ts
index 30f6796ac5..77e2eb6629 100644
--- a/webapp/packages/core-view/src/Menu/MenuService.ts
+++ b/webapp/packages/core-view/src/Menu/MenuService.ts
@@ -131,7 +131,10 @@ export class MenuService {
return this.createActionItem(context, item) as IMenuItem;
}
if (isMenu(item)) {
- return new MenuSubMenuItem({ menu: item }) as IMenuItem;
+ return new MenuSubMenuItem({
+ menu: item,
+ action: (isAction(item.action) ? this.createActionItem(context, item.action) : undefined) || undefined,
+ }) as IMenuItem;
}
return item;
})
diff --git a/webapp/packages/core-view/src/Menu/createMenu.ts b/webapp/packages/core-view/src/Menu/createMenu.ts
index 9ea3b50a3a..9d27c1bc49 100644
--- a/webapp/packages/core-view/src/Menu/createMenu.ts
+++ b/webapp/packages/core-view/src/Menu/createMenu.ts
@@ -5,16 +5,18 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import type { IAction } from '../Action/IAction';
import type { IMenu } from './IMenu';
const menuSymbol = Symbol('@menu');
-export function createMenu(id: string, label: string, icon?: string, tooltip?: string): IMenu {
+export function createMenu(id: string, label: string, icon?: string, tooltip?: string, action?: IAction): IMenu {
const menu = {
id: `@menu/${id}`,
label,
icon,
tooltip,
+ action,
};
(menu as any)[menuSymbol] = true;
diff --git a/webapp/packages/core-website/package.json b/webapp/packages/core-website/package.json
index 969115fc85..0791083df7 100644
--- a/webapp/packages/core-website/package.json
+++ b/webapp/packages/core-website/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"test": "core-cli-test",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
diff --git a/webapp/packages/plugin-administration/package.json b/webapp/packages/plugin-administration/package.json
index a10c842faf..0ec0567fde 100644
--- a/webapp/packages/plugin-administration/package.json
+++ b/webapp/packages/plugin-administration/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-authentication-administration/package.json b/webapp/packages/plugin-authentication-administration/package.json
index 84bfd2461b..b5c01d9c66 100644
--- a/webapp/packages/plugin-authentication-administration/package.json
+++ b/webapp/packages/plugin-authentication-administration/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationMenuBarItemStyles.module.css b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationMenuBarItemStyles.module.css
index a58ffebbf3..9f71d71f92 100644
--- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationMenuBarItemStyles.module.css
+++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationMenuBarItemStyles.module.css
@@ -5,12 +5,18 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-.menuBarItem {
- & .menuBarItemLabel {
- font-size: 14px;
- }
- & .menuBarItemIcon + .menuBarItemLabel {
- padding-left: initial;
+.menuBarItemGroup {
+ & .menuBarItem {
+ & .menuBarItemBox {
+ gap: 0;
+ }
+ & .menuBarItemLabel {
+ font-size: 14px;
+ }
+
+ & .menuBarItemIcon + .menuBarItemLabel {
+ padding-left: initial;
+ }
}
}
diff --git a/webapp/packages/plugin-authentication/package.json b/webapp/packages/plugin-authentication/package.json
index c1793f29b9..ef1fc18713 100644
--- a/webapp/packages/plugin-authentication/package.json
+++ b/webapp/packages/plugin-authentication/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts
index 63c95302a2..2af95402b6 100644
--- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts
+++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts
@@ -11,6 +11,7 @@ import { AdministrationScreenService } from '@cloudbeaver/core-administration';
import {
AppAuthService,
AUTH_PROVIDER_LOCAL_ID,
+ AuthInfoService,
AuthProviderContext,
AuthProviderService,
AuthProvidersResource,
@@ -19,7 +20,7 @@ import {
UserLogoutInfo,
} from '@cloudbeaver/core-authentication';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
-import type { DialogueStateResult } from '@cloudbeaver/core-dialogs';
+import { DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { Executor, ExecutorInterrupter, IExecutionContextProvider, IExecutorHandler } from '@cloudbeaver/core-executor';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
@@ -49,6 +50,7 @@ export class AuthenticationService extends Bootstrap {
private readonly appAuthService: AppAuthService,
private readonly authDialogService: AuthDialogService,
private readonly userInfoResource: UserInfoResource,
+ private readonly authInfoService: AuthInfoService,
private readonly notificationService: NotificationService,
private readonly administrationScreenService: AdministrationScreenService,
private readonly authProviderService: AuthProviderService,
@@ -65,6 +67,7 @@ export class AuthenticationService extends Bootstrap {
this.onLogin = new Executor();
this.onLogout.before(this.navigationService.navigationTask);
+ this.onLogin.before(this.navigationService.navigationTask, undefined, () => authInfoService.isAnonymous);
this.authPromise = null;
this.configureAuthProvider = null;
@@ -141,16 +144,6 @@ export class AuthenticationService extends Bootstrap {
options = observable(options);
- this.authPromise = this.authDialogService
- .showLoginForm(persistent, options)
- .then(async state => {
- await this.onLogin.execute('after');
- return state;
- })
- .finally(() => {
- this.authPromise = null;
- });
-
if (this.serverConfigResource.redirectOnFederatedAuth) {
await this.authProvidersResource.load(CachedMapAllKey);
@@ -168,6 +161,19 @@ export class AuthenticationService extends Bootstrap {
}
}
+ this.authPromise = this.authDialogService
+ .showLoginForm(persistent, options)
+ .then(async state => {
+ if (state === DialogueStateResult.Rejected) {
+ return state;
+ }
+ await this.onLogin.execute('after');
+ return state;
+ })
+ .finally(() => {
+ this.authPromise = null;
+ });
+
await this.authPromise;
}
diff --git a/webapp/packages/plugin-browser/package.json b/webapp/packages/plugin-browser/package.json
index 97b649aa0f..3ec9dfbb0d 100644
--- a/webapp/packages/plugin-browser/package.json
+++ b/webapp/packages/plugin-browser/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-codemirror6/package.json b/webapp/packages/plugin-codemirror6/package.json
index 27260449af..b0b86f1a71 100644
--- a/webapp/packages/plugin-codemirror6/package.json
+++ b/webapp/packages/plugin-codemirror6/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-connection-custom/package.json b/webapp/packages/plugin-connection-custom/package.json
index 166d45a05c..a2fd23c28a 100644
--- a/webapp/packages/plugin-connection-custom/package.json
+++ b/webapp/packages/plugin-connection-custom/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-connection-search/package.json b/webapp/packages/plugin-connection-search/package.json
index 90f6710e29..ba61294eb3 100644
--- a/webapp/packages/plugin-connection-search/package.json
+++ b/webapp/packages/plugin-connection-search/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-connection-template/package.json b/webapp/packages/plugin-connection-template/package.json
index 829b50f46c..f011ce67ac 100644
--- a/webapp/packages/plugin-connection-template/package.json
+++ b/webapp/packages/plugin-connection-template/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-connection-template/src/TemplateConnectionsService.ts b/webapp/packages/plugin-connection-template/src/TemplateConnectionsService.ts
index 729fee7d68..8150bdd7e8 100644
--- a/webapp/packages/plugin-connection-template/src/TemplateConnectionsService.ts
+++ b/webapp/packages/plugin-connection-template/src/TemplateConnectionsService.ts
@@ -24,5 +24,8 @@ export class TemplateConnectionsService {
// );
return [];
}
- constructor(private readonly templateConnectionsResource: TemplateConnectionsResource, private readonly projectsService: ProjectsService) {}
+ constructor(
+ private readonly templateConnectionsResource: TemplateConnectionsResource,
+ private readonly projectsService: ProjectsService,
+ ) {}
}
diff --git a/webapp/packages/plugin-connections-administration/package.json b/webapp/packages/plugin-connections-administration/package.json
index b74922aaef..49aeb1c144 100644
--- a/webapp/packages/plugin-connections-administration/package.json
+++ b/webapp/packages/plugin-connections-administration/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-connections/package.json b/webapp/packages/plugin-connections/package.json
index 73b2dcb49d..5ffdf09e13 100644
--- a/webapp/packages/plugin-connections/package.json
+++ b/webapp/packages/plugin-connections/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-connections/src/ConnectionAuthService.ts b/webapp/packages/plugin-connections/src/ConnectionAuthService.ts
index d9f0f41f5a..ddcc5d2d3b 100644
--- a/webapp/packages/plugin-connections/src/ConnectionAuthService.ts
+++ b/webapp/packages/plugin-connections/src/ConnectionAuthService.ts
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-import { AuthProviderService } from '@cloudbeaver/core-authentication';
+import { AuthInfoService, AuthProviderService } from '@cloudbeaver/core-authentication';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
import {
Connection,
@@ -28,16 +28,29 @@ export class ConnectionAuthService extends Dependency {
private readonly connectionInfoResource: ConnectionInfoResource,
private readonly commonDialogService: CommonDialogService,
private readonly authProviderService: AuthProviderService,
+ private readonly authInfoService: AuthInfoService,
private readonly connectionsManagerService: ConnectionsManagerService,
private readonly authenticationService: AuthenticationService,
) {
super();
connectionsManagerService.connectionExecutor.addHandler(this.connectionDialog.bind(this));
- this.authenticationService.onLogout.before(connectionsManagerService.onDisconnect, state => ({
- connections: connectionInfoResource.values.filter(connection => connection.connected).map(createConnectionParam),
- state,
- }));
+ this.authenticationService.onLogin.before(
+ connectionsManagerService.onDisconnect,
+ state => ({
+ connections: connectionInfoResource.values.filter(connection => connection.connected).map(createConnectionParam),
+ state,
+ }),
+ state => state === 'before' && authInfoService.isAnonymous,
+ );
+ this.authenticationService.onLogout.before(
+ connectionsManagerService.onDisconnect,
+ state => ({
+ connections: connectionInfoResource.values.filter(connection => connection.connected).map(createConnectionParam),
+ state,
+ }),
+ state => state === 'before',
+ );
}
private async connectionDialog(data: IRequireConnectionExecutorData, context: IExecutionContextProvider) {
diff --git a/webapp/packages/plugin-d3js/package.json b/webapp/packages/plugin-d3js/package.json
index 2dd4d23cac..7b1923c238 100644
--- a/webapp/packages/plugin-d3js/package.json
+++ b/webapp/packages/plugin-d3js/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"test": "core-cli-test",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
diff --git a/webapp/packages/plugin-data-export/package.json b/webapp/packages/plugin-data-export/package.json
index 28ffebd282..4205987f5c 100644
--- a/webapp/packages/plugin-data-export/package.json
+++ b/webapp/packages/plugin-data-export/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"test": "core-cli-test",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
diff --git a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts
index a488fb5c6e..f42885a63b 100644
--- a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts
+++ b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts
@@ -11,7 +11,6 @@ import { injectable } from '@cloudbeaver/core-di';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { DATA_CONTEXT_NAV_NODE, EObjectFeature } from '@cloudbeaver/core-navigation-tree';
-import { EAdminPermission, SessionPermissionsResource } from '@cloudbeaver/core-root';
import { withTimestamp } from '@cloudbeaver/core-utils';
import { ACTION_EXPORT, ActionService, menuExtractItems, MenuService } from '@cloudbeaver/core-view';
import {
@@ -20,24 +19,22 @@ import {
DATA_CONTEXT_DV_PRESENTATION,
DATA_VIEWER_DATA_MODEL_ACTIONS_MENU,
DataViewerPresentationType,
+ DataViewerService,
IDatabaseDataSource,
IDataContainerOptions,
} from '@cloudbeaver/plugin-data-viewer';
import type { IDataQueryOptions } from '@cloudbeaver/plugin-sql-editor';
-import { DataExportSettingsService } from './DataExportSettingsService';
-
const DataExportDialog = importLazyComponent(() => import('./Dialog/DataExportDialog').then(module => module.DataExportDialog));
@injectable()
export class DataExportMenuService {
constructor(
private readonly commonDialogService: CommonDialogService,
- private readonly dataExportSettingsService: DataExportSettingsService,
private readonly actionService: ActionService,
private readonly menuService: MenuService,
- private readonly sessionPermissionsResource: SessionPermissionsResource,
private readonly localizationService: LocalizationService,
+ private readonly dataViewerService: DataViewerService,
) {}
register(): void {
@@ -46,7 +43,7 @@ export class DataExportMenuService {
contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX],
isApplicable: context => {
const presentation = context.get(DATA_CONTEXT_DV_PRESENTATION);
- return !this.isExportDisabled() && (!presentation || presentation.type === DataViewerPresentationType.Data);
+ return this.dataViewerService.canExportData && (!presentation || presentation.type === DataViewerPresentationType.Data);
},
getItems(context, items) {
return [...items, ACTION_EXPORT];
@@ -60,6 +57,7 @@ export class DataExportMenuService {
id: 'data-export-base-handler',
menus: [DATA_VIEWER_DATA_MODEL_ACTIONS_MENU],
contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX],
+ isHidden: (context, action) => !this.dataViewerService.canExportData,
actions: [ACTION_EXPORT],
isDisabled(context) {
const model = context.get(DATA_CONTEXT_DV_DDM)!;
@@ -118,7 +116,7 @@ export class DataExportMenuService {
return false;
}
- return !this.isExportDisabled() && context.has(DATA_CONTEXT_CONNECTION);
+ return this.dataViewerService.canExportData && context.has(DATA_CONTEXT_CONNECTION);
},
getItems: (context, items) => [...items, ACTION_EXPORT],
});
@@ -141,12 +139,4 @@ export class DataExportMenuService {
},
});
}
-
- private isExportDisabled() {
- if (this.sessionPermissionsResource.has(EAdminPermission.admin)) {
- return false;
- }
-
- return this.dataExportSettingsService.disabled;
- }
}
diff --git a/webapp/packages/plugin-data-export/src/DataExportProcessService.ts b/webapp/packages/plugin-data-export/src/DataExportProcessService.ts
index 5b9856f42b..3f449313a2 100644
--- a/webapp/packages/plugin-data-export/src/DataExportProcessService.ts
+++ b/webapp/packages/plugin-data-export/src/DataExportProcessService.ts
@@ -31,7 +31,10 @@ export interface ExportProcess {
export class DataExportProcessService {
readonly exportProcesses = new OrderedMap(value => value.taskId);
- constructor(private readonly graphQLService: GraphQLService, private readonly notificationService: NotificationService) {}
+ constructor(
+ private readonly graphQLService: GraphQLService,
+ private readonly notificationService: NotificationService,
+ ) {}
async cancel(exportId: string): Promise {
const process = this.exportProcesses.get(exportId);
diff --git a/webapp/packages/plugin-data-export/src/DataExportService.ts b/webapp/packages/plugin-data-export/src/DataExportService.ts
index bd8fe8b31a..60b52fec00 100644
--- a/webapp/packages/plugin-data-export/src/DataExportService.ts
+++ b/webapp/packages/plugin-data-export/src/DataExportService.ts
@@ -15,7 +15,10 @@ import type { IExportContext } from './IExportContext';
@injectable()
export class DataExportService {
- constructor(private readonly notificationService: NotificationService, private readonly dataExportProcessService: DataExportProcessService) {}
+ constructor(
+ private readonly notificationService: NotificationService,
+ private readonly dataExportProcessService: DataExportProcessService,
+ ) {}
async cancel(exportId: string): Promise {
await this.dataExportProcessService.cancel(exportId);
diff --git a/webapp/packages/plugin-data-export/src/DataExportSettingsService.test.ts b/webapp/packages/plugin-data-export/src/DataExportSettingsService.test.ts
deleted file mode 100644
index b8bbf3fcfb..0000000000
--- a/webapp/packages/plugin-data-export/src/DataExportSettingsService.test.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 '@testing-library/jest-dom';
-
-import { coreAdministrationManifest } from '@cloudbeaver/core-administration';
-import { coreAppManifest } from '@cloudbeaver/core-app';
-import { coreAuthenticationManifest } from '@cloudbeaver/core-authentication';
-import { mockAuthentication } from '@cloudbeaver/core-authentication/dist/__custom_mocks__/mockAuthentication';
-import { coreBrowserManifest } from '@cloudbeaver/core-browser';
-import { coreClientActivityManifest } from '@cloudbeaver/core-client-activity';
-import { coreConnectionsManifest } from '@cloudbeaver/core-connections';
-import { coreDialogsManifest } from '@cloudbeaver/core-dialogs';
-import { coreEventsManifest } from '@cloudbeaver/core-events';
-import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
-import { coreNavigationTree } from '@cloudbeaver/core-navigation-tree';
-import { coreProjectsManifest } from '@cloudbeaver/core-projects';
-import { coreRootManifest, ServerConfigResource } from '@cloudbeaver/core-root';
-import { createGQLEndpoint } from '@cloudbeaver/core-root/dist/__custom_mocks__/createGQLEndpoint';
-import '@cloudbeaver/core-root/dist/__custom_mocks__/expectWebsocketClosedMessage';
-import { mockAppInit } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockAppInit';
-import { mockGraphQL } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockGraphQL';
-import { mockServerConfig } from '@cloudbeaver/core-root/dist/__custom_mocks__/resolvers/mockServerConfig';
-import { coreRoutingManifest } from '@cloudbeaver/core-routing';
-import { coreSDKManifest } from '@cloudbeaver/core-sdk';
-import { coreSettingsManifest } from '@cloudbeaver/core-settings';
-import {
- expectDeprecatedSettingMessage,
- expectNoDeprecatedSettingMessage,
-} from '@cloudbeaver/core-settings/dist/__custom_mocks__/expectDeprecatedSettingMessage';
-import { coreStorageManifest } from '@cloudbeaver/core-storage';
-import { coreUIManifest } from '@cloudbeaver/core-ui';
-import { coreViewManifest } from '@cloudbeaver/core-view';
-import { datasourceContextSwitchPluginManifest } from '@cloudbeaver/plugin-datasource-context-switch';
-import { navigationTabsPlugin } from '@cloudbeaver/plugin-navigation-tabs';
-import { navigationTreePlugin } from '@cloudbeaver/plugin-navigation-tree';
-import { objectViewerManifest } from '@cloudbeaver/plugin-object-viewer';
-import { createApp } from '@cloudbeaver/tests-runner';
-
-import { DataExportSettingsService } from './DataExportSettingsService';
-import { dataExportManifest } from './manifest';
-
-const endpoint = createGQLEndpoint();
-const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
-const app = createApp(
- dataExportManifest,
- coreLocalizationManifest,
- coreEventsManifest,
- coreRootManifest,
- coreSDKManifest,
- coreBrowserManifest,
- coreSettingsManifest,
- coreStorageManifest,
- coreViewManifest,
- coreAuthenticationManifest,
- coreProjectsManifest,
- coreUIManifest,
- coreRoutingManifest,
- coreAdministrationManifest,
- coreConnectionsManifest,
- coreDialogsManifest,
- coreNavigationTree,
- coreAppManifest,
- datasourceContextSwitchPluginManifest,
- navigationTreePlugin,
- navigationTabsPlugin,
- objectViewerManifest,
- coreClientActivityManifest,
-);
-
-const testValueA = true;
-const testValueB = true;
-
-const deprecatedSettings = {
- 'plugin_data_export.disabled': testValueB,
-};
-
-const newSettings = {
- ...deprecatedSettings,
- 'plugin.data-export.disabled': testValueA,
-};
-
-test('New settings override deprecated', async () => {
- const settings = app.injector.getServiceByClass(DataExportSettingsService);
- const config = app.injector.getServiceByClass(ServerConfigResource);
-
- server.use(endpoint.query('serverConfig', mockServerConfig(newSettings)));
-
- await config.refresh();
-
- expect(settings.disabled).toBe(testValueA);
- expectNoDeprecatedSettingMessage();
-});
-
-test('Deprecated settings are used if new settings are not defined', async () => {
- const settings = app.injector.getServiceByClass(DataExportSettingsService);
- const config = app.injector.getServiceByClass(ServerConfigResource);
-
- server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings)));
-
- await config.refresh();
-
- expect(settings.disabled).toBe(testValueB);
- expectDeprecatedSettingMessage();
-});
diff --git a/webapp/packages/plugin-data-export/src/DataExportSettingsService.ts b/webapp/packages/plugin-data-export/src/DataExportSettingsService.ts
deleted file mode 100644
index 5210503545..0000000000
--- a/webapp/packages/plugin-data-export/src/DataExportSettingsService.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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 { Dependency, injectable } from '@cloudbeaver/core-di';
-import {
- createSettingsAliasResolver,
- ROOT_SETTINGS_LAYER,
- SettingsManagerService,
- SettingsProvider,
- SettingsProviderService,
- SettingsResolverService,
-} from '@cloudbeaver/core-settings';
-import { schema, schemaExtra } from '@cloudbeaver/core-utils';
-
-const defaultSettings = schema.object({
- 'plugin.data-export.disabled': schemaExtra.stringedBoolean().default(false),
-});
-
-export type DataExportSettings = schema.infer;
-
-@injectable()
-export class DataExportSettingsService extends Dependency {
- get disabled(): boolean {
- return this.settings.getValue('plugin.data-export.disabled');
- }
- readonly settings: SettingsProvider;
-
- constructor(
- private readonly settingsProviderService: SettingsProviderService,
- private readonly settingsManagerService: SettingsManagerService,
- private readonly settingsResolverService: SettingsResolverService,
- ) {
- super();
- this.settings = this.settingsProviderService.createSettings(defaultSettings);
- this.settingsResolverService.addResolver(
- ROOT_SETTINGS_LAYER,
- /** @deprecated Use settings instead, will be removed in 23.0.0 */
- createSettingsAliasResolver(this.settingsResolverService, this.settings, { 'plugin.data-export.disabled': 'plugin_data_export.disabled' }),
- );
-
- this.registerSettings();
- }
-
- private registerSettings() {
- this.settingsManagerService.registerSettings(this.settings, () => [
- // {
- // group: DATA_EXPORT_SETTINGS_GROUP,
- // key: 'disabled',
- // type: ESettingsValueType.Checkbox,
- // name: 'Disable data export',
- // },
- ]);
- }
-}
diff --git a/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts
index 97c904e309..7869b1557a 100644
--- a/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts
+++ b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts
@@ -12,7 +12,10 @@ import { DataTransferProcessorInfo, GraphQLService } from '@cloudbeaver/core-sdk
@injectable()
export class DataTransferProcessorsResource extends CachedMapResource {
- constructor(private readonly graphQLService: GraphQLService, serverConfigResource: ServerConfigResource) {
+ constructor(
+ private readonly graphQLService: GraphQLService,
+ serverConfigResource: ServerConfigResource,
+ ) {
super(() => new Map());
this.sync(
serverConfigResource,
diff --git a/webapp/packages/plugin-data-export/src/ExportFromContainerProcess.ts b/webapp/packages/plugin-data-export/src/ExportFromContainerProcess.ts
index 6fba288238..9ae42a62dd 100644
--- a/webapp/packages/plugin-data-export/src/ExportFromContainerProcess.ts
+++ b/webapp/packages/plugin-data-export/src/ExportFromContainerProcess.ts
@@ -18,7 +18,10 @@ export class ExportFromContainerProcess extends Deferred {
private timeout?: CancellablePromise;
private isCancelConfirmed = false; // true when server successfully executed cancelQueryAsync
- constructor(private readonly graphQLService: GraphQLService, private readonly notificationService: NotificationService) {
+ constructor(
+ private readonly graphQLService: GraphQLService,
+ private readonly notificationService: NotificationService,
+ ) {
super();
}
diff --git a/webapp/packages/plugin-data-export/src/ExportFromResultsProcess.ts b/webapp/packages/plugin-data-export/src/ExportFromResultsProcess.ts
index d189da665a..5fc5f4b144 100644
--- a/webapp/packages/plugin-data-export/src/ExportFromResultsProcess.ts
+++ b/webapp/packages/plugin-data-export/src/ExportFromResultsProcess.ts
@@ -18,7 +18,10 @@ export class ExportFromResultsProcess extends Deferred {
private timeout?: CancellablePromise;
private isCancelConfirmed = false; // true when server successfully executed cancelQueryAsync
- constructor(private readonly graphQLService: GraphQLService, private readonly notificationService: NotificationService) {
+ constructor(
+ private readonly graphQLService: GraphQLService,
+ private readonly notificationService: NotificationService,
+ ) {
super();
}
diff --git a/webapp/packages/plugin-data-export/src/manifest.ts b/webapp/packages/plugin-data-export/src/manifest.ts
index 2c58f37f4f..c2a8e77884 100644
--- a/webapp/packages/plugin-data-export/src/manifest.ts
+++ b/webapp/packages/plugin-data-export/src/manifest.ts
@@ -15,7 +15,6 @@ export const dataExportManifest: PluginManifest = {
providers: [
() => import('./Bootstrap').then(m => m.Bootstrap),
() => import('./DataExportMenuService').then(m => m.DataExportMenuService),
- () => import('./DataExportSettingsService').then(m => m.DataExportSettingsService),
() => import('./DataExportService').then(m => m.DataExportService),
() => import('./DataExportProcessService').then(m => m.DataExportProcessService),
() => import('./DataTransferProcessorsResource').then(m => m.DataTransferProcessorsResource),
diff --git a/webapp/packages/plugin-data-import/package.json b/webapp/packages/plugin-data-import/package.json
index 3ac606776d..a065d42143 100644
--- a/webapp/packages/plugin-data-import/package.json
+++ b/webapp/packages/plugin-data-import/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"test": "core-cli-test",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
diff --git a/webapp/packages/plugin-data-spreadsheet-new/package.json b/webapp/packages/plugin-data-spreadsheet-new/package.json
index f75b57d8dd..d7d18a048a 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/package.json
+++ b/webapp/packages/plugin-data-spreadsheet-new/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"test": "core-cli-test",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts
index 97aabdb96c..fbaaad2931 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts
@@ -62,9 +62,8 @@ export class DataGridContextMenuFilterService {
throw new Error(`Failed to get result column info for the following column index: "${column.index}"`);
}
- await model.requestDataAction(async () => {
+ await model.request(() => {
constraints.setFilter(resultColumn.position, operator, filterValue);
- await model.request(true);
});
}
@@ -150,9 +149,8 @@ export class DataGridContextMenuFilterService {
const { model, resultIndex } = context.data;
const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction);
- await model.requestDataAction(async () => {
+ await model.request(() => {
constraints.deleteData();
- await model.request(true);
});
},
});
@@ -362,9 +360,8 @@ export class DataGridContextMenuFilterService {
throw new Error(`Failed to get result column info for the following column index: "${key.column.index}"`);
}
- await model.requestDataAction(async () => {
+ await model.request(() => {
constraints.deleteFilter(resultColumn.position);
- await model.request(true);
});
},
});
@@ -386,9 +383,8 @@ export class DataGridContextMenuFilterService {
const { model, resultIndex } = context.data;
const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction);
- await model.requestDataAction(async () => {
+ await model.request(() => {
constraints.deleteDataFilters();
- await model.request(true);
});
},
});
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts
index 1e8d468594..6eb31e9616 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts
@@ -36,9 +36,8 @@ export class DataGridContextMenuOrderService {
throw new Error(`Failed to get result column info for the following column index: "${column.index}"`);
}
- await model.requestDataAction(async () => {
+ await model.request(() => {
constraints.setOrder(resultColumn.position, order, true);
- await model.request(true);
});
}
@@ -130,9 +129,8 @@ export class DataGridContextMenuOrderService {
isDisabled: context => context.data.model.isLoading(),
onClick: async context => {
const constraints = context.data.model.source.getAction(context.data.resultIndex, DatabaseDataConstraintAction);
- await context.data.model.requestDataAction(async () => {
+ await context.data.model.request(() => {
constraints.deleteOrders();
- await context.data.model.request(true);
});
},
});
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts
index f0296277a1..70b61d8a7b 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts
@@ -8,7 +8,13 @@
import { selectFiles } from '@cloudbeaver/core-browser';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
-import { createResultSetBlobValue, ResultSetDataContentAction, ResultSetEditAction, ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
+import {
+ createResultSetBlobValue,
+ DataViewerService,
+ ResultSetDataContentAction,
+ ResultSetEditAction,
+ ResultSetFormatAction,
+} from '@cloudbeaver/plugin-data-viewer';
import { DataGridContextMenuService } from './DataGridContextMenuService';
@@ -17,6 +23,7 @@ export class DataGridContextMenuSaveContentService {
constructor(
private readonly dataGridContextMenuService: DataGridContextMenuService,
private readonly notificationService: NotificationService,
+ private readonly dataViewerService: DataViewerService,
) {}
register(): void {
@@ -38,7 +45,8 @@ export class DataGridContextMenuSaveContentService {
},
isHidden: context => {
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
- return !content.isDownloadable(context.data.key);
+
+ return !content.isDownloadable(context.data.key) || !this.dataViewerService.canExportData;
},
isDisabled: context => {
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx
index 2371708243..69a1681e8a 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx
@@ -35,9 +35,8 @@ export const OrderButton = observer(function OrderButton({ model, resultI
const handleSort = async (e: React.MouseEvent) => {
const nextOrder = getNextOrder(currentOrder);
- await model.requestDataAction(async () => {
+ await model.request(() => {
constraints.setOrder(attributePosition, nextOrder, e.ctrlKey || e.metaKey);
- await model.request(true);
});
};
diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/package.json b/webapp/packages/plugin-data-viewer-result-set-grouping/package.json
index 6e05ae23ab..312d8f5081 100644
--- a/webapp/packages/plugin-data-viewer-result-set-grouping/package.json
+++ b/webapp/packages/plugin-data-viewer-result-set-grouping/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DEFAULT_GROUPING_QUERY_OPERATION.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DEFAULT_GROUPING_QUERY_OPERATION.ts
index 3e9d744aac..07b1734997 100644
--- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DEFAULT_GROUPING_QUERY_OPERATION.ts
+++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DEFAULT_GROUPING_QUERY_OPERATION.ts
@@ -1 +1,8 @@
-export const DEFAULT_GROUPING_QUERY_OPERATION = 'COUNT(*)';
\ No newline at end of file
+/*
+ * 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.
+ */
+export const DEFAULT_GROUPING_QUERY_OPERATION = 'COUNT(*)';
diff --git a/webapp/packages/plugin-data-viewer-result-trace-details/package.json b/webapp/packages/plugin-data-viewer-result-trace-details/package.json
index 333f595dab..84f3da259f 100644
--- a/webapp/packages/plugin-data-viewer-result-trace-details/package.json
+++ b/webapp/packages/plugin-data-viewer-result-trace-details/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
},
diff --git a/webapp/packages/plugin-data-viewer/package.json b/webapp/packages/plugin-data-viewer/package.json
index d311e5d333..14218455fa 100644
--- a/webapp/packages/plugin-data-viewer/package.json
+++ b/webapp/packages/plugin-data-viewer/package.json
@@ -13,7 +13,6 @@
"build": "tsc -b",
"clean": "rimraf --glob dist",
"lint": "eslint ./src/ --ext .ts,.tsx",
- "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
"test": "core-cli-test",
"validate-dependencies": "core-cli-validate-dependencies",
"update-ts-references": "yarn run clean && typescript-resolve-references"
diff --git a/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts b/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts
index 710d0c4336..d0d835b7f9 100644
--- a/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts
+++ b/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts
@@ -26,7 +26,7 @@ import type { IResultSetBlobValue } from './DatabaseDataModel/Actions/ResultSet/
import { ResultSetEditAction } from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
import type { IDatabaseDataOptions } from './DatabaseDataModel/IDatabaseDataOptions';
import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet';
-import { ResultSetDataSource } from './ResultSetDataSource';
+import { ResultSetDataSource } from './ResultSet/ResultSetDataSource';
export interface IDataContainerOptions extends IDatabaseDataOptions {
containerNodePath: string;
@@ -60,12 +60,8 @@ export class ContainerDataSource extends ResultSetDataSource {
@@ -191,7 +187,7 @@ export class ContainerDataSource extends ResultSetDataSource operation === DatabaseDataSourceOperation.Request, this.checkUnsavedData));
}
}
- private async checkUnsavedData({ type, model }: IRequestEventData, contexts: IExecutionContextProvider>) {
- if (type === 'before') {
+ private async checkUnsavedData({ stage, model }: IRequestEventData, contexts: IExecutionContextProvider>) {
+ if (stage === 'request') {
const confirmationContext = contexts.getContext(SaveConfirmedContext);
if (confirmationContext.confirmed === false) {
@@ -70,8 +72,8 @@ export class DataViewerDataChangeConfirmationService {
}
}
} catch (exception: any) {
- ExecutorInterrupter.interrupt(contexts);
this.notificationService.logException(exception, 'data_viewer_data_save_error_title');
+ throw exception;
}
}
}
diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerService.ts
index 0a93eeb119..03f96962e3 100644
--- a/webapp/packages/plugin-data-viewer/src/DataViewerService.ts
+++ b/webapp/packages/plugin-data-viewer/src/DataViewerService.ts
@@ -17,6 +17,10 @@ export class DataViewerService {
return this.sessionPermissionsResource.has(EAdminPermission.admin) || !this.dataViewerSettingsService.disableCopyData;
}
+ get canExportData() {
+ return this.sessionPermissionsResource.has(EAdminPermission.admin) || !this.dataViewerSettingsService.disableExportData;
+ }
+
constructor(
private readonly dataViewerSettingsService: DataViewerSettingsService,
private readonly sessionPermissionsResource: SessionPermissionsResource,
diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts
index e93a0de0cc..6cefb37dd4 100644
--- a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts
+++ b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts
@@ -77,11 +77,14 @@ const testValueNew = false;
const deprecatedSettings = {
'core.app.dataViewer.disableEdit': testValueDeprecated,
+ 'plugin.data-viewer.disabled': testValueDeprecated,
+ 'plugin_data_export.disabled': testValueDeprecated,
};
const newSettings = {
...deprecatedSettings,
'plugin.data-viewer.disableEdit': testValueNew,
+ 'plugin.data-viewer.export.disabled': testValueNew,
};
async function setupSettingsService(mockConfig: any = {}) {
@@ -99,6 +102,8 @@ test('New settings override deprecated settings', async () => {
const settingsService = await setupSettingsService(newSettings);
expect(settingsService.disableEdit).toBe(testValueNew);
+ expect(settingsService.disableExportData).toBe(testValueNew);
+
expectNoDeprecatedSettingMessage();
});
@@ -106,6 +111,8 @@ test('Deprecated settings are used if new settings are not defined', async () =>
const settingsService = await setupSettingsService(deprecatedSettings);
expect(settingsService.disableEdit).toBe(testValueDeprecated);
+ expect(settingsService.disableExportData).toBe(testValueDeprecated);
+
expectDeprecatedSettingMessage();
});
diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts
index 89881ba7e6..ecd33d456a 100644
--- a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts
+++ b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts
@@ -31,6 +31,7 @@ const defaultSettings = schema.object({
'plugin.data-viewer.fetchMin': schema.coerce.number().min(FETCH_MIN).default(DEFAULT_FETCH_SIZE),
'plugin.data-viewer.fetchMax': schema.coerce.number().min(FETCH_MIN).default(FETCH_MAX),
'resultset.maxrows': schema.coerce.number().min(FETCH_MIN).max(FETCH_MAX).default(DEFAULT_FETCH_SIZE),
+ 'plugin.data-viewer.export.disabled': schemaExtra.stringedBoolean().default(false),
});
export type DataViewerSettings = schema.infer;
@@ -45,6 +46,10 @@ export class DataViewerSettingsService extends Dependency {
return this.settings.getValue('plugin.data-viewer.disableCopyData');
}
+ get disableExportData(): boolean {
+ return this.settings.getValue('plugin.data-viewer.export.disabled');
+ }
+
get maxFetchSize(): number {
return this.settings.getValue('plugin.data-viewer.fetchMax');
}
@@ -75,12 +80,17 @@ export class DataViewerSettingsService extends Dependency {
'plugin.data-viewer.disableCopyData': 'core.app.dataViewer.disableCopyData',
'plugin.data-viewer.fetchMin': 'core.app.dataViewer.fetchMin',
'plugin.data-viewer.fetchMax': 'core.app.dataViewer.fetchMax',
+ 'plugin.data-viewer.export.disabled': 'plugin.data-export.disabled',
'resultset.maxrows': 'core.app.dataViewer.fetchDefault',
}),
/** @deprecated Use settings instead, will be removed in 25.0.0 */
createSettingsAliasResolver(this.settingsResolverService, this.settings, {
'resultset.maxrows': 'plugin.data-viewer.fetchDefault',
}),
+ /** @deprecated Use settings instead, will be removed in 23.0.0 */
+ createSettingsAliasResolver(this.settingsResolverService, this.settings, {
+ 'plugin.data-viewer.export.disabled': 'plugin_data_export.disabled',
+ }),
);
this.registerSettings();
@@ -147,6 +157,16 @@ export class DataViewerSettingsService extends Dependency {
description: 'settings_data_editor_fetch_max_description',
group: DATA_EDITOR_SETTINGS_GROUP,
},
+ {
+ group: DATA_EDITOR_SETTINGS_GROUP,
+ key: 'plugin.data-viewer.export.disabled',
+ type: ESettingsValueType.Checkbox,
+ name: 'settings_data_editor_disable_data_export_name',
+ description: 'settings_data_editor_disable_data_export_description',
+ access: {
+ scope: ['server'],
+ },
+ },
];
if (!this.serverSettingsManagerService.providedSettings.has('resultset.maxrows')) {
diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts
index f45ca1d26b..ed2b276f48 100644
--- a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts
+++ b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts
@@ -5,6 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { importLazyComponent } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource, ConnectionsManagerService, IConnectionExecutorData } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
@@ -14,11 +15,12 @@ import { resourceKeyList } from '@cloudbeaver/core-resource';
import { ITab, NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
import { DBObjectPageService, IObjectViewerTabState, isObjectViewerTab, ObjectPage, ObjectViewerTabService } from '@cloudbeaver/plugin-object-viewer';
-import { DataViewerPanel } from './DataViewerPage/DataViewerPanel';
-import { DataViewerTab } from './DataViewerPage/DataViewerTab';
import type { IDataViewerPageState } from './IDataViewerPageState';
import { TableViewerStorageService } from './TableViewer/TableViewerStorageService';
+const DataViewerTab = importLazyComponent(() => import('./DataViewerPage/DataViewerTab').then(module => module.DataViewerTab));
+const DataViewerPanel = importLazyComponent(() => import('./DataViewerPage/DataViewerPanel').then(module => module.DataViewerPanel));
+
@injectable()
export class DataViewerTabService {
readonly page: ObjectPage;
@@ -40,6 +42,7 @@ export class DataViewerTabService {
getTabComponent: () => DataViewerTab,
getPanelComponent: () => DataViewerPanel,
onRestore: this.handleTabRestore.bind(this),
+ onUnload: this.handleTabClose.bind(this),
canClose: this.handleTabCanClose.bind(this),
onClose: this.handleTabClose.bind(this),
});
@@ -55,25 +58,27 @@ export class DataViewerTabService {
private async disconnectHandler(data: IConnectionExecutorData, contexts: IExecutionContextProvider) {
const connectionsKey = resourceKeyList(data.connections);
- if (data.state === 'before') {
- const tabs = Array.from(
- this.navigationTabsService.findTabs(
- isObjectViewerTab(tab => {
- if (!tab.handlerState.connectionKey) {
- return false;
- }
- return this.connectionInfoResource.isIntersect(connectionsKey, tab.handlerState.connectionKey);
- }),
- ),
- );
-
- for (const tab of tabs) {
+ const tabs = Array.from(
+ this.navigationTabsService.findTabs(
+ isObjectViewerTab(tab => {
+ if (!tab.handlerState.connectionKey) {
+ return false;
+ }
+ return this.connectionInfoResource.isIntersect(connectionsKey, tab.handlerState.connectionKey);
+ }),
+ ),
+ );
+
+ for (const tab of tabs) {
+ if (data.state === 'before') {
const canDisconnect = await this.handleTabCanClose(tab);
if (!canDisconnect) {
ExecutorInterrupter.interrupt(contexts);
return;
}
+ } else if (isObjectViewerTab(tab) && tab.handlerState.tableId) {
+ await this.disposeTableModel(tab.handlerState.tableId);
}
}
}
@@ -106,28 +111,25 @@ export class DataViewerTabService {
const model = this.tableViewerStorageService.get(tab.handlerState.tableId || '');
if (model) {
- let canClose = false;
- try {
- await model.requestDataAction(() => {
- canClose = true;
- });
- } catch {}
-
- return canClose;
+ return await model.source.canSafelyDispose();
}
return true;
}
private async handleTabClose(tab: ITab) {
- const tableId = tab.handlerState.tableId;
+ if (tab.handlerState.tableId) {
+ await this.disposeTableModel(tab.handlerState.tableId);
+ }
+ }
+ private async disposeTableModel(tableId: string) {
if (tableId) {
const model = this.tableViewerStorageService.get(tableId);
if (model) {
- this.tableViewerStorageService.remove(tableId);
await model.dispose();
+ this.tableViewerStorageService.remove(tableId);
}
}
}
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseRefreshAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseRefreshAction.ts
new file mode 100644
index 0000000000..6d18444e15
--- /dev/null
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseRefreshAction.ts
@@ -0,0 +1,111 @@
+/*
+ * 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 { observable } from 'mobx';
+
+import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
+
+import { DatabaseDataAction } from '../DatabaseDataAction';
+import type { IDatabaseDataResult } from '../IDatabaseDataResult';
+import type { IDatabaseDataSource } from '../IDatabaseDataSource';
+import { databaseDataAction } from './DatabaseDataActionDecorator';
+
+export interface IDatabaseRefreshState {
+ interval: number;
+ paused: boolean;
+ stopOnError: boolean;
+}
+
+@databaseDataAction()
+export class DatabaseRefreshAction extends DatabaseDataAction {
+ static dataFormat: ResultDataFormat[] | null = null;
+
+ get isAutoRefresh(): boolean {
+ return this.state.interval > 0;
+ }
+
+ get interval(): number {
+ return this.state.interval;
+ }
+
+ get paused(): boolean {
+ return this.state.paused;
+ }
+
+ get stopOnError(): boolean {
+ return this.state.stopOnError;
+ }
+
+ private state: IDatabaseRefreshState;
+ private timer: ReturnType | null;
+ constructor(source: IDatabaseDataSource) {
+ super(source);
+ this.state = observable({ interval: 0, paused: false, stopOnError: true });
+ this.timer = null;
+ }
+
+ setInterval(interval: number): void {
+ this.state.interval = interval;
+
+ if (this.state.interval) {
+ this.startTimer();
+ this.resume();
+ } else {
+ this.stopTimer();
+ }
+ }
+
+ setStopOnError(stopOnError: boolean): void {
+ this.state.stopOnError = stopOnError;
+ }
+
+ pause(): void {
+ this.state.paused = true;
+ }
+
+ resume(): void {
+ this.state.paused = false;
+ }
+
+ dispose(): void {
+ this.stopTimer();
+ }
+
+ private stopTimer(): void {
+ if (this.timer) {
+ clearTimeout(this.timer);
+ this.timer = null;
+ }
+ }
+
+ private startTimer(): void {
+ if (this.state.interval <= 0) {
+ return;
+ }
+ if (this.timer) {
+ this.stopTimer();
+ }
+ this.timer = setTimeout(this.refresh.bind(this), this.state.interval);
+ }
+
+ private async refresh(): Promise {
+ if (this.state.paused) {
+ this.startTimer();
+ return;
+ }
+ try {
+ await this.source.refreshData();
+ this.startTimer();
+ } catch (exception) {
+ if (this.state.stopOnError) {
+ this.setInterval(0);
+ } else {
+ this.startTimer();
+ }
+ }
+ }
+}
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts
index 4d425571d8..78ed677a46 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts
@@ -125,7 +125,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction {
+ const fullText = await this.source.runOperation(async () => {
try {
this.updateCache(element, { loading: true });
return await this.loadFileFullText(this.result, column.position, row);
@@ -134,6 +134,10 @@ export class ResultSetDataContentAction extends DatabaseDataAction {
const column = this.data.getColumn(element.column);
const row = this.data.getRowValue(element.row);
@@ -175,7 +179,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction {
+ const url = await this.source.runOperation(async () => {
try {
this.updateCache(element, { loading: true });
return await this.loadDataURL(this.result, column.position, row);
@@ -184,6 +188,10 @@ export class ResultSetDataContentAction extends DatabaseDataAction>;
- private currentTask: Promise | null;
-
constructor(source: IDatabaseDataSource) {
this.id = uuid();
this.name = null;
@@ -43,7 +41,7 @@ export class DatabaseDataModel ({ ...data, model: this }));
makeObservable(this, {
countGain: observable,
@@ -63,7 +61,7 @@ export class DatabaseDataModel= count;
+ return this.source.isDataAvailable(offset, count);
}
getResults(): TResult[] {
@@ -121,37 +119,29 @@ export class DatabaseDataModel {
- await this.requestSaveAction(() => this.source.saveData());
+ await this.source.saveData();
}
async retry(): Promise {
- await this.requestDataAction(() => this.source.retry());
+ await this.source.retry();
}
- async refresh(concurrent?: boolean): Promise {
- if (concurrent) {
- await this.source.refreshData();
- return;
- }
- await this.requestDataAction(() => this.source.refreshData());
+ async refresh(): Promise {
+ await this.source.refreshData();
}
- async request(concurrent?: boolean): Promise {
- if (concurrent) {
- await this.source.requestData();
- return;
- }
- await this.requestDataAction(() => this.source.requestData());
+ async request(mutation?: () => void): Promise {
+ await this.source.requestData(mutation);
}
async reload(): Promise {
- await this.requestDataAction(() => this.source.setSlice(0, this.countGain).requestData());
+ await this.request(() => {
+ this.setSlice(0, this.countGain);
+ });
}
async requestDataPortion(offset: number, count: number): Promise {
- if (!this.isDataAvailable(offset, count)) {
- await this.requestDataAction(() => this.source.setSlice(offset, count).requestData());
- }
+ await this.source.requestDataPortion(offset, count);
}
async cancel(): Promise {
@@ -166,38 +156,4 @@ export class DatabaseDataModel Promise | void): Promise {
- return action();
- }
-
- async requestDataAction(action: () => Promise | void): Promise {
- if (this.currentTask) {
- return this.currentTask;
- }
-
- try {
- this.currentTask = this.requestDataActionTask(action);
- return await this.currentTask;
- } finally {
- this.currentTask = null;
- }
- }
-
- private async requestDataActionTask(action: () => Promise | void): Promise {
- let contexts = await this.onRequest.execute({ type: 'on', model: this });
-
- if (ExecutorInterrupter.isInterrupted(contexts)) {
- return;
- }
-
- contexts = await this.onRequest.execute({ type: 'before', model: this });
-
- if (ExecutorInterrupter.isInterrupted(contexts)) {
- return;
- }
-
- await action();
- await this.onRequest.execute({ type: 'after', model: this });
- }
}
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts
index 2f79fd236b..cdd6b0c273 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts
@@ -9,14 +9,20 @@ import { action, makeObservable, observable, toJS } from 'mobx';
import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections';
import type { IServiceInjector } from '@cloudbeaver/core-di';
-import { ITask, Task } from '@cloudbeaver/core-executor';
+import { Executor, ExecutorInterrupter, IExecutor, ITask, Task } from '@cloudbeaver/core-executor';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { DatabaseDataActions } from './DatabaseDataActions';
import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction';
import type { IDatabaseDataActions } from './IDatabaseDataActions';
import type { IDatabaseDataResult } from './IDatabaseDataResult';
-import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
+import {
+ DatabaseDataAccessMode,
+ DatabaseDataSourceOperation,
+ IDatabaseDataSource,
+ IDatabaseDataSourceOperationEvent,
+ IRequestInfo,
+} from './IDatabaseDataSource';
export abstract class DatabaseDataSource implements IDatabaseDataSource {
access: DatabaseDataAccessMode;
@@ -32,20 +38,19 @@ export abstract class DatabaseDataSource | null;
get canCancel(): boolean {
- if (this.activeTask instanceof Task) {
- return this.activeTask.cancellable;
+ if (this.activeOperation instanceof Task) {
+ return this.activeOperation.cancellable;
}
return false;
}
get cancelled(): boolean {
- if (this.activeTask instanceof Task) {
- return this.activeTask.cancelled;
+ if (this.activeOperation instanceof Task) {
+ return this.activeOperation.cancelled;
}
return false;
@@ -53,10 +58,14 @@ export abstract class DatabaseDataSource | null;
- private activeSave: Promise | null;
- private activeTask: Promise | null;
private lastAction: () => Promise;
+ private outdated: boolean;
+
+ readonly onOperation: IExecutor;
+ private get activeOperation(): Promise | null {
+ return this.activeOperationStack[this.activeOperationStack.length - 1] ?? null;
+ }
+ private readonly activeOperationStack: Array>;
constructor(serviceInjector: IServiceInjector) {
this.serviceInjector = serviceInjector;
@@ -64,6 +73,7 @@ export abstract class DatabaseDataSource, 'activeRequest' | 'activeSave' | 'activeTask' | 'disabled'>(this, {
+ makeObservable, 'disabled' | 'activeOperationStack' | 'outdated'>(this, {
access: observable,
dataFormat: observable,
supportedDataFormats: observable,
@@ -102,10 +110,8 @@ export abstract class DatabaseDataSource= count;
+ }
+
isLoadable(): boolean {
return !this.isLoading() && !this.disabled;
}
@@ -211,11 +225,11 @@ export abstract class DatabaseDataSource(task: () => Promise): Promise {
- if (this.activeTask) {
- try {
- await this.activeTask;
- } catch {}
- }
-
- if (this.activeSave) {
- try {
- await this.activeSave;
- } catch {}
- }
-
- if (this.activeRequest) {
- try {
- await this.activeRequest;
- } catch {}
- }
-
- this.activeTask = task();
-
- try {
- return await this.activeTask;
- } finally {
- this.activeTask = null;
- }
+ async runOperation(task: () => Promise): Promise {
+ return this.tryExecuteOperation(DatabaseDataSourceOperation.Task, task);
}
- async requestData(): Promise {
- if (this.activeSave) {
- try {
- await this.activeSave;
- } finally {
- }
- }
-
- if (this.activeRequest) {
- await this.activeRequest;
- return;
- }
- this.lastAction = this.requestData.bind(this);
+ async requestData(mutation?: () => void): Promise {
+ await this.tryExecuteOperation(DatabaseDataSourceOperation.Request, () => {
+ this.lastAction = this.requestData.bind(this);
- try {
- this.activeRequest = this.requestDataAction();
+ mutation?.();
+ return this.requestDataAction();
+ });
+ }
- const data = await this.activeRequest;
- this.outdated = false;
+ async requestDataPortion(offset: number, count: number): Promise {
+ await this.tryExecuteOperation(DatabaseDataSourceOperation.Request, () => {
+ if (!this.isDataAvailable(offset, count)) {
+ this.lastAction = this.requestDataPortion.bind(this, offset, count);
- if (data !== null) {
- this.setResults(data);
+ this.setSlice(offset, count);
+ return this.requestDataAction();
}
- } finally {
- this.activeRequest = null;
- }
+ return Promise.resolve();
+ });
}
async refreshData(): Promise {
- if (this.prevOptions) {
- this.options = toJS(this.prevOptions);
- }
- await this.requestData();
+ await this.tryExecuteOperation(DatabaseDataSourceOperation.Request, () => {
+ this.lastAction = this.refreshData.bind(this);
+
+ if (this.prevOptions) {
+ this.options = toJS(this.prevOptions);
+ }
+
+ return this.requestDataAction();
+ });
}
async saveData(): Promise {
- if (this.activeRequest) {
- try {
- await this.activeRequest;
- } finally {
- }
- }
+ await this.tryExecuteOperation(DatabaseDataSourceOperation.Save, () => {
+ this.lastAction = this.saveData.bind(this);
- if (this.activeSave) {
- await this.activeSave;
- return;
- }
- this.lastAction = this.saveData.bind(this);
+ return this.save(this.results).then(data => {
+ this.setResults(data);
+ });
+ });
+ }
+ async canSafelyDispose(): Promise {
try {
- const promise = this.save(this.results);
-
- if (promise instanceof Promise) {
- this.activeSave = promise;
- }
- this.setResults(await promise);
- } finally {
- this.activeSave = null;
+ const result = await this.tryExecuteOperation(DatabaseDataSourceOperation.Request, async () => true);
+ return result || false;
+ } catch {
+ return false;
}
}
@@ -379,14 +361,61 @@ export abstract class DatabaseDataSource;
- abstract save(prevResults: TResult[]): Promise | TResult[];
+ abstract request(prevResults: TResult[]): Promise;
+ abstract save(prevResults: TResult[]): Promise;
abstract loadTotalCount(resultIndex: number): Promise>;
abstract cancelLoadTotalCount(): Promise | null>;
- async requestDataAction(): Promise {
+ private async requestDataAction(): Promise {
this.prevOptions = toJS(this.options);
- return this.request(this.results);
+ return this.request(this.results).then(data => {
+ this.outdated = false;
+
+ if (data !== null) {
+ this.setResults(data);
+ }
+ return data;
+ });
+ }
+
+ private async tryExecuteOperation(type: DatabaseDataSourceOperation, operation: () => Promise): Promise {
+ if (this.activeOperation && type !== DatabaseDataSourceOperation.Request) {
+ await this.activeOperation;
+ }
+
+ const operationTask = this.executeOperation(type, operation);
+ try {
+ this.activeOperationStack.push(operationTask);
+ return await operationTask;
+ } finally {
+ const index = this.activeOperationStack.indexOf(operationTask);
+ if (index !== -1) {
+ this.activeOperationStack.splice(index, 1);
+ }
+ }
+ }
+
+ private executeOperation(type: DatabaseDataSourceOperation, operation: () => Promise | T): ITask {
+ return new Task(async () => await this.onOperation.execute({ stage: 'request', operation: type })).run().then(contexts => {
+ // TODO: maybe it's better to throw an exception instead, so we will not have unexpected undefined results
+ if (ExecutorInterrupter.isInterrupted(contexts)) {
+ return null;
+ }
+
+ return new Task(async () => await this.onOperation.execute({ stage: 'before', operation: type }))
+ .run()
+ .then(contexts => {
+ if (ExecutorInterrupter.isInterrupted(contexts)) {
+ return null;
+ }
+
+ return operation();
+ })
+ .then(async result => {
+ await this.onOperation.execute({ stage: 'after', operation: type });
+ return result;
+ });
+ });
}
}
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts
index dc7d1c5f51..076079940e 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts
@@ -9,10 +9,10 @@ import type { IExecutor } from '@cloudbeaver/core-executor';
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseDataResult } from './IDatabaseDataResult';
-import type { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
+import type { DatabaseDataAccessMode, IDatabaseDataSource, IDatabaseDataSourceOperationEvent, IRequestInfo } from './IDatabaseDataSource';
-export interface IRequestEventData {
- type: 'before' | 'after' | 'on';
+export interface IRequestEventData
+ extends IDatabaseDataSourceOperationEvent {
model: IDatabaseDataModel;
}
@@ -48,11 +48,10 @@ export interface IDatabaseDataModel this;
requestOptionsChange: () => Promise;
- requestDataAction: (action: () => Promise | void) => Promise;
retry: () => Promise;
save: () => Promise;
- refresh: (concurrent?: boolean) => Promise;
- request: (concurrent?: boolean) => Promise;
+ refresh: () => Promise;
+ request: (mutation?: () => void) => Promise;
reload: () => Promise;
requestDataPortion: (offset: number, count: number) => Promise;
cancel: () => Promise | void;
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts
index 1b5b3f8a97..29062697c7 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts
@@ -7,13 +7,26 @@
*/
import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections';
import type { IServiceInjector } from '@cloudbeaver/core-di';
-import type { ITask } from '@cloudbeaver/core-executor';
+import type { IExecutor, ITask } from '@cloudbeaver/core-executor';
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction';
import type { IDatabaseDataActions } from './IDatabaseDataActions';
import type { IDatabaseDataResult } from './IDatabaseDataResult';
+export enum DatabaseDataSourceOperation {
+ /** Abstract operation with data, should not lead to data lost */
+ Task = 'task',
+ /** Saving operation */
+ Save = 'save',
+ /** May lead to data lost */
+ Request = 'request',
+}
+export interface IDatabaseDataSourceOperationEvent {
+ stage: 'request' | 'before' | 'after';
+ operation: DatabaseDataSourceOperation;
+}
+
export interface IRequestInfo {
readonly originalQuery: string;
readonly requestDuration: number;
@@ -47,11 +60,13 @@ export interface IDatabaseDataSource | null;
+ readonly onOperation: IExecutor;
+ isOutdated: () => boolean;
isLoadable: () => boolean;
isReadonly: (resultIndex: number) => boolean;
+ isDataAvailable: (offset: number, count: number) => boolean;
isLoading: () => boolean;
isDisabled: (resultIndex: number) => boolean;
@@ -83,19 +98,25 @@ export interface IDatabaseDataSource this;
setSupportedDataFormats: (dataFormats: ResultDataFormat[]) => this;
setExecutionContext: (context: IConnectionExecutionContext | null) => this;
+
setTotalCount: (resultIndex: number, count: number) => this;
loadTotalCount: (resultIndex: number) => Promise>;
cancelLoadTotalCount: () => Promise | null>;
retry: () => Promise;
- /** Allows to perform an asynchronous action on the data source, this action will wait previous action to finish and save or load requests.
- * The data source will have a loading and disabled state while performing an action */
- runTask: (task: () => Promise) => Promise;
- requestData: () => Promise | void;
- refreshData: () => Promise | void;
- saveData: () => Promise | void;
- cancel: () => Promise | void;
+ /**
+ * Perform operation with data source. This action should not lead to data lost. Can be cancelled when operation is Task.
+ * @param operation Task or Promise
+ * @returns
+ */
+ runOperation: (operation: () => Promise) => Promise;
+ requestDataPortion(offset: number, count: number): Promise;
+ requestData: (mutation?: () => void) => Promise;
+ refreshData: () => Promise;
+ saveData: () => Promise;
+ cancel: () => Promise;
clearError: () => this;
resetData: () => this;
+ canSafelyDispose: () => Promise;
dispose: (keepExecutionContext?: boolean) => Promise;
}
diff --git a/webapp/packages/plugin-data-viewer/src/ResultSetDataSource.ts b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts
similarity index 80%
rename from webapp/packages/plugin-data-viewer/src/ResultSetDataSource.ts
rename to webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts
index d26eb1b87c..ebc6142d96 100644
--- a/webapp/packages/plugin-data-viewer/src/ResultSetDataSource.ts
+++ b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts
@@ -5,12 +5,13 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
import type { IServiceInjector } from '@cloudbeaver/core-di';
import type { ITask } from '@cloudbeaver/core-executor';
import type { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk';
-import { DatabaseDataSource } from './DatabaseDataModel/DatabaseDataSource';
-import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet';
+import { DatabaseDataSource } from '../DatabaseDataModel/DatabaseDataSource';
+import type { IDatabaseResultSet } from '../DatabaseDataModel/IDatabaseResultSet';
export abstract class ResultSetDataSource extends DatabaseDataSource