diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 72ac7ec64d..96fcdd0661 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -34,27 +34,16 @@ } }, { - "label": "Generate dev proprties for CBCE", + "label": "Generate Eclipse PDE CloudBeaver", "type": "shell", - "command": "mvn", "osx": { - "args": [ - "package", - "-q", - "exec:java", - "-Dexec.args= -eclipse.version ${eclipse-version} -config ${workspaceFolder}/../idea-workspace-dbeaver/osgi-app.properties -productFile ${workspaceFolder}/server/product/web-server/CloudbeaverServer.product -projectsFolder ${workspaceFolder}/../ -eclipse ${workspaceFolder}/../dbeaver-workspace/dependencies -output ${workspaceFolder}/../dbeaver-workspace/products/CloudbeaverServer.product" - ] + "command": "./generate_workspace.sh" }, "windows": { - "args": [ - "package", - "-q", - "exec:java", - "-Dexec.args= -eclipse.version ${eclipse-version} -config ${workspaceFolder}/../cloudbeaver/osgi-app.properties" - ] + "command": "./generate_workspace.cmd" }, "options": { - "cwd": "${workspaceFolder}/../idea-rcp-launch-config-generator" + "cwd": "${workspaceFolder}/../cloudbeaver" }, "presentation": { "reveal": "silent", diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java index 9c4984db7e..9c5e42c945 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java @@ -24,12 +24,11 @@ import io.cloudbeaver.utils.CBModelConstants; import io.cloudbeaver.utils.WebAppUtils; import io.cloudbeaver.utils.WebCommonUtils; +import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; -import org.jkiss.dbeaver.model.DBConstants; -import org.jkiss.dbeaver.model.DBPDataSource; -import org.jkiss.dbeaver.model.DBPDataSourceContainer; -import org.jkiss.dbeaver.model.DBPDataSourceFolder; +import org.jkiss.dbeaver.model.*; +import org.jkiss.dbeaver.model.admin.sessions.DBAServerSessionManager; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.connection.DBPAuthModelDescriptor; import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; @@ -40,6 +39,7 @@ import org.jkiss.dbeaver.model.navigator.DBNDataSource; import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.preferences.DBPPropertySource; +import org.jkiss.dbeaver.model.rm.RMConstants; import org.jkiss.dbeaver.model.rm.RMProjectPermission; import org.jkiss.dbeaver.model.runtime.DBRRunnableParametrized; import org.jkiss.dbeaver.runtime.DBWorkbench; @@ -56,6 +56,17 @@ public class WebConnectionInfo { private static final Log log = Log.getLog(WebConnectionInfo.class); public static final String SECURED_VALUE = "********"; + + private static final String FEATURE_HAS_TOOLS = "hasTools"; + private static final String FEATURE_CONNECTED = "connected"; + private static final String FEATURE_VIRTUAL = "virtual"; + private static final String FEATURE_TEMPORARY = "temporary"; + private static final String FEATURE_READ_ONLY = "readOnly"; + private static final String FEATURE_PROVIDED = "provided"; + private static final String FEATURE_MANAGEABLE = "manageable"; + + private static final String TOOL_SESSION_MANAGER = "sessionManager"; + private final WebSession session; private final DBPDataSourceContainer dataSourceContainer; private WebServerError connectError; @@ -243,22 +254,25 @@ public String[] getFeatures() { List features = new ArrayList<>(); if (dataSourceContainer.isConnected()) { - features.add("connected"); + features.add(FEATURE_CONNECTED); + if (!getTools().isEmpty()) { + features.add(FEATURE_HAS_TOOLS); + } } if (dataSourceContainer.isHidden()) { - features.add("virtual"); + features.add(FEATURE_VIRTUAL); } if (dataSourceContainer.isTemporary()) { - features.add("temporary"); + features.add(FEATURE_TEMPORARY); } if (dataSourceContainer.isConnectionReadOnly()) { - features.add("readOnly"); + features.add(FEATURE_READ_ONLY); } if (dataSourceContainer.isProvided()) { - features.add("provided"); + features.add(FEATURE_PROVIDED); } if (dataSourceContainer.isManageable()) { - features.add("manageable"); + features.add(FEATURE_MANAGEABLE); } return features.toArray(new String[0]); @@ -475,4 +489,18 @@ public List getSharedSecrets() throws DBException { .collect(Collectors.toList()); } + @NotNull + @Property + public List getTools() { + if (!session.hasPermission(RMConstants.PERMISSION_DATABASE_DEVELOPER)) { + return List.of(); + } + List tools = new ArrayList<>(); + // checks inside that datasource is not null in container, and it is adaptable to session manager class + if (DBUtils.getAdapter(DBAServerSessionManager.class, dataSourceContainer) != null) { + tools.add(TOOL_SESSION_MANAGER); + } + return tools; + } + } diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 66e49b6a85..622b05fa23 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -401,6 +401,8 @@ type ConnectionInfo { projectId: ID! requiredAuth: String + + tools: [String!]! @since(version: "24.1.3") } type ConnectionFolderInfo { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java index 77395d7c09..3383195c22 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java @@ -21,6 +21,7 @@ import com.google.gson.InstanceCreator; import io.cloudbeaver.model.WebConnectionConfig; import io.cloudbeaver.model.WebNetworkHandlerConfigInput; +import io.cloudbeaver.model.WebPropertyInfo; import io.cloudbeaver.model.session.WebActionParameters; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.registry.WebAuthProviderDescriptor; @@ -28,6 +29,7 @@ import io.cloudbeaver.server.CBAppConfig; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; +import io.cloudbeaver.service.navigator.WebPropertyFilter; import io.cloudbeaver.utils.WebAppUtils; import io.cloudbeaver.utils.WebCommonUtils; import io.cloudbeaver.utils.WebDataSourceUtils; @@ -36,6 +38,7 @@ import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBConstants; import org.jkiss.dbeaver.model.DBPDataSourceContainer; +import org.jkiss.dbeaver.model.DBPObject; import org.jkiss.dbeaver.model.access.DBAAuthCredentials; import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPProject; @@ -46,6 +49,7 @@ import org.jkiss.dbeaver.model.navigator.DBNModel; import org.jkiss.dbeaver.model.navigator.DBNProject; import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; +import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.rm.RMProjectType; import org.jkiss.dbeaver.registry.DataSourceDescriptor; import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings; @@ -54,6 +58,7 @@ import org.jkiss.dbeaver.registry.driver.DriverDescriptor; import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor; import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry; +import org.jkiss.dbeaver.runtime.properties.PropertyCollector; import org.jkiss.utils.CommonUtils; import java.io.InputStream; @@ -365,4 +370,35 @@ public static Set getApplicableDriversIds() { .map(DBPDriver::getId) .collect(Collectors.toSet()); } + + /** + * Returns filtered properties collected from object. + */ + @NotNull + public static WebPropertyInfo[] getObjectFilteredProperties( + @NotNull WebSession session, + @NotNull DBPObject object, + @Nullable WebPropertyFilter filter + ) { + PropertyCollector propertyCollector = new PropertyCollector(object, true); + propertyCollector.setLocale(session.getLocale()); + propertyCollector.collectProperties(); + List webProps = new ArrayList<>(); + for (DBPPropertyDescriptor prop : propertyCollector.getProperties()) { + if (filter != null && !CommonUtils.isEmpty(filter.getIds()) && !filter.getIds().contains(CommonUtils.toString(prop.getId()))) { + continue; + } + WebPropertyInfo webProperty = new WebPropertyInfo(session, prop, propertyCollector); + if (filter != null) { + if (!CommonUtils.isEmpty(filter.getFeatures()) && !webProperty.hasAnyFeature(filter.getFeatures())) { + continue; + } + if (!CommonUtils.isEmpty(filter.getCategories()) && !filter.getCategories().contains(webProperty.getCategory())) { + continue; + } + } + webProps.add(webProperty); + } + return webProps.toArray(new WebPropertyInfo[0]); + } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java index 9c92d1f9fe..11d386ba7f 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java @@ -16,19 +16,17 @@ */ package io.cloudbeaver.service.navigator; +import io.cloudbeaver.WebServiceUtils; import io.cloudbeaver.model.WebPropertyInfo; import io.cloudbeaver.model.session.WebSession; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.meta.Property; -import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.struct.*; import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog; import org.jkiss.dbeaver.model.struct.rdb.DBSSchema; import org.jkiss.dbeaver.model.struct.rdb.DBSTable; -import org.jkiss.dbeaver.runtime.properties.PropertyCollector; -import org.jkiss.utils.CommonUtils; import java.util.ArrayList; import java.util.List; @@ -90,26 +88,7 @@ public WebPropertyInfo[] getProperties() { @Property public WebPropertyInfo[] filterProperties(@Nullable WebPropertyFilter filter) { - PropertyCollector propertyCollector = new PropertyCollector(object, true); - propertyCollector.setLocale(session.getLocale()); - propertyCollector.collectProperties(); - List webProps = new ArrayList<>(); - for (DBPPropertyDescriptor prop : propertyCollector.getProperties()) { - if (filter != null && !CommonUtils.isEmpty(filter.getIds()) && !filter.getIds().contains(CommonUtils.toString(prop.getId()))) { - continue; - } - WebPropertyInfo webProperty = new WebPropertyInfo(session, prop, propertyCollector); - if (filter != null) { - if (!CommonUtils.isEmpty(filter.getFeatures()) && !webProperty.hasAnyFeature(filter.getFeatures())) { - continue; - } - if (!CommonUtils.isEmpty(filter.getCategories()) && !filter.getCategories().contains(webProperty.getCategory())) { - continue; - } - } - webProps.add(webProperty); - } - return webProps.toArray(new WebPropertyInfo[0]); + return WebServiceUtils.getObjectFilteredProperties(session, object, filter); } /////////////////////////////////// diff --git a/webapp/packages/core-authentication/src/AuthSettingsService.test.ts b/webapp/packages/core-authentication/src/AuthSettingsService.test.ts index 31c74d25c6..a5e7e2fece 100644 --- a/webapp/packages/core-authentication/src/AuthSettingsService.test.ts +++ b/webapp/packages/core-authentication/src/AuthSettingsService.test.ts @@ -42,8 +42,8 @@ const equalConfig = { }; test('Read settings', async () => { - const settings = app.injector.getServiceByClass(AuthSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(AuthSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(equalConfig))); diff --git a/webapp/packages/core-blocks/src/Table/TableItemGroup.tsx b/webapp/packages/core-blocks/src/Table/TableItemGroup.tsx new file mode 100644 index 0000000000..69dbe636a1 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItemGroup.tsx @@ -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 { observer } from 'mobx-react-lite'; +import { useState } from 'react'; + +import { TableItemGroupContext } from './TableItemGroupContext'; + +export interface TableItemGroupProps extends React.PropsWithChildren { + expanded?: boolean; +} + +export const TableItemGroup = observer(function TableItemGroup({ expanded = true, children }) { + const [internalExpanded, setExpanded] = useState(expanded); + + return {children}; +}); diff --git a/webapp/packages/core-blocks/src/Table/TableItemGroupContent.tsx b/webapp/packages/core-blocks/src/Table/TableItemGroupContent.tsx new file mode 100644 index 0000000000..a545ae4ef7 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItemGroupContent.tsx @@ -0,0 +1,29 @@ +/* + * 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 { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; + +import { TableItemGroupContext } from './TableItemGroupContext'; + +export interface TableItemGroupContentProps { + children?: React.ReactNode | (() => React.ReactNode); +} + +export const TableItemGroupContent = observer(function TableItemGroupContent({ children }) { + const context = useContext(TableItemGroupContext); + + if (!context) { + throw new Error('TableItemGroupContent can be used only inside TableItemGroup'); + } + + if (!context.expanded) { + return null; + } + + return <>{typeof children === 'function' ? children() : children}; +}); diff --git a/webapp/packages/core-blocks/src/Table/TableItemGroupContext.ts b/webapp/packages/core-blocks/src/Table/TableItemGroupContext.ts new file mode 100644 index 0000000000..d2953e2388 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItemGroupContext.ts @@ -0,0 +1,14 @@ +/* + * 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 { createContext } from 'react'; + +export interface ITableItemGroupContext { + expanded: boolean; + setExpanded: (expanded: boolean) => void; +} +export const TableItemGroupContext = createContext(undefined); diff --git a/webapp/packages/core-blocks/src/Table/TableItemGroupExpand.module.css b/webapp/packages/core-blocks/src/Table/TableItemGroupExpand.module.css new file mode 100644 index 0000000000..e9e9a4b0bc --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItemGroupExpand.module.css @@ -0,0 +1,28 @@ +/* + * 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. + */ + +.box { + display: inline-block; + width: 16px; + height: 16px; + align-self: center; +} + +.icon { + composes: theme-text-on-surface from global; + width: 100%; + height: 100%; + cursor: pointer; + opacity: 0.5; + transform: rotate(-90deg); + transition: transform 0.15s ease-in-out; + + &.expanded { + transform: rotate(0deg); + } +} diff --git a/webapp/packages/core-blocks/src/Table/TableItemGroupExpand.tsx b/webapp/packages/core-blocks/src/Table/TableItemGroupExpand.tsx new file mode 100644 index 0000000000..fc659fdd09 --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItemGroupExpand.tsx @@ -0,0 +1,38 @@ +/* + * 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 { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; + +import { Icon } from '../Icon'; +import { s } from '../s'; +import { useS } from '../useS'; +import { TableItemGroupContext } from './TableItemGroupContext'; +import classes from './TableItemGroupExpand.module.css'; + +export interface TableItemGroupExpandProps { + className?: string; +} + +export const TableItemGroupExpand = observer(function TableItemGroupExpand({ className }) { + const context = useContext(TableItemGroupContext); + const styles = useS(classes); + + if (!context) { + throw new Error('TableItemGroupExpand can be used only inside TableItemGroup'); + } + + function handleClick() { + context!.setExpanded(!context!.expanded); + } + + return ( +
+ +
+ ); +}); diff --git a/webapp/packages/core-blocks/src/Table/TableItemGroupExpandSpace.tsx b/webapp/packages/core-blocks/src/Table/TableItemGroupExpandSpace.tsx new file mode 100644 index 0000000000..084f9d58bb --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/TableItemGroupExpandSpace.tsx @@ -0,0 +1,22 @@ +/* + * 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 { observer } from 'mobx-react-lite'; + +import { s } from '../s'; +import { useS } from '../useS'; +import classes from './TableItemGroupExpand.module.css'; + +export interface TableItemGroupExpandSpaceProps { + className?: string; +} + +export const TableItemGroupExpandSpaceSpace = observer(function TableItemGroupExpandSpaceSpace({ className }) { + const styles = useS(classes); + + return
; +}); diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index ed29b86fba..8ccb413fa2 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -113,6 +113,11 @@ export * from './Table/useTable'; export * from './Table/TableState'; export * from './Table/TableSelect'; export * from './Table/getSelectedItems'; +export * from './Table/TableItemGroup'; +export * from './Table/TableItemGroupContext'; +export * from './Table/TableItemGroupExpand'; +export * from './Table/TableItemGroupContent'; +export * from './Table/TableItemGroupExpandSpace'; export * from './Expand/Expandable'; diff --git a/webapp/packages/core-bootstrap/src/bootstrap.ts b/webapp/packages/core-bootstrap/src/bootstrap.ts index 5eaebd5029..5797e5b5a9 100644 --- a/webapp/packages/core-bootstrap/src/bootstrap.ts +++ b/webapp/packages/core-bootstrap/src/bootstrap.ts @@ -26,7 +26,7 @@ export async function bootstrap(plugins: PluginManifest[]): Promise { } const { renderLayout } = await import('./renderLayout'); - const render = renderLayout(app.getServiceInjector()); + const render = renderLayout(app.getServiceProvider()); const unmountExecutor = new SyncExecutor(); unmountExecutor.addHandler(() => render.unmount()); diff --git a/webapp/packages/core-bootstrap/src/renderLayout.tsx b/webapp/packages/core-bootstrap/src/renderLayout.tsx index b3999741ac..db85c817bd 100644 --- a/webapp/packages/core-bootstrap/src/renderLayout.tsx +++ b/webapp/packages/core-bootstrap/src/renderLayout.tsx @@ -10,7 +10,7 @@ import { createRoot, Root } from 'react-dom/client'; import { BodyLazy } from '@cloudbeaver/core-app'; import { DisplayError, ErrorBoundary, Loader, s } from '@cloudbeaver/core-blocks'; -import { AppContext, HideAppLoadingScreen, IServiceInjector } from '@cloudbeaver/core-di'; +import { HideAppLoadingScreen, IServiceProvider, ServiceProviderContext } from '@cloudbeaver/core-di'; import styles from './renderLayout.module.css'; @@ -21,7 +21,7 @@ interface IRender { unmount(): void; } -export function renderLayout(serviceInjector: IServiceInjector): IRender { +export function renderLayout(serviceProvider: IServiceProvider): IRender { let root: Root | undefined; return { @@ -48,14 +48,14 @@ export function renderLayout(serviceInjector: IServiceInjector): IRender { renderApp() { this.initRoot().render( } simple> - + } root> }> - + , ); }, @@ -65,10 +65,10 @@ export function renderLayout(serviceInjector: IServiceInjector): IRender { } this.initRoot().render( } simple> - + - + , ); }, diff --git a/webapp/packages/core-browser-settings/src/BrowserSettingsService.test.ts b/webapp/packages/core-browser-settings/src/BrowserSettingsService.test.ts index 93ee370f1d..514c4e51af 100644 --- a/webapp/packages/core-browser-settings/src/BrowserSettingsService.test.ts +++ b/webapp/packages/core-browser-settings/src/BrowserSettingsService.test.ts @@ -52,8 +52,8 @@ const equalConfigB = { }; test('New settings override deprecated settings', async () => { - const settings = app.injector.getServiceByClass(BrowserSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(BrowserSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(equalConfigA))); @@ -64,8 +64,8 @@ test('New settings override deprecated settings', async () => { }); test('New settings fall back to deprecated settings', async () => { - const settings = app.injector.getServiceByClass(BrowserSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(BrowserSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(equalConfigB))); diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index d37c3c6e62..aa64be5228 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -641,9 +641,13 @@ export function compareNewConnectionsInfo(a: DatabaseConnection, b: DatabaseConn return 0; } +export function createConnectionParam(connection: Pick): IConnectionInfoParams; export function createConnectionParam(connection: Connection): IConnectionInfoParams; export function createConnectionParam(projectId: string, connectionId: string): IConnectionInfoParams; -export function createConnectionParam(projectIdOrConnection: string | Connection, connectionId?: string): IConnectionInfoParams { +export function createConnectionParam( + projectIdOrConnection: string | Connection | Pick, + connectionId?: string, +): IConnectionInfoParams { if (typeof projectIdOrConnection === 'object') { connectionId = projectIdOrConnection.id; projectIdOrConnection = projectIdOrConnection.projectId; diff --git a/webapp/packages/core-connections/src/ConnectionToolsResource.ts b/webapp/packages/core-connections/src/ConnectionToolsResource.ts new file mode 100644 index 0000000000..05cf7b25a1 --- /dev/null +++ b/webapp/packages/core-connections/src/ConnectionToolsResource.ts @@ -0,0 +1,71 @@ +/* + * 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 { toJS } from 'mobx'; + +import { injectable } from '@cloudbeaver/core-di'; +import { CachedMapResource, isResourceAlias, type ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-resource'; +import { DatabaseConnectionToolsFragment, GraphQLService } from '@cloudbeaver/core-sdk'; +import { schemaValidationError } from '@cloudbeaver/core-utils'; + +import { CONNECTION_INFO_PARAM_SCHEMA, type IConnectionInfoParams } from './CONNECTION_INFO_PARAM_SCHEMA'; +import { ConnectionInfoResource, createConnectionParam, isConnectionInfoParamEqual } from './ConnectionInfoResource'; + +export type ConnectionTools = DatabaseConnectionToolsFragment; + +@injectable() +export class ConnectionToolsResource extends CachedMapResource { + constructor( + private readonly graphQLService: GraphQLService, + connectionInfoResource: ConnectionInfoResource, + ) { + super(); + + this.sync(connectionInfoResource); + connectionInfoResource.onItemDelete.addHandler(this.delete.bind(this)); + } + + isKeyEqual(param: IConnectionInfoParams, second: IConnectionInfoParams): boolean { + return isConnectionInfoParamEqual(param, second); + } + + protected async loader(originalKey: ResourceKey): Promise> { + const connectionsList: ConnectionTools[] = []; + + if (isResourceAlias(originalKey)) { + throw new Error('Aliases are not supported'); + } + + await ResourceKeyUtils.forEachAsync(originalKey, async key => { + const projectId = key.projectId; + const connectionId = key.connectionId; + + const { connections } = await this.graphQLService.sdk.getConnectionsTools({ + projectId, + connectionId, + }); + + if (connectionId && !connections.some(connection => connection.id === connectionId)) { + throw new Error(`Connection is not found (${connectionId})`); + } + + connectionsList.push(...connections); + }); + + const key = resourceKeyList(connectionsList.map(createConnectionParam)); + this.set(key, connectionsList); + + return this.data; + } + protected validateKey(key: IConnectionInfoParams): boolean { + const parse = CONNECTION_INFO_PARAM_SCHEMA.safeParse(toJS(key)); + if (!parse.success) { + this.logger.warn(`Invalid resource key ${(schemaValidationError(parse.error).toString(), { prefix: null })}`); + } + return parse.success; + } +} diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 202ebbf4a9..f00de1eb2d 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -1,3 +1,10 @@ +/* + * 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 * from './ConnectionExecutionContext/ConnectionExecutionContext'; export * from './ConnectionExecutionContext/ConnectionExecutionContextResource'; export * from './ConnectionExecutionContext/ConnectionExecutionContextService'; @@ -28,6 +35,7 @@ export * from './ConnectionInfoResource'; export * from './CONNECTIONS_SETTINGS_GROUP'; export * from './EConnectionFeature'; export * from './ConnectionsSettingsService'; +export * from './ConnectionToolsResource'; export * from './ContainerResource'; export * from './ConnectionsLocaleService'; export * from './createConnectionFolderParam'; diff --git a/webapp/packages/core-connections/src/manifest.ts b/webapp/packages/core-connections/src/manifest.ts index e827636cf0..a0ea16a88f 100644 --- a/webapp/packages/core-connections/src/manifest.ts +++ b/webapp/packages/core-connections/src/manifest.ts @@ -18,6 +18,7 @@ export const manifest: PluginManifest = { () => import('./ConnectionExecutionContext/ConnectionExecutionContextService').then(m => m.ConnectionExecutionContextService), () => import('./ConnectionsManagerService').then(m => m.ConnectionsManagerService), () => import('./ConnectionInfoResource').then(m => m.ConnectionInfoResource), + () => import('./ConnectionToolsResource').then(m => m.ConnectionToolsResource), () => import('./ContainerResource').then(m => m.ContainerResource), () => import('./ConnectionsLocaleService').then(m => m.ConnectionsLocaleService), () => import('./DatabaseAuthModelsResource').then(m => m.DatabaseAuthModelsResource), diff --git a/webapp/packages/core-data-context/src/DataContext/DATA_CONTEXT_DI_PROVIDER.ts b/webapp/packages/core-data-context/src/DataContext/DATA_CONTEXT_DI_PROVIDER.ts index 020c53bac7..b7c898e515 100644 --- a/webapp/packages/core-data-context/src/DataContext/DATA_CONTEXT_DI_PROVIDER.ts +++ b/webapp/packages/core-data-context/src/DataContext/DATA_CONTEXT_DI_PROVIDER.ts @@ -5,8 +5,8 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import type { IServiceInjector } from '@cloudbeaver/core-di'; +import type { IServiceProvider } from '@cloudbeaver/core-di'; import { createDataContext } from './createDataContext'; -export const DATA_CONTEXT_DI_PROVIDER = createDataContext('DI Provider'); +export const DATA_CONTEXT_DI_PROVIDER = createDataContext('DI Provider'); diff --git a/webapp/packages/core-data-context/src/DataContext/dataContextAddDIProvider.ts b/webapp/packages/core-data-context/src/DataContext/dataContextAddDIProvider.ts index 70430c86e2..22e392f2e8 100644 --- a/webapp/packages/core-data-context/src/DataContext/dataContextAddDIProvider.ts +++ b/webapp/packages/core-data-context/src/DataContext/dataContextAddDIProvider.ts @@ -5,12 +5,12 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import type { App } from '@cloudbeaver/core-di'; +import { IServiceProvider } from '@cloudbeaver/core-di'; import { DATA_CONTEXT_DI_PROVIDER } from './DATA_CONTEXT_DI_PROVIDER'; import type { IDataContext } from './IDataContext'; -export function dataContextAddDIProvider(context: IDataContext, app: App, id: string): IDataContext { - context.set(DATA_CONTEXT_DI_PROVIDER, app.getServiceInjector(), id); +export function dataContextAddDIProvider(context: IDataContext, serviceProvider: IServiceProvider, id: string): IDataContext { + context.set(DATA_CONTEXT_DI_PROVIDER, serviceProvider, id); return context; } diff --git a/webapp/packages/core-di/src/App.ts b/webapp/packages/core-di/src/App.ts index b6a8e225c9..4d3976c026 100644 --- a/webapp/packages/core-di/src/App.ts +++ b/webapp/packages/core-di/src/App.ts @@ -14,6 +14,7 @@ import { Dependency } from './Dependency'; import type { DIContainer } from './DIContainer'; import type { IServiceCollection, IServiceConstructor, IServiceInjector } from './IApp'; import { IDiWrapper, inversifyWrapper } from './inversifyWrapper'; +import { IServiceProvider } from './IServiceProvider'; import type { PluginManifest } from './PluginManifest'; export interface IStartData { @@ -83,18 +84,23 @@ export class App { this.plugins.push(manifest); } - getServiceInjector(): IServiceInjector { - return this.diWrapper.injector; + getServiceProvider(): IServiceProvider { + return this.diWrapper.injector.resolveServiceByClass(IServiceProvider); } getServiceCollection(): IServiceCollection { return this.diWrapper.collection; } + getServiceInjector(): IServiceInjector { + return this.diWrapper.injector; + } + // first phase register all dependencies private async registerServices(preload?: boolean): Promise { if (!this.isAppServiceBound) { this.getServiceCollection().addServiceByClass(App, this); + this.getServiceCollection().addServiceByClass(IServiceProvider, new IServiceProvider(this.diWrapper.injector)); this.isAppServiceBound = true; } diff --git a/webapp/packages/core-di/src/AppContext.tsx b/webapp/packages/core-di/src/AppContext.tsx deleted file mode 100644 index 44f6168b4a..0000000000 --- a/webapp/packages/core-di/src/AppContext.tsx +++ /dev/null @@ -1,24 +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 { createContext } from 'react'; - -import type { IServiceInjector } from './IApp'; - -export const appContext = createContext(undefined as any); - -export interface AppContextProps extends React.PropsWithChildren { - app: IServiceInjector; -} - -export const AppContext: React.FC = function AppContext({ app, children }) { - return ( - // // problems with TabState when empty -> displayed state switch - {children} - // - ); -}; diff --git a/webapp/packages/core-di/src/DIContainer.ts b/webapp/packages/core-di/src/DIContainer.ts index 9545443884..909947148d 100644 --- a/webapp/packages/core-di/src/DIContainer.ts +++ b/webapp/packages/core-di/src/DIContainer.ts @@ -8,8 +8,6 @@ import { Container, interfaces } from 'inversify'; import type { IServiceCollection, IServiceConstructor, IServiceInjector } from './IApp'; -import type { InjectionToken } from './InjectionToken'; -import { isConstructor } from './isConstructor'; function logger(planAndResolve: interfaces.Next): interfaces.Next { return (args: interfaces.NextArgs) => { @@ -100,10 +98,6 @@ export class DIContainer implements IServiceInjector, IServiceCollection { return this.container.get(ctor); } - getServiceByToken(token: InjectionToken): T { - return this.container.get(token); - } - resolveServiceByClass(ctor: IServiceConstructor): T { return this.container.resolve(ctor); } @@ -115,12 +109,4 @@ export class DIContainer implements IServiceInjector, IServiceCollection { this.container.bind(Ctor).toSelf(); } } - - addServiceByToken>(token: InjectionToken, value: T | IServiceConstructor): void { - if (isConstructor(value)) { - this.container.bind(token).to(value as IServiceConstructor); - } else { - this.container.bind(token).toConstantValue(value); - } - } } diff --git a/webapp/packages/core-di/src/DIService.ts b/webapp/packages/core-di/src/DIService.ts index 7c7fdaf2fb..1b477e1e02 100644 --- a/webapp/packages/core-di/src/DIService.ts +++ b/webapp/packages/core-di/src/DIService.ts @@ -7,23 +7,19 @@ */ import type { IExecutorHandlersCollection, ISyncContextLoader } from '@cloudbeaver/core-executor'; -import { App } from './App'; import { dependencyInjectorContext } from './dependencyInjectorContext'; -import type { IServiceConstructor, IServiceInjector } from './IApp'; +import type { IServiceConstructor } from './IApp'; import { injectable } from './injectable'; +import { IServiceProvider } from './IServiceProvider'; @injectable() export class DIService { - get serviceInjector(): IServiceInjector { - return this.app.getServiceInjector(); - } - - constructor(private readonly app: App) {} + constructor(private readonly serviceProvider: IServiceProvider) {} addDIContext(context: IExecutorHandlersCollection): void { context.addContextCreator(dependencyInjectorContext, this.dependencyInjectorContext); } private readonly dependencyInjectorContext: ISyncContextLoader<(ctor: IServiceConstructor) => T> = () => - this.serviceInjector.getServiceByClass.bind(this); + this.serviceProvider.getService.bind(this.serviceProvider); } diff --git a/webapp/packages/core-di/src/IApp.ts b/webapp/packages/core-di/src/IApp.ts index 8546a64715..f0e064168c 100644 --- a/webapp/packages/core-di/src/IApp.ts +++ b/webapp/packages/core-di/src/IApp.ts @@ -5,11 +5,9 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import type { ValueToken } from './InjectionToken'; import type { ITypedConstructor } from './ITypedConstructor'; export interface IServiceCollection { - addServiceByToken: >(token: any, value: T) => void; addServiceByClass: (ctor: IServiceConstructor, value?: any) => void; unbindAll: () => void; } @@ -29,6 +27,5 @@ export type IServiceConstructor = ITypedConstructor; export interface IServiceInjector { hasServiceByClass: (ctor: IServiceConstructor) => boolean; getServiceByClass: (ctor: IServiceConstructor) => T; - getServiceByToken: (token: ValueToken) => T; resolveServiceByClass: (ctor: IServiceConstructor) => T; } diff --git a/webapp/packages/core-di/src/IServiceProvider.ts b/webapp/packages/core-di/src/IServiceProvider.ts new file mode 100644 index 0000000000..0882bcb732 --- /dev/null +++ b/webapp/packages/core-di/src/IServiceProvider.ts @@ -0,0 +1,16 @@ +/* + * 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 type { IServiceConstructor, IServiceInjector } from './IApp'; + +export class IServiceProvider { + constructor(private injector: IServiceInjector) {} + + getService(service: IServiceConstructor): T { + return this.injector.getServiceByClass(service); + } +} diff --git a/webapp/packages/core-di/src/InjectionToken.ts b/webapp/packages/core-di/src/InjectionToken.ts deleted file mode 100644 index 3effec082d..0000000000 --- a/webapp/packages/core-di/src/InjectionToken.ts +++ /dev/null @@ -1,44 +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 type { ITypedConstructor } from './ITypedConstructor'; - -/** - * Use this token to inject value as a service - */ -export type ValueToken = () => T; - -/** - * there are two types of tokens in the application: tokens for classes and tokens for values (class instances) - */ -export type InjectionToken = ITypedConstructor | ValueToken; - -export function createValueToken>(obj: string | ITypedConstructor | T): ValueToken { - // just fake function to keep type T - const token = () => null as unknown as T; - const name = getName(obj); - Object.defineProperty(token, 'name', { value: name, writable: false }); - - return token; -} - -function getName(obj: any): string { - if (!obj) { - return 'unknown'; - } - if (typeof obj === 'string') { - return obj; - } - if (typeof obj === 'object') { - if (obj.constructor) { - return obj.constructor.name || 'unknown'; - } - } else if (typeof obj === 'function') { - return obj.name || 'unknown'; - } - return 'unexpected'; -} diff --git a/webapp/packages/core-di/src/ServiceProviderContext.tsx b/webapp/packages/core-di/src/ServiceProviderContext.tsx new file mode 100644 index 0000000000..5fd8ea681c --- /dev/null +++ b/webapp/packages/core-di/src/ServiceProviderContext.tsx @@ -0,0 +1,24 @@ +/* + * 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 { createContext } from 'react'; + +import { IServiceProvider } from './IServiceProvider'; + +export const serviceProviderContext = createContext(undefined as any); + +export interface ServiceProviderContextProps extends React.PropsWithChildren { + serviceProvider: IServiceProvider; +} + +export const ServiceProviderContext: React.FC = function ServiceProviderContext({ serviceProvider, children }) { + return ( + // // problems with TabState when empty -> displayed state switch + {children} + // + ); +}; diff --git a/webapp/packages/core-di/src/AppContextLazy.ts b/webapp/packages/core-di/src/ServiceProviderContextLazy.ts similarity index 64% rename from webapp/packages/core-di/src/AppContextLazy.ts rename to webapp/packages/core-di/src/ServiceProviderContextLazy.ts index 786aef2e94..8e6091aa37 100644 --- a/webapp/packages/core-di/src/AppContextLazy.ts +++ b/webapp/packages/core-di/src/ServiceProviderContextLazy.ts @@ -7,4 +7,4 @@ */ import { lazy } from 'react'; -export const AppContext = lazy(() => import('./AppContext').then(m => ({ default: m.AppContext }))); +export const ServiceProviderContext = lazy(() => import('./ServiceProviderContext').then(m => ({ default: m.ServiceProviderContext }))); diff --git a/webapp/packages/core-di/src/__tests__/app-init/app-init.test.ts b/webapp/packages/core-di/src/__tests__/app-init/app-init.test.ts index f05112a4ca..a877cc393b 100644 --- a/webapp/packages/core-di/src/__tests__/app-init/app-init.test.ts +++ b/webapp/packages/core-di/src/__tests__/app-init/app-init.test.ts @@ -12,12 +12,12 @@ import { TestService } from './TestService'; test('App Initialization', async () => { const app = new App([manifest]); - const injector = app.getServiceInjector(); + const serviceProvider = app.getServiceProvider(); await (app as any).registerServices(); - const service = injector.getServiceByClass(TestService); - const bootstrap = injector.getServiceByClass(TestBootstrap); + const service = serviceProvider.getService(TestService); + const bootstrap = serviceProvider.getService(TestBootstrap); expect(service).toBeInstanceOf(TestService); expect(service.sum(1, 2)).toBe(3); diff --git a/webapp/packages/core-di/src/entities/ServiceInjectorToken.ts b/webapp/packages/core-di/src/entities/ServiceInjectorToken.ts deleted file mode 100644 index 17941e0c88..0000000000 --- a/webapp/packages/core-di/src/entities/ServiceInjectorToken.ts +++ /dev/null @@ -1,11 +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 type { IServiceInjector } from '../IApp'; -import { createValueToken } from '../InjectionToken'; - -export const ServiceInjectorToken = createValueToken('IServiceInjector'); diff --git a/webapp/packages/core-di/src/index.ts b/webapp/packages/core-di/src/index.ts index a2afd6e788..ecd20ca38e 100644 --- a/webapp/packages/core-di/src/index.ts +++ b/webapp/packages/core-di/src/index.ts @@ -7,7 +7,7 @@ */ export * from './IApp'; export * from './App'; -export * from './AppContextLazy'; +export * from './ServiceProviderContextLazy'; export * from './Bootstrap'; export * from './Disposable'; export * from './Dependency'; @@ -17,10 +17,9 @@ export * from './injectable'; export * from './PluginManifest'; export * from './useService'; export * from './useController'; -export * from './InjectionToken'; -export * from './entities/ServiceInjectorToken'; export * from './ITypedConstructor'; export * from './isConstructor'; +export * from './IServiceProvider'; export * from './AppLoadingScreen/HideAppLoadingScreenLazy'; export * from './AppLoadingScreen/displayUpdateStatus'; export { manifest as coreDIManifest } from './manifest'; diff --git a/webapp/packages/core-di/src/injectable.ts b/webapp/packages/core-di/src/injectable.ts index 718e14b468..c667c41eb7 100644 --- a/webapp/packages/core-di/src/injectable.ts +++ b/webapp/packages/core-di/src/injectable.ts @@ -5,15 +5,8 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { inject as inversifyInject, injectable as inversifyInjectable } from 'inversify'; -import type { DecoratorTarget } from 'inversify/lib/annotation/decorator_utils'; - -import type { ValueToken } from './InjectionToken'; +import { injectable as inversifyInjectable } from 'inversify'; export function injectable(): (target: any) => any { return inversifyInjectable(); } - -export function inject(token: ValueToken): (target: DecoratorTarget, targetKey: string, index?: number) => void { - return inversifyInject(token); -} diff --git a/webapp/packages/core-di/src/inversifyWrapper.ts b/webapp/packages/core-di/src/inversifyWrapper.ts index cb8f34a18e..a79184fd92 100644 --- a/webapp/packages/core-di/src/inversifyWrapper.ts +++ b/webapp/packages/core-di/src/inversifyWrapper.ts @@ -7,7 +7,6 @@ */ import { DIContainer } from './DIContainer'; import type { IServiceCollection, IServiceConstructor, IServiceInjector } from './IApp'; -import type { ValueToken } from './InjectionToken'; export interface IDiWrapper { injector: IServiceInjector; @@ -25,9 +24,6 @@ export const inversifyWrapper: IDiWrapper = { getServiceByClass(ctor: IServiceConstructor): T { return mainContainer.getServiceByClass(ctor); }, - getServiceByToken(token: any): T { - return mainContainer.getServiceByToken(token); - }, resolveServiceByClass(ctor: IServiceConstructor): T { return mainContainer.resolveServiceByClass(ctor); }, @@ -37,10 +33,6 @@ export const inversifyWrapper: IDiWrapper = { addServiceByClass(Ctor: IServiceConstructor, value?: any): void { mainContainer.addServiceByClass(Ctor, value); }, - - addServiceByToken>(token: ValueToken | IServiceConstructor, value: T): void { - mainContainer.addServiceByToken(token, value); - }, unbindAll() { mainContainer.unbindAll(); }, diff --git a/webapp/packages/core-di/src/useController.ts b/webapp/packages/core-di/src/useController.ts index fe0012ed86..76be113d79 100644 --- a/webapp/packages/core-di/src/useController.ts +++ b/webapp/packages/core-di/src/useController.ts @@ -5,10 +5,11 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { useContext, useEffect, useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; -import { appContext } from './AppContext'; +import { App } from './App'; import type { ExtractInitArgs, IDestructibleController, IInitializableController, IServiceConstructor } from './IApp'; +import { useService } from './useService'; /** * @deprecated use hooks instead @@ -22,7 +23,7 @@ export function useController(ctor: IServiceConstructor): T; * @deprecated use hooks instead */ export function useController(ctor: IServiceConstructor, ...args: any[]): T { - const app = useContext(appContext); + const appService = useService(App); const controllerRef = useRef(); useMemo(() => { @@ -30,7 +31,7 @@ export function useController(ctor: IServiceConstructor, ...args: any[]): controllerRef.current.destruct(); } - const controller = app.resolveServiceByClass(ctor); + const controller = appService.getServiceInjector().resolveServiceByClass(ctor); if (isInitializableController(controller)) { controller.init(...args); diff --git a/webapp/packages/core-di/src/useService.ts b/webapp/packages/core-di/src/useService.ts index 8eb9ea22b3..d1a35347d5 100644 --- a/webapp/packages/core-di/src/useService.ts +++ b/webapp/packages/core-di/src/useService.ts @@ -7,23 +7,21 @@ */ import { useContext } from 'react'; -import { appContext } from './AppContext'; import type { IServiceConstructor } from './IApp'; -import type { ValueToken } from './InjectionToken'; +import { serviceProviderContext } from './ServiceProviderContext'; export function useService(ctor: IServiceConstructor): T; export function useService(ctor: IServiceConstructor, optional: true): T | undefined; export function useService(ctor: IServiceConstructor, optional?: boolean): T | undefined { - const app = useContext(appContext); + const serviceProvider = useContext(serviceProviderContext); - if (optional && !app.hasServiceByClass(ctor)) { - return undefined; + if (optional) { + try { + return serviceProvider.getService(ctor); + } catch { + return undefined; + } } - return app.getServiceByClass(ctor); -} - -export function useServiceByToken(token: ValueToken): T { - const app = useContext(appContext); - return app.getServiceByToken(token); + return serviceProvider.getService(ctor); } diff --git a/webapp/packages/core-events/src/EventsSettingsService.test.ts b/webapp/packages/core-events/src/EventsSettingsService.test.ts index 64e00bdaf6..1e11fbb042 100644 --- a/webapp/packages/core-events/src/EventsSettingsService.test.ts +++ b/webapp/packages/core-events/src/EventsSettingsService.test.ts @@ -49,8 +49,8 @@ const overrideConfig = { }; test('New settings override deprecated settings', async () => { - const settings = app.injector.getServiceByClass(EventsSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(EventsSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(overrideConfig))); @@ -62,8 +62,8 @@ test('New settings override deprecated settings', async () => { }); test('Deprecated settings accessible with new settings', async () => { - const settings = app.injector.getServiceByClass(EventsSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(EventsSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(oldConfig))); diff --git a/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.test.ts b/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.test.ts index ec0d22004f..4b6ffd9fc7 100644 --- a/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.test.ts +++ b/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.test.ts @@ -68,8 +68,8 @@ const newSettings = { }; test('New settings override deprecated', async () => { - const settings = app.injector.getServiceByClass(NavTreeSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(NavTreeSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(newSettings))); @@ -82,8 +82,8 @@ test('New settings override deprecated', async () => { }); test('Deprecated settings are used if new settings are not defined', async () => { - const settings = app.injector.getServiceByClass(NavTreeSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(NavTreeSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings))); diff --git a/webapp/packages/core-sdk/src/queries/connections/getConnectionsTools.gql b/webapp/packages/core-sdk/src/queries/connections/getConnectionsTools.gql new file mode 100644 index 0000000000..85211c3729 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/connections/getConnectionsTools.gql @@ -0,0 +1,5 @@ +query getConnectionsTools($projectId: ID, $connectionId: ID!) { + connections: userConnections(projectId: $projectId, id: $connectionId) { + ...DatabaseConnectionTools + } +} diff --git a/webapp/packages/core-sdk/src/queries/fragments/DatabaseConnectionTools.gql b/webapp/packages/core-sdk/src/queries/fragments/DatabaseConnectionTools.gql new file mode 100644 index 0000000000..71b1704290 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/DatabaseConnectionTools.gql @@ -0,0 +1,5 @@ +fragment DatabaseConnectionTools on ConnectionInfo { + id + projectId + tools +} diff --git a/webapp/packages/core-settings-localization/src/SettingsLocalizationService.test.ts b/webapp/packages/core-settings-localization/src/SettingsLocalizationService.test.ts index afda73df98..d4fcbd2ce6 100644 --- a/webapp/packages/core-settings-localization/src/SettingsLocalizationService.test.ts +++ b/webapp/packages/core-settings-localization/src/SettingsLocalizationService.test.ts @@ -52,8 +52,8 @@ const newSettings = { }; test('New settings override deprecated settings', async () => { - const settings = app.injector.getServiceByClass(SettingsLocalizationService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(SettingsLocalizationService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(newSettings))); @@ -64,8 +64,8 @@ test('New settings override deprecated settings', async () => { }); test('Deprecated settings are used if new settings are not defined', async () => { - const settings = app.injector.getServiceByClass(SettingsLocalizationService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(SettingsLocalizationService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings))); diff --git a/webapp/packages/core-theming/src/ThemeSettingsService.test.ts b/webapp/packages/core-theming/src/ThemeSettingsService.test.ts index d4d34c3f94..36ff36d699 100644 --- a/webapp/packages/core-theming/src/ThemeSettingsService.test.ts +++ b/webapp/packages/core-theming/src/ThemeSettingsService.test.ts @@ -54,8 +54,8 @@ const newSettings = { }; test('New Settings override deprecated settings', async () => { - const settings = app.injector.getServiceByClass(ThemeSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(ThemeSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(newSettings))); @@ -66,8 +66,8 @@ test('New Settings override deprecated settings', async () => { }); test('Deprecated settings are used if new settings are not defined', async () => { - const settings = app.injector.getServiceByClass(ThemeSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(ThemeSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings))); diff --git a/webapp/packages/core-ui/src/Form/FormState.ts b/webapp/packages/core-ui/src/Form/FormState.ts index 3d620b2b48..2b651eda81 100644 --- a/webapp/packages/core-ui/src/Form/FormState.ts +++ b/webapp/packages/core-ui/src/Form/FormState.ts @@ -8,7 +8,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { DataContext, dataContextAddDIProvider, DataContextGetter, type IDataContext } from '@cloudbeaver/core-data-context'; -import type { App } from '@cloudbeaver/core-di'; +import type { IServiceProvider } from '@cloudbeaver/core-di'; import type { ENotificationType } from '@cloudbeaver/core-events'; import { Executor, ExecutorInterrupter, IExecutionContextProvider, type IExecutor } from '@cloudbeaver/core-executor'; import { isLoadableStateHasException, MetadataMap, uuid } from '@cloudbeaver/core-utils'; @@ -48,7 +48,7 @@ export class FormState implements IFormState { readonly formatTask: IExecutor>; readonly validationTask: IExecutor>; - constructor(app: App, service: FormBaseService, state: TState) { + constructor(serviceProvider: IServiceProvider, service: FormBaseService, state: TState) { this.id = uuid(); this.service = service; this.dataContext = new DataContext(); @@ -84,7 +84,7 @@ export class FormState implements IFormState { this.dataContext.set(DATA_CONTEXT_LOADABLE_STATE, loadableStateContext(), this.id); this.dataContext.set(DATA_CONTEXT_FORM_STATE, this, this.id); - dataContextAddDIProvider(this.dataContext, app, this.id); + dataContextAddDIProvider(this.dataContext, serviceProvider, this.id); makeObservable(this, { mode: observable, diff --git a/webapp/packages/core-utils/package.json b/webapp/packages/core-utils/package.json index 7d5d4cc25c..7fdd37ed27 100644 --- a/webapp/packages/core-utils/package.json +++ b/webapp/packages/core-utils/package.json @@ -35,11 +35,9 @@ "devDependencies": { "@types/jest": "^29", "@types/md5": "^2", - "@types/react": "^18", "@types/underscore": "^1", "@types/uuid": "^10", "@types/whatwg-mimetype": "^3", - "react": "^18", "typescript": "^5", "zod": "^3" } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts index e00bdeb8ae..587c6839ec 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts @@ -5,13 +5,13 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import type { App } from '@cloudbeaver/core-di'; +import type { IServiceProvider } from '@cloudbeaver/core-di'; import { FormState } from '@cloudbeaver/core-ui'; import type { AdministrationUserFormService, IUserFormState } from './AdministrationUserFormService'; export class AdministrationUserFormState extends FormState { - constructor(app: App, service: AdministrationUserFormService, config: IUserFormState) { - super(app, service, config); + constructor(serviceProvider: IServiceProvider, service: AdministrationUserFormService, config: IUserFormState) { + super(serviceProvider, service, config); } } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/getUserFormConnectionAccessPart.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/getUserFormConnectionAccessPart.ts index 362ef7c772..a96b94d8e5 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/getUserFormConnectionAccessPart.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/getUserFormConnectionAccessPart.ts @@ -21,8 +21,8 @@ export function getUserFormConnectionAccessPart(formState: IFormState('Us export function getUserFormInfoPart(formState: IFormState): UserFormInfoPart { return formState.getPart(DATA_CONTEXT_USER_FORM_INFO_PART, context => { const di = context.get(DATA_CONTEXT_DI_PROVIDER)!; - const usersResource = di.getServiceByClass(UsersResource); - const serverConfigResource = di.getServiceByClass(ServerConfigResource); - const authRolesResource = di.getServiceByClass(AuthRolesResource); + const usersResource = di.getService(UsersResource); + const serverConfigResource = di.getService(ServerConfigResource); + const authRolesResource = di.getService(AuthRolesResource); return new UserFormInfoPart(authRolesResource, serverConfigResource, formState, usersResource); }); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts index 0f45e1fe01..d2e5c83cf0 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts @@ -8,7 +8,7 @@ import { makeObservable, observable } from 'mobx'; import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; -import { App, injectable } from '@cloudbeaver/core-di'; +import { injectable, IServiceProvider } from '@cloudbeaver/core-di'; import { AdministrationUserFormService } from '../UserForm/AdministrationUserFormService'; import { AdministrationUserFormState } from '../UserForm/AdministrationUserFormState'; @@ -24,7 +24,7 @@ export class CreateUserService { readonly toolsContainer: PlaceholderContainer; constructor( - private readonly app: App, + private readonly serviceProvider: IServiceProvider, private readonly administrationUserFormService: AdministrationUserFormService, private readonly usersAdministrationNavigationService: UsersAdministrationNavigationService, ) { @@ -50,7 +50,7 @@ export class CreateUserService { return; } - this.state = new AdministrationUserFormState(this.app, this.administrationUserFormService, { userId: null }); + this.state = new AdministrationUserFormState(this.serviceProvider, this.administrationUserFormService, { userId: null }); this.usersAdministrationNavigationService.navToCreate(); } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx index 88f69f5477..f61bae0f5a 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx @@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite'; import { useState } from 'react'; import { Container, Loader, s, TableItemExpandProps, useS } from '@cloudbeaver/core-blocks'; -import { App, useService } from '@cloudbeaver/core-di'; +import { IServiceProvider, useService } from '@cloudbeaver/core-di'; import { FormMode } from '@cloudbeaver/core-ui'; import { AdministrationUserForm } from '../UserForm/AdministrationUserForm'; @@ -20,9 +20,9 @@ import style from './UserEdit.module.css'; export const UserEdit = observer>(function UserEdit({ item, onClose }) { const styles = useS(style); const administrationUserFormService = useService(AdministrationUserFormService); - const app = useService(App); + const serviceProvider = useService(IServiceProvider); const [state] = useState(() => { - const state = new AdministrationUserFormState(app, administrationUserFormService, { + const state = new AdministrationUserFormState(serviceProvider, administrationUserFormService, { userId: item, }); state.setMode(FormMode.Edit); diff --git a/webapp/packages/plugin-data-export/package.json b/webapp/packages/plugin-data-export/package.json index 4205987f5c..ea9a09faf4 100644 --- a/webapp/packages/plugin-data-export/package.json +++ b/webapp/packages/plugin-data-export/package.json @@ -28,7 +28,6 @@ "@cloudbeaver/core-resource": "^0", "@cloudbeaver/core-root": "^0", "@cloudbeaver/core-sdk": "^0", - "@cloudbeaver/core-settings": "^0", "@cloudbeaver/core-ui": "^0", "@cloudbeaver/core-utils": "^0", "@cloudbeaver/core-view": "^0", @@ -40,31 +39,6 @@ }, "peerDependencies": {}, "devDependencies": { - "@cloudbeaver/core-administration": "^0", - "@cloudbeaver/core-app": "^0", - "@cloudbeaver/core-authentication": "^0", - "@cloudbeaver/core-browser": "^0", - "@cloudbeaver/core-client-activity": "^0", - "@cloudbeaver/core-connections": "^0", - "@cloudbeaver/core-dialogs": "^0", - "@cloudbeaver/core-events": "^0", - "@cloudbeaver/core-localization": "^0", - "@cloudbeaver/core-navigation-tree": "^0", - "@cloudbeaver/core-projects": "^0", - "@cloudbeaver/core-root": "^0", - "@cloudbeaver/core-routing": "^0", - "@cloudbeaver/core-sdk": "^0", - "@cloudbeaver/core-settings": "^0", - "@cloudbeaver/core-storage": "^0", - "@cloudbeaver/core-ui": "^0", - "@cloudbeaver/core-view": "^0", - "@cloudbeaver/plugin-datasource-context-switch": "^0", - "@cloudbeaver/plugin-navigation-tabs": "^0", - "@cloudbeaver/plugin-navigation-tree": "^0", - "@cloudbeaver/plugin-object-viewer": "^0", - "@cloudbeaver/tests-runner": "^0", - "@testing-library/jest-dom": "^6", - "@types/jest": "^29", "@types/react": "^18", "typescript": "^5", "typescript-plugin-css-modules": "^5" diff --git a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts index f42885a63b..f926dd692b 100644 --- a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts @@ -20,8 +20,8 @@ import { DATA_VIEWER_DATA_MODEL_ACTIONS_MENU, DataViewerPresentationType, DataViewerService, - IDatabaseDataSource, IDataContainerOptions, + isResultSetDataSource, } from '@cloudbeaver/plugin-data-viewer'; import type { IDataQueryOptions } from '@cloudbeaver/plugin-sql-editor'; @@ -59,11 +59,15 @@ export class DataExportMenuService { contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], isHidden: (context, action) => !this.dataViewerService.canExportData, actions: [ACTION_EXPORT], + isActionApplicable: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + return isResultSetDataSource(model.source); + }, isDisabled(context) { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - return model.isLoading() || model.isDisabled(resultIndex) || !model.getResult(resultIndex); + return model.isLoading() || model.isDisabled(resultIndex) || !model.source.getResult(resultIndex); }, getActionInfo(context, action) { if (action === ACTION_EXPORT) { @@ -77,14 +81,13 @@ export class DataExportMenuService { const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; if (action === ACTION_EXPORT) { - const result = model.getResult(resultIndex); + const result = model.source.getResult(resultIndex); + const source = model.source; - if (!result) { + if (!result || !isResultSetDataSource(source)) { throw new Error('Result must be provided'); } - const source = model.source as IDatabaseDataSource; - if (!source.options) { throw new Error('Source options must be provided'); } diff --git a/webapp/packages/plugin-data-export/tsconfig.json b/webapp/packages/plugin-data-export/tsconfig.json index c4c69c86c9..defe9a5fab 100644 --- a/webapp/packages/plugin-data-export/tsconfig.json +++ b/webapp/packages/plugin-data-export/tsconfig.json @@ -6,27 +6,9 @@ "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" }, "references": [ - { - "path": "../core-administration/tsconfig.json" - }, - { - "path": "../core-app/tsconfig.json" - }, - { - "path": "../core-authentication/tsconfig.json" - }, { "path": "../core-blocks/tsconfig.json" }, - { - "path": "../core-browser/tsconfig.json" - }, - { - "path": "../core-client-activity/tsconfig.json" - }, - { - "path": "../core-connections/tsconfig.json" - }, { "path": "../core-connections/tsconfig.json" }, @@ -36,60 +18,24 @@ { "path": "../core-dialogs/tsconfig.json" }, - { - "path": "../core-dialogs/tsconfig.json" - }, - { - "path": "../core-events/tsconfig.json" - }, { "path": "../core-events/tsconfig.json" }, { "path": "../core-localization/tsconfig.json" }, - { - "path": "../core-localization/tsconfig.json" - }, { "path": "../core-navigation-tree/tsconfig.json" }, - { - "path": "../core-navigation-tree/tsconfig.json" - }, - { - "path": "../core-projects/tsconfig.json" - }, { "path": "../core-resource/tsconfig.json" }, { "path": "../core-root/tsconfig.json" }, - { - "path": "../core-root/tsconfig.json" - }, - { - "path": "../core-routing/tsconfig.json" - }, { "path": "../core-sdk/tsconfig.json" }, - { - "path": "../core-sdk/tsconfig.json" - }, - { - "path": "../core-settings/tsconfig.json" - }, - { - "path": "../core-settings/tsconfig.json" - }, - { - "path": "../core-storage/tsconfig.json" - }, - { - "path": "../core-ui/tsconfig.json" - }, { "path": "../core-ui/tsconfig.json" }, @@ -99,29 +45,11 @@ { "path": "../core-view/tsconfig.json" }, - { - "path": "../core-view/tsconfig.json" - }, { "path": "../plugin-data-viewer/tsconfig.json" }, - { - "path": "../plugin-datasource-context-switch/tsconfig.json" - }, - { - "path": "../plugin-navigation-tabs/tsconfig.json" - }, - { - "path": "../plugin-navigation-tree/tsconfig.json" - }, - { - "path": "../plugin-object-viewer/tsconfig.json" - }, { "path": "../plugin-sql-editor/tsconfig.json" - }, - { - "path": "../tests-runner/tsconfig.json" } ], "include": [ diff --git a/webapp/packages/plugin-data-grid/package.json b/webapp/packages/plugin-data-grid/package.json index 52e3b9fadd..08554571b8 100644 --- a/webapp/packages/plugin-data-grid/package.json +++ b/webapp/packages/plugin-data-grid/package.json @@ -21,7 +21,6 @@ "dependencies": { "@cloudbeaver/core-blocks": "^0", "@cloudbeaver/core-di": "^0", - "@cloudbeaver/core-theming": "^0", "@cloudbeaver/plugin-react-data-grid": "^0", "clsx": "^2", "mobx-react-lite": "^4", @@ -29,7 +28,6 @@ }, "peerDependencies": {}, "devDependencies": { - "@cloudbeaver/core-theming": "^0", "@types/react": "^18", "typescript": "^5", "typescript-plugin-css-modules": "^5" diff --git a/webapp/packages/plugin-data-grid/tsconfig.json b/webapp/packages/plugin-data-grid/tsconfig.json index 4ea0a654c6..6c742da4c4 100644 --- a/webapp/packages/plugin-data-grid/tsconfig.json +++ b/webapp/packages/plugin-data-grid/tsconfig.json @@ -12,12 +12,6 @@ { "path": "../core-di/tsconfig.json" }, - { - "path": "../core-theming/tsconfig.json" - }, - { - "path": "../core-theming/tsconfig.json" - }, { "path": "../plugin-react-data-grid/tsconfig.json" } diff --git a/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts b/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts index 8c6d556735..b57afad88a 100644 --- a/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts +++ b/webapp/packages/plugin-data-import/src/DataImportBootstrap.ts @@ -15,6 +15,7 @@ import { DATA_CONTEXT_DV_PRESENTATION, DATA_VIEWER_DATA_MODEL_ACTIONS_MENU, DataViewerPresentationType, + isResultSetDataModel, } from '@cloudbeaver/plugin-data-viewer'; import { DataImportDialogLazy } from './DataImportDialog/DataImportDialogLazy'; @@ -36,11 +37,12 @@ export class DataImportBootstrap extends Bootstrap { id: 'data-import-base-handler', contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], actions: [ACTION_IMPORT], + isActionApplicable: context => isResultSetDataModel(context.get(DATA_CONTEXT_DV_DDM)), isDisabled(context) { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - return model.isLoading() || model.isDisabled(resultIndex) || !model.getResult(resultIndex); + return model.isLoading() || model.isDisabled(resultIndex) || !model.source.getResult(resultIndex); }, getActionInfo(_, action) { if (action === ACTION_IMPORT) { @@ -53,8 +55,12 @@ export class DataImportBootstrap extends Bootstrap { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + if (!isResultSetDataModel(model)) { + throw new Error('Execution context is not provided'); + } + if (action === ACTION_IMPORT) { - const result = model.getResult(resultIndex); + const result = model.source.getResult(resultIndex); if (!result?.id) { throw new Error('Result must be provided'); diff --git a/webapp/packages/plugin-data-spreadsheet-new/package.json b/webapp/packages/plugin-data-spreadsheet-new/package.json index 172697b9e0..d4225fa3b4 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/package.json +++ b/webapp/packages/plugin-data-spreadsheet-new/package.json @@ -28,7 +28,6 @@ "@cloudbeaver/core-localization": "^0", "@cloudbeaver/core-sdk": "^0", "@cloudbeaver/core-settings": "^0", - "@cloudbeaver/core-theming": "^0", "@cloudbeaver/core-ui": "^0", "@cloudbeaver/core-utils": "^0", "@cloudbeaver/core-view": "^0", @@ -59,7 +58,6 @@ "@cloudbeaver/core-sdk": "^0", "@cloudbeaver/core-settings": "^0", "@cloudbeaver/core-storage": "^0", - "@cloudbeaver/core-theming": "^0", "@cloudbeaver/core-ui": "^0", "@cloudbeaver/core-view": "^0", "@cloudbeaver/plugin-data-viewer": "^0", diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts index 375daf8ee0..298b6f8788 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts @@ -9,7 +9,9 @@ import { injectable } from '@cloudbeaver/core-di'; import { DatabaseEditChangeType, isBooleanValuePresentationAvailable, + isResultSetDataSource, ResultSetDataContentAction, + ResultSetDataSource, ResultSetEditAction, ResultSetFormatAction, ResultSetSelectAction, @@ -48,12 +50,13 @@ export class DataGridContextMenuCellEditingService { title: 'data_grid_table_editing_open_inline_editor', icon: 'edit', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const format = context.data.model.source.getAction(context.data.resultIndex, ResultSetFormatAction); - const view = context.data.model.source.getAction(context.data.resultIndex, ResultSetViewAction); - const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const format = source.getAction(context.data.resultIndex, ResultSetFormatAction); + const view = source.getAction(context.data.resultIndex, ResultSetViewAction); + const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); const cellValue = view.getCellValue(context.data.key); const column = view.getColumn(context.data.key.column); const isComplex = format.isBinary(context.data.key) || format.isGeometry(context.data.key); @@ -74,18 +77,20 @@ export class DataGridContextMenuCellEditingService { order: 1, title: 'data_grid_table_editing_set_to_null', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { const { key, model, resultIndex } = context.data; - const view = model.source.getAction(resultIndex, ResultSetViewAction); - const format = model.source.getAction(resultIndex, ResultSetFormatAction); + const source = model.source as unknown as ResultSetDataSource; + const view = source.getAction(resultIndex, ResultSetViewAction); + const format = source.getAction(resultIndex, ResultSetFormatAction); const cellValue = view.getCellValue(key); return cellValue === undefined || format.isReadOnly(context.data.key) || view.getColumn(key.column)?.required || format.isNull(key); }, onClick(context) { - context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction).set(context.data.key, null); + const source = context.data.model.source as unknown as ResultSetDataSource; + source.getAction(context.data.resultIndex, ResultSetEditAction).set(context.data.key, null); }, }); this.dataGridContextMenuService.add(this.getMenuEditingToken(), { @@ -94,14 +99,16 @@ export class DataGridContextMenuCellEditingService { icon: '/icons/data_add_sm.svg', title: 'data_grid_table_editing_row_add', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); return !editor.hasFeature('add'); }, onClick(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); editor.addRow(context.data.key.row); }, }); @@ -111,14 +118,16 @@ export class DataGridContextMenuCellEditingService { icon: '/icons/data_add_copy_sm.svg', title: 'data_grid_table_editing_row_add_copy', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); return !editor.hasFeature('add'); }, onClick(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); editor.duplicateRow(context.data.key.row); }, }); @@ -128,20 +137,22 @@ export class DataGridContextMenuCellEditingService { icon: '/icons/data_delete_sm.svg', title: 'data_grid_table_editing_row_delete', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); if (context.data.model.isReadonly(context.data.resultIndex) || !editor.hasFeature('delete')) { return true; } - const format = context.data.model.source.getAction(context.data.resultIndex, ResultSetFormatAction); + const format = source.getAction(context.data.resultIndex, ResultSetFormatAction); return format.isReadOnly(context.data.key) || editor.getElementState(context.data.key) === DatabaseEditChangeType.delete; }, onClick(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); editor.deleteRow(context.data.key.row); }, }); @@ -151,24 +162,26 @@ export class DataGridContextMenuCellEditingService { icon: '/icons/data_delete_sm.svg', title: 'data_viewer_action_edit_delete', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); if (context.data.model.isReadonly(context.data.resultIndex) || !editor.hasFeature('delete')) { return true; } - const select = context.data.model.source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); const selectedElements = select?.getSelectedElements() || []; return !selectedElements.some(key => editor.getElementState(key) !== DatabaseEditChangeType.delete); }, onClick(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); - const select = context.data.model.source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); + const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); const selectedElements = select?.getSelectedElements() || []; @@ -181,14 +194,16 @@ export class DataGridContextMenuCellEditingService { icon: '/icons/data_revert_sm.svg', title: 'data_grid_table_editing_row_revert', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); return editor.getElementState(context.data.key) === null; }, onClick(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); editor.revert(context.data.key); }, }); @@ -198,18 +213,20 @@ export class DataGridContextMenuCellEditingService { icon: '/icons/data_revert_sm.svg', title: 'data_viewer_action_edit_revert', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); - const select = context.data.model.source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); + const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); const selectedElements = select?.getSelectedElements() || []; return !selectedElements.some(key => editor.getElementState(key) !== null); }, onClick(context) { - const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); - const select = context.data.model.source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); + const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); const selectedElements = select?.getSelectedElements() || []; editor.revert(...selectedElements); 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 fbaaad2931..54e065d5bf 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 @@ -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 { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, ComputedContextMenuModel, DialogueStateResult, IContextMenuItem, IMenuContext } from '@cloudbeaver/core-dialogs'; import { ClipboardService } from '@cloudbeaver/core-ui'; @@ -13,19 +14,21 @@ import { DatabaseDataConstraintAction, IDatabaseDataModel, IDatabaseDataOptions, - IDatabaseResultSet, IResultSetColumnKey, IS_NOT_NULL_ID, IS_NULL_ID, isFilterConstraint, + isResultSetDataSource, nullOperationsFilter, ResultSetDataAction, + ResultSetDataSource, ResultSetFormatAction, wrapOperationArgument, } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContextMenuService, IDataGridCellMenuContext } from '../DataGridContextMenuService'; -import { FilterCustomValueDialog } from './FilterCustomValueDialog'; + +const FilterCustomValueDialog = importLazyComponent(() => import('./FilterCustomValueDialog').then(m => m.FilterCustomValueDialog)); @injectable() export class DataGridContextMenuFilterService { @@ -44,7 +47,7 @@ export class DataGridContextMenuFilterService { } private async applyFilter( - model: IDatabaseDataModel, + model: IDatabaseDataModel, resultIndex: number, column: IResultSetColumnKey, operator: string, @@ -80,7 +83,8 @@ export class DataGridContextMenuFilterService { isHidden?: (context: IMenuContext) => boolean, ): Array> { const { model, resultIndex, key } = context.data; - const data = model.source.getAction(resultIndex, ResultSetDataAction); + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); const supportedOperations = data.getColumnOperations(key.column); const columnLabel = data.getColumn(key.column)?.label || ''; @@ -89,7 +93,7 @@ export class DataGridContextMenuFilterService { .map(operation => ({ id: operation.id, icon, - isPresent: () => true, + isPresent: context => isResultSetDataSource(context.data.model.source), isDisabled(context) { return context.data.model.isLoading(); }, @@ -105,7 +109,7 @@ export class DataGridContextMenuFilterService { onClick: async () => { const val = typeof value === 'function' ? value() : value; const wrappedValue = wrapOperationArgument(operation.id, val); - await this.applyFilter(model, resultIndex, key.column, operation.id, wrappedValue); + await this.applyFilter(model as unknown as IDatabaseDataModel, resultIndex, key.column, operation.id, wrappedValue); }, })); } @@ -118,14 +122,15 @@ export class DataGridContextMenuFilterService { icon: 'filter', isPanel: true, isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { if (context.data.model.isDisabled(context.data.resultIndex)) { return true; } - const constraints = context.data.model.source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); return !constraints.supported; }, }); @@ -135,19 +140,21 @@ export class DataGridContextMenuFilterService { title: 'data_grid_table_delete_filters_and_orders', icon: 'erase', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { if (context.data.model.isDisabled(context.data.resultIndex)) { return true; } - const constraints = context.data.model.source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); return constraints.orderConstraints.length === 0 && constraints.filterConstraints.length === 0; }, onClick: async context => { const { model, resultIndex } = context.data; - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); + const source = model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); await model.request(() => { constraints.deleteData(); @@ -160,14 +167,15 @@ export class DataGridContextMenuFilterService { title: 'ui_clipboard', icon: 'filter-clipboard', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { if (!this.clipboardService.clipboardAvailable || this.clipboardService.state === 'denied') { return true; } - const data = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const data = source.getAction(context.data.resultIndex, ResultSetDataAction); const supportedOperations = data.getColumnOperations(context.data.key.column); return supportedOperations.length === 0; @@ -207,12 +215,13 @@ export class DataGridContextMenuFilterService { title: 'data_grid_table_filter_cell_value', icon: 'filter', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { const { model, resultIndex, key } = context.data; - const data = model.source.getAction(resultIndex, ResultSetDataAction); - const format = model.source.getAction(resultIndex, ResultSetFormatAction); + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + const format = source.getAction(resultIndex, ResultSetFormatAction); const supportedOperations = data.getColumnOperations(key.column); const value = data.getCellValue(key); @@ -222,7 +231,8 @@ export class DataGridContextMenuFilterService { id: 'cellValuePanel', menuItemsGetter: context => { const { model, resultIndex, key } = context.data; - const format = model.source.getAction(resultIndex, ResultSetFormatAction); + const source = model.source as unknown as ResultSetDataSource; + const format = source.getAction(resultIndex, ResultSetFormatAction); const cellValue = format.getText(key); const items = this.getGeneralizedMenuItems(context, cellValue, 'filter'); return items; @@ -235,11 +245,12 @@ export class DataGridContextMenuFilterService { title: 'data_grid_table_filter_custom_value', icon: 'filter-custom', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { const { model, resultIndex, key } = context.data; - const data = model.source.getAction(resultIndex, ResultSetDataAction); + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); const cellValue = data.getCellValue(key); const supportedOperations = data.getColumnOperations(key.column); @@ -249,7 +260,8 @@ export class DataGridContextMenuFilterService { id: 'customValuePanel', menuItemsGetter: context => { const { model, resultIndex, key } = context.data; - const data = model.source.getAction(resultIndex, ResultSetDataAction); + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); const supportedOperations = data.getColumnOperations(key.column); const columnLabel = data.getColumn(key.column)?.label || ''; @@ -267,7 +279,8 @@ export class DataGridContextMenuFilterService { title: title + ' ..', icon: 'filter-custom', onClick: async () => { - const format = model.source.getAction(resultIndex, ResultSetFormatAction); + const source = model.source as unknown as ResultSetDataSource; + const format = source.getAction(resultIndex, ResultSetFormatAction); const displayString = format.getText(key); const customValue = await this.commonDialogService.open(FilterCustomValueDialog, { defaultValue: displayString, @@ -278,7 +291,13 @@ export class DataGridContextMenuFilterService { return; } - await this.applyFilter(model, resultIndex, key.column, operation.id, customValue); + await this.applyFilter( + model as unknown as IDatabaseDataModel, + resultIndex, + key.column, + operation.id, + customValue, + ); }, }; }); @@ -290,21 +309,28 @@ export class DataGridContextMenuFilterService { order: 3, icon: 'filter', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { - const data = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const data = source.getAction(context.data.resultIndex, ResultSetDataAction); const supportedOperations = data.getColumnOperations(context.data.key.column); return !supportedOperations.some(operation => operation.id === IS_NULL_ID); }, titleGetter: context => { - const data = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const data = source.getAction(context.data.resultIndex, ResultSetDataAction); const columnLabel = data.getColumn(context.data.key.column)?.label || ''; return `${columnLabel} IS NULL`; }, onClick: async context => { - await this.applyFilter(context.data.model, context.data.resultIndex, context.data.key.column, IS_NULL_ID); + await this.applyFilter( + context.data.model as unknown as IDatabaseDataModel, + context.data.resultIndex, + context.data.key.column, + IS_NULL_ID, + ); }, }); this.dataGridContextMenuService.add(this.getMenuFilterToken(), { @@ -312,21 +338,28 @@ export class DataGridContextMenuFilterService { order: 4, icon: 'filter', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { - const data = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const data = source.getAction(context.data.resultIndex, ResultSetDataAction); const supportedOperations = data.getColumnOperations(context.data.key.column); return !supportedOperations.some(operation => operation.id === IS_NOT_NULL_ID); }, titleGetter: context => { - const data = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const data = source.getAction(context.data.resultIndex, ResultSetDataAction); const columnLabel = data.getColumn(context.data.key.column)?.label || ''; return `${columnLabel} IS NOT NULL`; }, onClick: async context => { - await this.applyFilter(context.data.model, context.data.resultIndex, context.data.key.column, IS_NOT_NULL_ID); + await this.applyFilter( + context.data.model as unknown as IDatabaseDataModel, + context.data.resultIndex, + context.data.key.column, + IS_NOT_NULL_ID, + ); }, }); this.dataGridContextMenuService.add(this.getMenuFilterToken(), { @@ -334,26 +367,29 @@ export class DataGridContextMenuFilterService { order: 5, icon: 'filter-reset', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { const { model, resultIndex, key } = context.data; - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); - const data = model.source.getAction(resultIndex, ResultSetDataAction); + const source = model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + const data = source.getAction(resultIndex, ResultSetDataAction); const resultColumn = data.getColumn(key.column); const currentConstraint = resultColumn ? constraints.get(resultColumn.position) : undefined; return !currentConstraint || !isFilterConstraint(currentConstraint); }, titleGetter: context => { - const data = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const data = source.getAction(context.data.resultIndex, ResultSetDataAction); const columnLabel = data.getColumn(context.data.key.column)?.name || ''; return `Delete filter for ${columnLabel}`; }, onClick: async context => { const { model, resultIndex, key } = context.data; - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); - const data = model.source.getAction(resultIndex, ResultSetDataAction); + const source = model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + const data = source.getAction(resultIndex, ResultSetDataAction); const resultColumn = data.getColumn(key.column); if (!resultColumn) { @@ -371,17 +407,19 @@ export class DataGridContextMenuFilterService { icon: 'filter-reset-all', title: 'data_grid_table_filter_reset_all_filters', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { const { model, resultIndex } = context.data; - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); + const source = model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); return constraints.filterConstraints.length === 0 && !model.requestInfo.requestFilter; }, onClick: async context => { const { model, resultIndex } = context.data; - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); + const source = model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); await model.request(() => { constraints.deleteDataFilters(); 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 6eb31e9616..6005d997ff 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 @@ -10,9 +10,13 @@ import { DatabaseDataConstraintAction, EOrder, IDatabaseDataModel, + IDatabaseDataOptions, IResultSetColumnKey, + isResultSetDataModel, + isResultSetDataSource, Order, ResultSetDataAction, + ResultSetDataSource, } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContextMenuService } from './DataGridContextMenuService'; @@ -27,7 +31,11 @@ export class DataGridContextMenuOrderService { return DataGridContextMenuOrderService.menuOrderToken; } - private async changeOrder(model: IDatabaseDataModel, resultIndex: number, column: IResultSetColumnKey, order: Order) { + private async changeOrder(unknownModel: IDatabaseDataModel, resultIndex: number, column: IResultSetColumnKey, order: Order) { + const model = unknownModel as any; + if (!isResultSetDataModel(model)) { + throw new Error('Unsupported data model'); + } const data = model.source.getAction(resultIndex, ResultSetDataAction); const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); const resultColumn = data.getColumn(column); @@ -49,10 +57,11 @@ export class DataGridContextMenuOrderService { icon: 'order-arrow-unknown', isPanel: true, isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden(context) { - const constraints = context.data.model.source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); return !constraints.supported || context.data.model.isDisabled(context.data.resultIndex); }, }); @@ -61,7 +70,7 @@ export class DataGridContextMenuOrderService { type: 'radio', title: 'ASC', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isDisabled: context => context.data.model.isLoading(), onClick: async context => { @@ -69,8 +78,9 @@ export class DataGridContextMenuOrderService { }, isChecked: context => { const { model, resultIndex, key } = context.data; - const data = model.source.getAction(resultIndex, ResultSetDataAction); - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); const resultColumn = data.getColumn(key.column); return !!resultColumn && constraints.getOrder(resultColumn.position) === EOrder.asc; @@ -81,7 +91,7 @@ export class DataGridContextMenuOrderService { type: 'radio', title: 'DESC', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isDisabled: context => context.data.model.isLoading(), onClick: async context => { @@ -89,8 +99,9 @@ export class DataGridContextMenuOrderService { }, isChecked: context => { const { model, resultIndex, key } = context.data; - const data = model.source.getAction(resultIndex, ResultSetDataAction); - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); const resultColumn = data.getColumn(key.column); return !!resultColumn && constraints.getOrder(resultColumn.position) === EOrder.desc; @@ -101,7 +112,7 @@ export class DataGridContextMenuOrderService { type: 'radio', title: 'data_grid_table_disable_order', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isDisabled: context => context.data.model.isLoading(), onClick: async context => { @@ -109,8 +120,9 @@ export class DataGridContextMenuOrderService { }, isChecked: context => { const { model, resultIndex, key } = context.data; - const data = model.source.getAction(resultIndex, ResultSetDataAction); - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); const resultColumn = data.getColumn(key.column); return !!resultColumn && constraints.getOrder(resultColumn.position) === null; @@ -120,15 +132,17 @@ export class DataGridContextMenuOrderService { id: 'disableOrders', title: 'data_grid_table_disable_all_orders', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, isHidden: context => { - const constraints = context.data.model.source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); return !constraints.orderConstraints.length; }, isDisabled: context => context.data.model.isLoading(), onClick: async context => { - const constraints = context.data.model.source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); await context.data.model.request(() => { constraints.deleteOrders(); }); 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 70b61d8a7b..fe4b084dd9 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 @@ -11,7 +11,9 @@ import { NotificationService } from '@cloudbeaver/core-events'; import { createResultSetBlobValue, DataViewerService, + isResultSetDataSource, ResultSetDataContentAction, + ResultSetDataSource, ResultSetEditAction, ResultSetFormatAction, } from '@cloudbeaver/plugin-data-viewer'; @@ -33,10 +35,11 @@ export class DataGridContextMenuSaveContentService { title: 'ui_download', icon: '/icons/export.svg', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, onClick: async context => { - const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); try { await content.downloadFileData(context.data.key); } catch (exception: any) { @@ -44,12 +47,14 @@ export class DataGridContextMenuSaveContentService { } }, isHidden: context => { - const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); return !content.isDownloadable(context.data.key) || !this.dataViewerService.canExportData; }, isDisabled: context => { - const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); return context.data.model.isLoading() || content.isLoading(context.data.key); }, @@ -60,11 +65,12 @@ export class DataGridContextMenuSaveContentService { title: 'ui_upload', icon: '/icons/import.svg', isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); }, onClick: async context => { selectFiles(files => { - const edit = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const edit = source.getAction(context.data.resultIndex, ResultSetEditAction); const file = files?.[0] ?? undefined; if (file) { edit.set(context.data.key, createResultSetBlobValue(file)); @@ -72,12 +78,14 @@ export class DataGridContextMenuSaveContentService { }); }, isHidden: context => { - const format = context.data.model.source.getAction(context.data.resultIndex, ResultSetFormatAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const format = source.getAction(context.data.resultIndex, ResultSetFormatAction); return !format.isBinary(context.data.key) || context.data.model.isReadonly(context.data.resultIndex); }, isDisabled: context => { - const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction); + const source = context.data.model.source as unknown as ResultSetDataSource; + const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); return context.data.model.isLoading() || content.isLoading(context.data.key); }, diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx index c8094f88e3..ee97e65381 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx @@ -19,14 +19,16 @@ import { DATA_CONTEXT_DV_PRESENTATION, DatabaseDataSelectActionsData, DatabaseEditChangeType, + DatabaseSelectAction, DataViewerPresentationType, - IDatabaseResultSet, + IDatabaseDataModel, IDataPresentationProps, IResultSetEditActionData, IResultSetElementKey, IResultSetPartialKey, IResultSetRowKey, ResultSetDataKeysUtils, + ResultSetDataSource, ResultSetSelectAction, } from '@cloudbeaver/plugin-data-viewer'; @@ -57,13 +59,7 @@ const rowHeight = 25; const headerHeight = 28; const MAX_CELL_TEXT_SIZE = 100 * 1024; -export const DataGridTable = observer>(function DataGridTable({ - model, - actions, - resultIndex, - simple, - className, -}) { +export const DataGridTable = observer(function DataGridTable({ model, actions, resultIndex, simple, className }) { const translate = useTranslate(); const styles = useS(classes); @@ -81,11 +77,11 @@ export const DataGridTable = observer new Executor()); - const selectionAction = model.source.getAction(resultIndex, ResultSetSelectAction); + const selectionAction = (model.source as unknown as ResultSetDataSource).getAction(resultIndex, ResultSetSelectAction); const focusSyncRef = useRef(null); - const tableData = useTableData(model, resultIndex, dataGridDivRef); + const tableData = useTableData(model as unknown as IDatabaseDataModel, resultIndex, dataGridDivRef); const editingContext = useEditing({ readonly: model.isReadonly(resultIndex) || model.isDisabled(resultIndex), onEdit: (position, code, key) => { @@ -176,7 +172,7 @@ export const DataGridTable = observer { hamdlers.selectCell({ idx: startPosition.colIdx, rowIdx: startPosition.rowIdx }); @@ -403,7 +399,7 @@ export const DataGridTable = observer; resultIndex: number; attributePosition: number; className?: string; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.module.css b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.module.css index ebc91b289a..3cb6905343 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.module.css +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.module.css @@ -17,6 +17,7 @@ align-items: center; flex: 1 1 auto; overflow: hidden; + gap: 8px; cursor: pointer; } .icon { @@ -27,7 +28,6 @@ height: 16px; } .name { - margin-left: 8px; font-weight: 400; flex-grow: 1; } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx index 5a0035608a..907430caf7 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx @@ -11,6 +11,7 @@ import { useContext, useMemo } from 'react'; import { getComputed, s, StaticImage, useS } from '@cloudbeaver/core-blocks'; import type { SqlResultColumn } from '@cloudbeaver/core-sdk'; import type { RenderHeaderCellProps } from '@cloudbeaver/plugin-data-grid'; +import { DatabaseDataConstraintAction, isResultSetDataModel, ResultSetDataSource } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContext } from '../DataGridContext'; import { DataGridSelectionContext } from '../DataGridSelection/DataGridSelectionContext'; @@ -29,9 +30,14 @@ export const TableColumnHeader = observer>(function T const model = dataGridContext.model; const dnd = useTableColumnDnD(model, resultIndex, calculatedColumn.columnDataIndex); + let constraintsAction: DatabaseDataConstraintAction | undefined; + + if (isResultSetDataModel(model)) { + constraintsAction = (model.source as ResultSetDataSource).tryGetAction(resultIndex, DatabaseDataConstraintAction); + } const dataReadonly = getComputed(() => tableDataContext.isReadOnly() || model.isReadonly(resultIndex)); - const sortingDisabled = getComputed(() => !tableDataContext.constraints.supported || !model.source.executionContext?.context); + const sortingDisabled = getComputed(() => !constraintsAction?.supported || model.isDisabled(resultIndex)); let resultColumn: SqlResultColumn | undefined; let icon = calculatedColumn.icon; @@ -71,16 +77,22 @@ export const TableColumnHeader = observer>(function T } }, [calculatedColumn]); + const hasIcon = icon || (!dataReadonly && columnReadOnly); + return (
-
- {icon && } - {!dataReadonly && columnReadOnly &&
} -
+ {hasIcon && ( +
+ {icon && } + {!dataReadonly && columnReadOnly &&
} +
+ )}
{columnName}
- {!sortingDisabled && resultColumn && } + {!sortingDisabled && resultColumn && isResultSetDataModel(model) && ( + + )}
); }); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/useTableColumnDnD.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/useTableColumnDnD.ts index dc3556d042..92ae83b8ed 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/useTableColumnDnD.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/useTableColumnDnD.ts @@ -14,6 +14,8 @@ import { DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY, IDatabaseDataModel, IResultSetColumnKey, + isResultSetDataModel, + ResultSetDataSource, ResultSetViewAction, } from '@cloudbeaver/plugin-data-viewer'; @@ -28,7 +30,11 @@ interface TableColumnDnD { export function useTableColumnDnD(model: IDatabaseDataModel, resultIndex: number, columnKey: IResultSetColumnKey | null): TableColumnDnD { const context = useDataContext(); - const resultSetViewAction = model.source.tryGetAction(resultIndex, ResultSetViewAction); + let resultSetViewAction: ResultSetViewAction | undefined; + + if (isResultSetDataModel(model)) { + resultSetViewAction = (model.source as ResultSetDataSource).tryGetAction(resultIndex, ResultSetViewAction); + } useDataContextLink(context, (context, id) => { context.set(DATA_CONTEXT_DV_DDM, model, id); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts index 8b8a88e803..3ad473635e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts @@ -43,7 +43,6 @@ export interface ITableData { data: ResultSetDataAction; editor: ResultSetEditAction; view: ResultSetViewAction; - constraints: DatabaseDataConstraintAction; columns: Array>; columnKeys: IResultSetColumnKey[]; rows: IResultSetRowKey[]; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts index d3028b6c45..19ea8c2d80 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts @@ -12,6 +12,7 @@ import { useService } from '@cloudbeaver/core-di'; import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events'; import { copyToClipboard } from '@cloudbeaver/core-utils'; import { + DatabaseSelectAction, DataViewerService, IResultSetColumnKey, IResultSetElementKey, @@ -67,11 +68,11 @@ function getSelectedCellsValue(tableData: ITableData, selectedCells: Map { @@ -87,7 +88,11 @@ export function useGridSelectedCellsCopy( EventContext.set(event, EventStopPropagationFlag); if (dataViewerService.canCopyData) { - const focusedElement = props.resultSetSelectAction.getFocusedElement(); + if (!(props.selectAction instanceof ResultSetSelectAction)) { + throw new Error('Copying data is not supported'); + } + + const focusedElement = props.selectAction?.getFocusedElement(); let value: string | null = null; if (Array.from(props.selectionContext.selectedCells.keys()).length > 0) { diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.tsx index 3ed529a0ed..c559d7922a 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.tsx @@ -12,13 +12,13 @@ import type { Column } from '@cloudbeaver/plugin-data-grid'; import { DatabaseDataConstraintAction, IDatabaseDataModel, - IDatabaseResultSet, IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, ResultSetDataAction, ResultSetDataContentAction, ResultSetDataKeysUtils, + ResultSetDataSource, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction, @@ -43,7 +43,7 @@ export const indexColumn: Column = { }; export function useTableData( - model: IDatabaseDataModel, + model: IDatabaseDataModel, resultIndex: number, gridDIVElement: React.RefObject, ): ITableData { @@ -53,7 +53,6 @@ export function useTableData( const editor = model.source.getAction(resultIndex, ResultSetEditAction); const view = model.source.getAction(resultIndex, ResultSetViewAction); const dataContent = model.source.getAction(resultIndex, ResultSetDataContentAction); - const constraints = model.source.getAction(resultIndex, DatabaseDataConstraintAction); return useObservableRef }>( () => ({ @@ -173,7 +172,6 @@ export function useTableData( data: observable.ref, editor: observable.ref, view: observable.ref, - constraints: observable.ref, gridDIVElement: observable.ref, }, { @@ -182,7 +180,6 @@ export function useTableData( data, editor, view, - constraints, gridDIVElement, }, ); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableDataMeasurements.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableDataMeasurements.ts index d6d6b6d52e..21a1941c23 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableDataMeasurements.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableDataMeasurements.ts @@ -11,9 +11,9 @@ import { useObservableRef } from '@cloudbeaver/core-blocks'; import { TextTools } from '@cloudbeaver/core-utils'; import { type IDatabaseDataModel, - type IDatabaseResultSet, type IResultSetColumnKey, ResultSetDataKeysUtils, + ResultSetDataSource, ResultSetFormatAction, ResultSetViewAction, } from '@cloudbeaver/plugin-data-viewer'; @@ -34,7 +34,7 @@ interface ITableDataMeasurements { } // TODO: clear removed columns from cache -export function useTableDataMeasurements(model: IDatabaseDataModel, resultIndex: number): ITableDataMeasurements { +export function useTableDataMeasurements(model: IDatabaseDataModel, resultIndex: number): ITableDataMeasurements { const format = model.source.getAction(resultIndex, ResultSetFormatAction); const view = model.source.getAction(resultIndex, ResultSetViewAction); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.test.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.test.ts index dd09e44af3..54c95529bb 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.test.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.test.ts @@ -87,8 +87,8 @@ const newSettings = { }; test('New settings override deprecated', async () => { - const settings = app.injector.getServiceByClass(DataGridSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(DataGridSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(newSettings))); @@ -99,8 +99,8 @@ test('New settings override deprecated', async () => { }); test('Deprecated settings are used if new settings are not defined', async () => { - const settings = app.injector.getServiceByClass(DataGridSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(DataGridSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings))); diff --git a/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json b/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json index 7eedcec706..494f029e2e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json +++ b/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json @@ -84,12 +84,6 @@ { "path": "../core-storage/tsconfig.json" }, - { - "path": "../core-theming/tsconfig.json" - }, - { - "path": "../core-theming/tsconfig.json" - }, { "path": "../core-ui/tsconfig.json" }, diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPluginBootstrap.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPluginBootstrap.ts index 503c769889..630b253375 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPluginBootstrap.ts +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPluginBootstrap.ts @@ -15,10 +15,14 @@ import { DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_PRESENTATION, DATA_VIEWER_DATA_MODEL_ACTIONS_MENU, + DatabaseDataResultAction, DataPresentationService, DataPresentationType, DataViewerPresentationType, + IDatabaseDataModel, + isResultSetDataSource, ResultSetDataAction, + ResultSetDataSource, ResultSetSelectAction, } from '@cloudbeaver/plugin-data-viewer'; @@ -64,7 +68,8 @@ export class DVResultSetGroupingPluginBootstrap extends Bootstrap { menus: [DATA_VIEWER_DATA_MODEL_ACTIONS_MENU], isActionApplicable(context, action) { const presentation = context.get(DATA_CONTEXT_DV_PRESENTATION); - if (presentation && presentation.type !== DataViewerPresentationType.Data) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + if ((presentation && presentation.type !== DataViewerPresentationType.Data) || !isResultSetDataSource(model.source)) { return false; } switch (action) { @@ -99,7 +104,7 @@ export class DVResultSetGroupingPluginBootstrap extends Bootstrap { case ACTION_DATA_VIEWER_GROUPING_CLEAR: return grouping.getColumns().length === 0; case ACTION_DATA_VIEWER_GROUPING_REMOVE_COLUMN: { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; if (!model.source.hasResult(resultIndex)) { return true; @@ -136,7 +141,7 @@ export class DVResultSetGroupingPluginBootstrap extends Bootstrap { grouping.clear(); break; case ACTION_DATA_VIEWER_GROUPING_REMOVE_COLUMN: { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const selectionAction = model.source.getAction(resultIndex, ResultSetSelectAction); const dataAction = model.source.getAction(resultIndex, ResultSetDataAction); @@ -186,11 +191,12 @@ export class DVResultSetGroupingPluginBootstrap extends Bootstrap { icon: '/icons/plugin_data_viewer_result_set_grouping_m.svg', dataFormat: ResultDataFormat.Resultset, hidden: (dataFormat, model, resultIndex) => { - if (!model.source.hasResult(resultIndex)) { + const source = model.source; + if (!source.hasResult(resultIndex) || !isResultSetDataSource(source)) { return true; } - const data = model.source.tryGetAction(resultIndex, ResultSetDataAction); + const data = source.getActionImplementation(resultIndex, DatabaseDataResultAction); return data?.empty ?? true; }, getPresentationComponent: () => DVResultSetGroupingPresentation, diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx index 2c858c1110..2c3f1c2269 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import { s, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { useTabLocalState } from '@cloudbeaver/core-ui'; import { CaptureViewScope } from '@cloudbeaver/core-view'; -import { DataPresentationComponent, IDatabaseResultSet, TableViewerLoader } from '@cloudbeaver/plugin-data-viewer'; +import { DataPresentationComponent, isResultSetDataModel, TableViewerLoader } from '@cloudbeaver/plugin-data-viewer'; import { DEFAULT_GROUPING_QUERY_OPERATION } from './DEFAULT_GROUPING_QUERY_OPERATION'; import styles from './DVResultSetGroupingPresentation.module.css'; @@ -19,10 +19,13 @@ import type { IDVResultSetGroupingPresentationState } from './IDVResultSetGroupi import { useGroupingDataModel } from './useGroupingDataModel'; import { useGroupingDnDColumns } from './useGroupingDnDColumns'; -export const DVResultSetGroupingPresentation: DataPresentationComponent = observer(function DVResultSetGroupingPresentation({ +export const DVResultSetGroupingPresentation: DataPresentationComponent = observer(function DVResultSetGroupingPresentation({ model: originalModel, resultIndex, }) { + if (!isResultSetDataModel(originalModel)) { + throw new Error('DVResultSetGroupingPresentation can only be used with ResultSetDataSource'); + } const state = useTabLocalState(() => ({ presentationId: '', valuePresentationId: null, diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts index d9049ecc56..7310c8e382 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts @@ -10,7 +10,7 @@ import { useEffect } from 'react'; import { useObjectRef, useResource } from '@cloudbeaver/core-blocks'; import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; -import { App, useService } from '@cloudbeaver/core-di'; +import { IServiceProvider, useService } from '@cloudbeaver/core-di'; import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk'; import { isObjectsEqual } from '@cloudbeaver/core-utils'; import { @@ -18,24 +18,24 @@ import { DatabaseDataModel, DataViewerSettingsService, IDatabaseDataModel, - IDatabaseResultSet, + ResultSetDataSource, TableViewerStorageService, } from '@cloudbeaver/plugin-data-viewer'; -import { GroupingDataSource, IDataGroupingOptions } from './GroupingDataSource'; +import { GroupingDataSource } from './GroupingDataSource'; import type { IGroupingQueryState } from './IGroupingQueryState'; export interface IGroupingDataModel { - model: IDatabaseDataModel; + model: IDatabaseDataModel; } export function useGroupingDataModel( - sourceModel: IDatabaseDataModel, + sourceModel: IDatabaseDataModel, sourceResultIndex: number, state: IGroupingQueryState, ): IGroupingDataModel { const tableViewerStorageService = useService(TableViewerStorageService); - const app = useService(App); + const serviceProvider = useService(IServiceProvider); const graphQLService = useService(GraphQLService); const asyncTaskInfoService = useService(AsyncTaskInfoService); const dataViewerSettingsService = useService(DataViewerSettingsService); @@ -49,8 +49,9 @@ export function useGroupingDataModel( const model = useObjectRef( () => { - const source = new GroupingDataSource(app.getServiceInjector(), graphQLService, asyncTaskInfoService); + const source = new GroupingDataSource(serviceProvider, graphQLService, asyncTaskInfoService); + source.setKeepExecutionContextOnDispose(true); const model = tableViewerStorageService.add(new DatabaseDataModel(source)); model.setAccess(DatabaseDataAccessMode.Readonly).setCountGain(dataViewerSettingsService.getDefaultRowsCount()).setSlice(0); @@ -59,7 +60,7 @@ export function useGroupingDataModel( source, model, dispose() { - this.model.dispose(true); + this.model.dispose(); tableViewerStorageService.remove(this.model.id); }, }; @@ -78,7 +79,7 @@ export function useGroupingDataModel( useEffect(() => { const sub = reaction( () => { - const result = sourceModel.source.hasResult(sourceResultIndex) ? sourceModel.source.getResult(sourceResultIndex) : null; + const result = sourceModel.source.getResult(sourceResultIndex); return { columns: state.columns, @@ -90,14 +91,16 @@ export function useGroupingDataModel( async ({ columns, functions, sourceResultId }) => { if (columns.length !== 0 && functions.length !== 0 && sourceResultId) { const executionContext = sourceModel.source.executionContext; - model.model.source.setExecutionContext(executionContext).setSupportedDataFormats(connectionInfo?.supportedDataFormats ?? []); + model.source.setExecutionContext(executionContext).setSupportedDataFormats(connectionInfo?.supportedDataFormats ?? []); const context = executionContext?.context; if (context) { const connectionKey = createConnectionParam(context.projectId, context.connectionId); model.model - .setOptions({ + .setCountGain(dataViewerSettingsService.getDefaultRowsCount()) + .setSlice(0) + .source.setOptions({ query: '', columns, functions, @@ -107,9 +110,7 @@ export function useGroupingDataModel( constraints: [], whereFilter: '', }) - .setCountGain(dataViewerSettingsService.getDefaultRowsCount()) - .setSlice(0) - .source.resetData(); + .resetData(); } } else { model.model @@ -129,7 +130,5 @@ export function useGroupingDataModel( useEffect(() => model.dispose, []); - return { - model: model.model, - }; + return model; } diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDnDColumns.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDnDColumns.ts index 7723c7ecf3..b429d734ee 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDnDColumns.ts +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDnDColumns.ts @@ -11,9 +11,10 @@ import { DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY, IDatabaseDataModel, - IDatabaseResultSet, IResultSetColumnKey, + isResultSetDataSource, ResultSetDataAction, + ResultSetDataSource, } from '@cloudbeaver/plugin-data-viewer'; import type { IGroupingQueryState } from './IGroupingQueryState'; @@ -26,20 +27,15 @@ interface IGroupingQueryResult { export function useGroupingDnDColumns( state: IGroupingQueryState, - sourceModel: IDatabaseDataModel, + sourceModel: IDatabaseDataModel, groupingModel: IGroupingDataModel, ): IGroupingQueryResult { - async function dropItem( - model: IDatabaseDataModel, - resultIndex: number, - columnKey: IResultSetColumnKey | null, - outside: boolean, - ) { + async function dropItem(source: ResultSetDataSource, resultIndex: number, columnKey: IResultSetColumnKey | null, outside: boolean) { if (!columnKey) { return; } - const resultSetDataAction = model.source.getAction(resultIndex, ResultSetDataAction); + const resultSetDataAction = source.getAction(resultIndex, ResultSetDataAction); const name = resultSetDataAction.getColumn(columnKey)?.name; if (!name) { @@ -59,16 +55,16 @@ export function useGroupingDnDColumns( const dndBox = useDNDBox({ canDrop: context => { - const model = context.get(DATA_CONTEXT_DV_DDM); + const model = context.get(DATA_CONTEXT_DV_DDM)!; - return context.has(DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY) && model === sourceModel; + return isResultSetDataSource(model.source) && context.has(DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY) && model === sourceModel; }, onDrop: async context => { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const columnKey = context.get(DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY)!; - dropItem(model, resultIndex, columnKey, false); + dropItem(model.source as unknown as ResultSetDataSource, resultIndex, columnKey, false); }, }); @@ -76,14 +72,14 @@ export function useGroupingDnDColumns( canDrop: context => { const model = context.get(DATA_CONTEXT_DV_DDM); - return context.has(DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY) && model?.id === groupingModel.model.id; + return context.has(DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY) && model?.id === groupingModel.model.id && isResultSetDataSource(model.source); }, onDrop: async context => { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const columnKey = context.get(DATA_CONTEXT_DV_DDM_RS_COLUMN_KEY)!; - dropItem(model, resultIndex, columnKey, true); + dropItem(model.source as unknown as ResultSetDataSource, resultIndex, columnKey, true); }, }); 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 5fe14a0c64..bc213d37a2 100644 --- a/webapp/packages/plugin-data-viewer-result-trace-details/package.json +++ b/webapp/packages/plugin-data-viewer-result-trace-details/package.json @@ -21,7 +21,6 @@ "@cloudbeaver/core-di": "^0", "@cloudbeaver/core-localization": "^0", "@cloudbeaver/core-sdk": "^0", - "@cloudbeaver/core-theming": "^0", "@cloudbeaver/core-utils": "^0", "@cloudbeaver/plugin-data-grid": "^0", "@cloudbeaver/plugin-data-viewer": "^0", @@ -31,7 +30,6 @@ }, "peerDependencies": {}, "devDependencies": { - "@cloudbeaver/core-theming": "^0", "@types/react": "^18", "typescript": "^5", "typescript-plugin-css-modules": "^5" diff --git a/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsBootstrap.ts b/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsBootstrap.ts index eaf8c607f6..1988effedb 100644 --- a/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsBootstrap.ts +++ b/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsBootstrap.ts @@ -28,7 +28,7 @@ export class DVResultTraceDetailsBootstrap extends Bootstrap { icon: '/icons/result_details_sm.svg', title: 'plugin_data_viewer_result_trace_details', hidden(dataFormat, model, resultIndex) { - const result = model.getResult(resultIndex); + const result = model.source.getResult(resultIndex); return !result?.data?.hasDynamicTrace; }, getPresentationComponent: () => DVResultTraceDetailsPresentation, diff --git a/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsPresentation.tsx b/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsPresentation.tsx index efd6f7c547..2bb2d82d9b 100644 --- a/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsPresentation.tsx +++ b/webapp/packages/plugin-data-viewer-result-trace-details/src/DVResultTraceDetailsPresentation.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import { s, TextPlaceholder, useAutoLoad, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { DynamicTraceProperty } from '@cloudbeaver/core-sdk'; import { type Column, DataGrid } from '@cloudbeaver/plugin-data-grid'; -import type { DataPresentationComponent, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer'; +import { type DataPresentationComponent, IDatabaseDataOptions, isResultSetDataModel, isResultSetDataSource } from '@cloudbeaver/plugin-data-viewer'; import classes from './DVResultTraceDetailsPresentation.module.css'; import { HeaderCell } from './ResultTraceDetailsTable/HeaderCell'; @@ -40,22 +40,26 @@ const COLUMNS: Column[] = [ }, ]; -export const DVResultTraceDetailsPresentation: DataPresentationComponent = observer( - function DVResultTraceDetailsPresentation({ model, resultIndex }) { - const translate = useTranslate(); - const styles = useS(classes); - const state = useResultTraceDetails(model, resultIndex); +export const DVResultTraceDetailsPresentation: DataPresentationComponent = observer(function DVResultTraceDetailsPresentation({ + model, + resultIndex, +}) { + if (!isResultSetDataModel(model)) { + throw new Error('DVResultTraceDetailsPresentation can only be used with ResultSetDataSource'); + } + const translate = useTranslate(); + const styles = useS(classes); + const state = useResultTraceDetails(model, resultIndex); - useAutoLoad(DVResultTraceDetailsPresentation, state, undefined, undefined, true); + useAutoLoad(DVResultTraceDetailsPresentation, state, undefined, undefined, true); - if (!state.trace?.length) { - return {translate('plugin_data_viewer_result_trace_no_data_placeholder')}; - } + if (!state.trace?.length) { + return {translate('plugin_data_viewer_result_trace_no_data_placeholder')}; + } - return ( -
- row.name} columns={COLUMNS} rowHeight={30} /> -
- ); - }, -); + return ( +
+ row.name} columns={COLUMNS} rowHeight={30} /> +
+ ); +}); diff --git a/webapp/packages/plugin-data-viewer-result-trace-details/src/useResultTraceDetails.tsx b/webapp/packages/plugin-data-viewer-result-trace-details/src/useResultTraceDetails.tsx index 2ea90909fa..5b3c39e15c 100644 --- a/webapp/packages/plugin-data-viewer-result-trace-details/src/useResultTraceDetails.tsx +++ b/webapp/packages/plugin-data-viewer-result-trace-details/src/useResultTraceDetails.tsx @@ -14,9 +14,9 @@ import { ILoadableState, isContainsException } from '@cloudbeaver/core-utils'; import { DatabaseMetadataAction, IDatabaseDataModel, - IDatabaseResultSet, IResultSetElementKey, ResultSetCacheAction, + ResultSetDataSource, } from '@cloudbeaver/plugin-data-viewer'; import { DVResultTraceDetailsService } from './DVResultTraceDetailsService'; @@ -29,7 +29,7 @@ interface MetadataState { } interface State extends ILoadableState { readonly trace: DynamicTraceProperty[] | undefined; - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; cache: ResultSetCacheAction; metadataState: MetadataState; @@ -44,7 +44,7 @@ const FAKE_ELEMENT_KEY: IResultSetElementKey = { row: { index: Number.MAX_SAFE_INTEGER, subIndex: Number.MAX_SAFE_INTEGER }, }; -export function useResultTraceDetails(model: IDatabaseDataModel, resultIndex: number) { +export function useResultTraceDetails(model: IDatabaseDataModel, resultIndex: number) { const dvResultTraceDetailsService = useService(DVResultTraceDetailsService); const cache = model.source.getAction(resultIndex, ResultSetCacheAction); const metadataAction = model.source.getAction(resultIndex, DatabaseMetadataAction); @@ -74,7 +74,7 @@ export function useResultTraceDetails(model: IDatabaseDataModel { +export interface IDataPresentationProps { dataFormat: ResultDataFormat; - model: IDatabaseDataModel; + model: IDatabaseDataModel; actions: IDataTableActions; resultIndex: number; simple: boolean; @@ -27,9 +26,7 @@ export enum DataPresentationType { toolsPanel, } -export type DataPresentationComponent = React.FunctionComponent< - IDataPresentationProps ->; +export type DataPresentationComponent = React.FunctionComponent; export type PresentationTabProps = TabProps & { presentation: IDataPresentationOptions; diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerDataChangeConfirmationService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerDataChangeConfirmationService.ts index 0ec64adf13..709e17736e 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerDataChangeConfirmationService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerDataChangeConfirmationService.ts @@ -35,7 +35,7 @@ export class DataViewerDataChangeConfirmationService { } } - private async checkUnsavedData({ stage, model }: IRequestEventData, contexts: IExecutionContextProvider>) { + private async checkUnsavedData({ stage, model }: IRequestEventData, contexts: IExecutionContextProvider) { if (stage === 'request') { const confirmationContext = contexts.getContext(SaveConfirmedContext); @@ -43,13 +43,13 @@ export class DataViewerDataChangeConfirmationService { return; } - const results = model.getResults(); + const results = model.source.getResults(); try { for (let resultIndex = 0; resultIndex < results.length; resultIndex++) { const editor = model.source.getActionImplementation(resultIndex, DatabaseEditAction); - if (editor?.isEdited() && model.source.executionContext?.context) { + if (editor?.isEdited() && !model.isDisabled(resultIndex)) { if (confirmationContext.confirmed) { await model.save(); } else { diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerPanel.ts b/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerPanel.ts index fe94244d84..8277447a77 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerPanel.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerPanel.ts @@ -11,6 +11,8 @@ import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; import type { ITab } from '@cloudbeaver/plugin-navigation-tabs'; import type { IObjectViewerTabState } from '@cloudbeaver/plugin-object-viewer'; +import { ContainerDataSource } from '../ContainerDataSource'; +import { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel'; import { DataPresentationService } from '../DataPresentationService'; import { DataViewerDataChangeConfirmationService } from '../DataViewerDataChangeConfirmationService'; import { DataViewerTableService } from '../DataViewerTableService'; @@ -39,9 +41,9 @@ export function useDataViewerPanel(tab: ITab) { return; } - let model = tableViewerStorageService.get(tab.handlerState.tableId || ''); + let model = tableViewerStorageService.get>(tab.handlerState.tableId || ''); - if (model && !model.source.executionContext?.context && model.source.results.length > 0) { + if (model && !model.isDisabled() && model.source.results.length > 0) { model.resetData(); } diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts index 6cefb37dd4..23f38dab6e 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.test.ts @@ -88,8 +88,8 @@ const newSettings = { }; async function setupSettingsService(mockConfig: any = {}) { - const settings = app.injector.getServiceByClass(DataViewerSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(DataViewerSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(mockConfig))); diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts index e4d99cb13f..ed9aac9466 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts @@ -6,15 +6,14 @@ * you may not use this file except in compliance with the License. */ import { Connection, ConnectionExecutionContextService, createConnectionParam } from '@cloudbeaver/core-connections'; -import { App, injectable } from '@cloudbeaver/core-di'; +import { injectable, IServiceProvider } from '@cloudbeaver/core-di'; import { EObjectFeature, NavNode, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk'; -import { ContainerDataSource, IDataContainerOptions } from './ContainerDataSource'; +import { ContainerDataSource } from './ContainerDataSource'; import { DatabaseDataModel } from './DatabaseDataModel/DatabaseDataModel'; import type { IDatabaseDataModel } from './DatabaseDataModel/IDatabaseDataModel'; import { DatabaseDataAccessMode } from './DatabaseDataModel/IDatabaseDataSource'; -import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet'; import { DataViewerService } from './DataViewerService'; import { DataViewerSettingsService } from './DataViewerSettingsService'; import { TableViewerStorageService } from './TableViewer/TableViewerStorageService'; @@ -22,7 +21,7 @@ import { TableViewerStorageService } from './TableViewer/TableViewerStorageServi @injectable() export class DataViewerTableService { constructor( - private readonly app: App, + private readonly serviceProvider: IServiceProvider, private readonly navNodeManagerService: NavNodeManagerService, private readonly tableViewerStorageService: TableViewerStorageService, private readonly graphQLService: GraphQLService, @@ -32,11 +31,11 @@ export class DataViewerTableService { private readonly dataViewerSettingsService: DataViewerSettingsService, ) {} - create(connection: Connection, node: NavNode | undefined): IDatabaseDataModel { + create(connection: Connection, node: NavNode | undefined): IDatabaseDataModel { const nodeInfo = this.navNodeManagerService.getNodeContainerInfo(node?.id ?? ''); const source = new ContainerDataSource( - this.app.getServiceInjector(), + this.serviceProvider, this.graphQLService, this.asyncTaskInfoService, this.connectionExecutionContextService, diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseDataResultAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseDataResultAction.ts index 5299f6c41b..0451104234 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseDataResultAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseDataResultAction.ts @@ -20,6 +20,10 @@ export abstract class DatabaseDataResultAction) { super(source); } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseMetadataAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseMetadataAction.ts index 9f70f7f519..053b4146d3 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseMetadataAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseMetadataAction.ts @@ -9,20 +9,16 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; import { DatabaseDataAction } from '../DatabaseDataAction'; -import type { IDatabaseDataResult } from '../IDatabaseDataResult'; import type { IDatabaseDataSource } from '../IDatabaseDataSource'; import { databaseDataAction } from './DatabaseDataActionDecorator'; import type { IDatabaseDataMetadataAction } from './IDatabaseDataMetadataAction'; @databaseDataAction() -export class DatabaseMetadataAction - extends DatabaseDataAction - implements IDatabaseDataMetadataAction -{ +export class DatabaseMetadataAction extends DatabaseDataAction implements IDatabaseDataMetadataAction { static dataFormat: ResultDataFormat[] | null = null; readonly metadata: MetadataMap; - constructor(source: IDatabaseDataSource) { + constructor(source: IDatabaseDataSource) { super(source); this.metadata = new MetadataMap(); } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts index 78db0e9d6e..098d0a3bb6 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts @@ -15,7 +15,7 @@ import { databaseDataAction } from './DatabaseDataActionDecorator'; import type { DatabaseDataSelectActionsData, IDatabaseDataSelectAction } from './IDatabaseDataSelectAction'; @databaseDataAction() -export abstract class DatabaseSelectAction +export abstract class DatabaseSelectAction extends DatabaseDataAction implements IDatabaseDataSelectAction { @@ -29,9 +29,11 @@ export abstract class DatabaseSelectAction extends IDatabaseDataAction { + readonly empty: boolean; getIdentifier(key: TKey): string; serialize(key: TKey): string; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM.ts index 1076061a0c..b56300d59b 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM.ts @@ -8,6 +8,5 @@ import { createDataContext } from '@cloudbeaver/core-data-context'; import type { IDatabaseDataModel } from '../IDatabaseDataModel'; -import type { IDatabaseDataOptions } from '../IDatabaseDataOptions'; -export const DATA_CONTEXT_DV_DDM = createDataContext>('data-viewer-database-data-model'); +export const DATA_CONTEXT_DV_DDM = createDataContext('data-viewer-database-data-model'); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataAction.ts index 67ea88cb9c..a81dc77c25 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataAction.ts @@ -15,10 +15,6 @@ export abstract class DatabaseDataAction; constructor(source: IDatabaseDataSource) { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts index bfb44b24e3..97b8a566b8 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts @@ -59,7 +59,7 @@ export class DatabaseDataActions if (isDatabaseDataAction(dependency)) { depends.push(this.get(result, dependency)); } else { - depends.push(this.source.serviceInjector.getServiceByClass(dependency as any)); + depends.push(this.source.serviceProvider.getService(dependency as any)); } } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataFormat.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataFormat.ts index 6c06c06462..23889ed711 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataFormat.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataFormat.ts @@ -8,5 +8,5 @@ import type { DatabaseDataModel } from './DatabaseDataModel'; export interface DatabaseDataFormat { - processResult: (model: DatabaseDataModel, data: T) => void; + processResult: (model: DatabaseDataModel, data: T) => void; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts index 0e03a6eda0..fd2ac4af80 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts @@ -12,13 +12,12 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { uuid } from '@cloudbeaver/core-utils'; import type { IDatabaseDataModel, IRequestEventData } from './IDatabaseDataModel'; -import type { IDatabaseDataResult } from './IDatabaseDataResult'; import type { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource'; -export class DatabaseDataModel implements IDatabaseDataModel { +export class DatabaseDataModel = IDatabaseDataSource> implements IDatabaseDataModel { id: string; name: string | null; - source: IDatabaseDataSource; + source: TSource; countGain: number; get requestInfo(): IRequestInfo { @@ -31,9 +30,9 @@ export class DatabaseDataModel>; + readonly onRequest: IExecutor>; - constructor(source: IDatabaseDataSource) { + constructor(source: TSource) { this.id = uuid(); this.name = null; this.source = source; @@ -52,7 +51,7 @@ export class DatabaseDataModel { const contexts = await this.onOptionsChange.execute(); @@ -152,8 +133,8 @@ export class DatabaseDataModel { + async dispose(): Promise { await this.onDispose.execute(); - await this.source.dispose(keepExecutionContext); + await this.source.dispose(); } } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts index faff69d5cd..72fc095062 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts @@ -7,13 +7,12 @@ */ import { action, makeObservable, observable, toJS } from 'mobx'; -import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections'; -import type { IServiceInjector } from '@cloudbeaver/core-di'; +import type { IServiceProvider } from '@cloudbeaver/core-di'; 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 { IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction'; import type { IDatabaseDataActions } from './IDatabaseDataActions'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; import { @@ -37,8 +36,6 @@ export abstract class DatabaseDataSource | null; get canCancel(): boolean { if (this.activeOperation instanceof Task) { @@ -56,7 +53,7 @@ export abstract class DatabaseDataSource Promise; private outdated: boolean; @@ -67,9 +64,8 @@ export abstract class DatabaseDataSource>; - constructor(serviceInjector: IServiceInjector) { - this.serviceInjector = serviceInjector; - this.totalCountRequestTask = null; + constructor(serviceProvider: IServiceProvider) { + this.serviceProvider = serviceProvider; this.actions = new DatabaseDataActions(this); this.access = DatabaseDataAccessMode.Default; this.results = []; @@ -79,9 +75,8 @@ export abstract class DatabaseDataSource>( - resultIndex: number, - action: IDatabaseDataActionClass, - ): T | undefined; - tryGetAction>( - result: TResult, - action: IDatabaseDataActionClass, - ): T | undefined; - tryGetAction>( - resultIndex: number | TResult, - action: IDatabaseDataActionClass, - ): T | undefined { + tryGetAction>(resultIndex: number, action: T): InstanceType | undefined; + tryGetAction>(result: TResult, action: T): InstanceType | undefined; + tryGetAction>(resultIndex: number | TResult, action: T): InstanceType | undefined { if (typeof resultIndex === 'number') { if (!this.hasResult(resultIndex)) { return undefined; @@ -140,12 +124,9 @@ export abstract class DatabaseDataSource>(resultIndex: number, action: IDatabaseDataActionClass): T; - getAction>(result: TResult, action: IDatabaseDataActionClass): T; - getAction>( - resultIndex: number | TResult, - action: IDatabaseDataActionClass, - ): T { + getAction>(resultIndex: number, action: T): InstanceType; + getAction>(result: TResult, action: T): InstanceType; + getAction>(resultIndex: number | TResult, action: T): InstanceType { if (typeof resultIndex === 'number') { if (!this.hasResult(resultIndex)) { throw new Error('Result index out of range'); @@ -156,18 +137,15 @@ export abstract class DatabaseDataSource>( + getActionImplementation>( resultIndex: number, - action: IDatabaseDataActionInterface, - ): T | undefined; - getActionImplementation>( - result: TResult, - action: IDatabaseDataActionInterface, - ): T | undefined; - getActionImplementation>( + action: T, + ): InstanceType | undefined; + getActionImplementation>(result: TResult, action: T): InstanceType | undefined; + getActionImplementation>( resultIndex: number | TResult, - action: IDatabaseDataActionInterface, - ): T | undefined { + action: T, + ): InstanceType | undefined { if (typeof resultIndex === 'number') { if (!this.hasResult(resultIndex)) { return undefined; @@ -196,6 +174,10 @@ export abstract class DatabaseDataSource 1 || !this.executionContext?.context || this.disabled; + return this.access === DatabaseDataAccessMode.Readonly || this.results.length > 1 || this.disabled; } isLoading(): boolean { return !!this.activeOperation; } - isDisabled(resultIndex: number): boolean { - return (!this.getResult(resultIndex)?.data && this.error === null) || !this.executionContext?.context; + isDisabled(resultIndex?: number): boolean { + if (resultIndex === undefined) { + return !this.results.length && this.error === null; + } + return this.hasResult(resultIndex) && !this.getResult(resultIndex)?.data && this.error === null; } setAccess(access: DatabaseDataAccessMode): this { @@ -267,18 +256,8 @@ export abstract class DatabaseDataSource { + async dispose(): Promise { await this.cancel(); - if (!keepExecutionContext) { - await this.executionContext?.destroy(); - } } abstract request(prevResults: TResult[]): Promise; abstract save(prevResults: TResult[]): Promise; - abstract loadTotalCount(resultIndex: number): Promise>; - abstract cancelLoadTotalCount(): Promise | null>; - private async requestDataAction(): Promise { this.prevOptions = toJS(this.options); - return this.request(this.results).then(data => { - this.outdated = false; - - if (data !== null) { - this.setResults(data); - } - return data; - }); + return this.request(this.results) + .finally(() => { + this.outdated = false; + }) + .then(data => { + if (data !== null) { + this.setResults(data); + } + return data; + }); } private async tryExecuteOperation(type: DatabaseDataSourceOperation, operation: () => Promise): Promise { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataAction.ts index 092692a312..189a9ccdad 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataAction.ts @@ -41,7 +41,6 @@ export type IDatabaseDataActionClass< export interface IDatabaseDataAction { readonly source: IDatabaseDataSource; - readonly empty: boolean; result: TResult; resultIndex: number; updateResult: (result: TResult, index: number) => void; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts index 076079940e..5b06576352 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts @@ -8,19 +8,17 @@ import type { IExecutor } from '@cloudbeaver/core-executor'; import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; -import type { IDatabaseDataResult } from './IDatabaseDataResult'; import type { DatabaseDataAccessMode, IDatabaseDataSource, IDatabaseDataSourceOperationEvent, IRequestInfo } from './IDatabaseDataSource'; -export interface IRequestEventData - extends IDatabaseDataSourceOperationEvent { - model: IDatabaseDataModel; +export interface IRequestEventData = IDatabaseDataSource> extends IDatabaseDataSourceOperationEvent { + model: IDatabaseDataModel; } /** Represents an interface for interacting with a database. It is used for managing and requesting data. */ -export interface IDatabaseDataModel { +export interface IDatabaseDataModel = IDatabaseDataSource> { readonly id: string; readonly name: string | null; - readonly source: IDatabaseDataSource; + readonly source: TSource; /** Holds metadata about a data request. */ readonly requestInfo: IRequestInfo; readonly supportedDataFormats: ResultDataFormat[]; @@ -28,22 +26,18 @@ export interface IDatabaseDataModel>; + readonly onRequest: IExecutor>; readonly onDispose: IExecutor; setName: (name: string | null) => this; isReadonly: (resultIndex: number) => boolean; - isDisabled: (resultIndex: number) => boolean; + isDisabled: (resultIndex?: number) => boolean; isLoading: () => boolean; isDataAvailable: (offset: number, count: number) => boolean; - getResults: () => TResult[]; - getResult: (index: number) => TResult | null; - setAccess: (access: DatabaseDataAccessMode) => this; setCountGain: (count: number) => this; setSlice: (offset: number, count?: number) => this; - setOptions: (options: TOptions) => this; setDataFormat: (dataFormat: ResultDataFormat) => this; setSupportedDataFormats: (dataFormats: ResultDataFormat[]) => this; @@ -56,5 +50,5 @@ export interface IDatabaseDataModel Promise; cancel: () => Promise | void; resetData: () => void; - dispose: (keepExecutionContext?: boolean) => Promise; + dispose: () => Promise; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataResult.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataResult.ts index a8c8916ae5..3ce35f7cf9 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataResult.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataResult.ts @@ -10,13 +10,8 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; export interface IDatabaseDataResult { id: string | null; uniqueResultId: string; - projectId: string; - connectionId: string; - contextId: string; dataFormat: ResultDataFormat; loadedFully: boolean; - updateRowCount: number; count: number; - totalCount: number | null; data: any; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts index 29062697c7..6f17e8e53c 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts @@ -5,12 +5,11 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections'; -import type { IServiceInjector } from '@cloudbeaver/core-di'; -import type { IExecutor, ITask } from '@cloudbeaver/core-executor'; +import type { IServiceProvider } from '@cloudbeaver/core-di'; +import type { IExecutor } from '@cloudbeaver/core-executor'; import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; -import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction'; +import type { IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction'; import type { IDatabaseDataActions } from './IDatabaseDataActions'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; @@ -41,7 +40,13 @@ export enum DatabaseDataAccessMode { Readonly, } -export interface IDatabaseDataSource { +export type GetDatabaseDataSourceOptions> = + TSource extends IDatabaseDataSource ? TOptions : never; + +export type GetDatabaseDataSourceResult> = + TSource extends IDatabaseDataSource ? TResult : never; + +export interface IDatabaseDataSource { readonly access: DatabaseDataAccessMode; readonly dataFormat: ResultDataFormat; readonly supportedDataFormats: ResultDataFormat[]; @@ -56,39 +61,33 @@ export interface IDatabaseDataSource | null; + readonly serviceProvider: IServiceProvider; readonly onOperation: IExecutor; + isError: () => boolean; isOutdated: () => boolean; isLoadable: () => boolean; isReadonly: (resultIndex: number) => boolean; isDataAvailable: (offset: number, count: number) => boolean; isLoading: () => boolean; - isDisabled: (resultIndex: number) => boolean; + isDisabled: (resultIndex?: number) => boolean; hasResult: (resultIndex: number) => boolean; - tryGetAction: (>( + tryGetAction: (>(resultIndex: number, action: T) => InstanceType | undefined) & + (>(result: TResult, action: T) => InstanceType | undefined); + getAction: (>(resultIndex: number, action: T) => InstanceType) & + (>(result: TResult, action: T) => InstanceType); + getActionImplementation: (>( resultIndex: number, - action: IDatabaseDataActionClass, - ) => T | undefined) & - (>(result: TResult, action: IDatabaseDataActionClass) => T | undefined); - getAction: (>(resultIndex: number, action: IDatabaseDataActionClass) => T) & - (>(result: TResult, action: IDatabaseDataActionClass) => T); - getActionImplementation: (>( - resultIndex: number, - action: IDatabaseDataActionInterface, - ) => T | undefined) & - (>( - result: TResult, - action: IDatabaseDataActionInterface, - ) => T | undefined); + action: T, + ) => InstanceType | undefined) & + (>(result: TResult, action: T) => InstanceType | undefined); getResult: (index: number) => TResult | null; + getResults: () => TResult[]; setOutdated: () => this; setResults: (results: TResult[]) => this; @@ -97,11 +96,6 @@ export interface IDatabaseDataSource this; setDataFormat: (dataFormat: ResultDataFormat) => 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; /** @@ -116,7 +110,8 @@ export interface IDatabaseDataSource Promise; cancel: () => Promise; clearError: () => this; + setError: (error: Error) => this; resetData: () => this; canSafelyDispose: () => Promise; - dispose: (keepExecutionContext?: boolean) => Promise; + dispose: () => Promise; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseResultSet.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseResultSet.ts index 58cb9c2c85..db3c9a8aac 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseResultSet.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseResultSet.ts @@ -10,5 +10,10 @@ import type { SqlResultSet } from '@cloudbeaver/core-sdk'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; export interface IDatabaseResultSet extends IDatabaseDataResult { + totalCount: number | null; + updateRowCount: number; + projectId: string; + connectionId: string; + contextId: string; data: SqlResultSet | undefined; } diff --git a/webapp/packages/plugin-data-viewer/src/IDataViewerTableStorage.ts b/webapp/packages/plugin-data-viewer/src/IDataViewerTableStorage.ts index e0ee8ba463..cbbdb8ce81 100644 --- a/webapp/packages/plugin-data-viewer/src/IDataViewerTableStorage.ts +++ b/webapp/packages/plugin-data-viewer/src/IDataViewerTableStorage.ts @@ -6,11 +6,11 @@ * you may not use this file except in compliance with the License. */ import type { IDatabaseDataModel } from './DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseDataResult } from './DatabaseDataModel/IDatabaseDataResult'; +import { IDatabaseDataSource } from './DatabaseDataModel/IDatabaseDataSource'; export interface IDataViewerTableStorage { has(tableId: string): boolean; - get>(tableId: string): T | undefined; - add(model: IDatabaseDataModel): IDatabaseDataModel; + get = IDatabaseDataModel>(tableId: string): T | undefined; + add = IDatabaseDataSource>(model: IDatabaseDataModel): IDatabaseDataModel; remove(tableId: string): void; } diff --git a/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts index ebc6142d96..f9ec52fb74 100644 --- a/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts @@ -5,25 +5,40 @@ * 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 { makeObservable, observable } from 'mobx'; + +import type { IConnectionExecutionContext, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; +import type { IServiceProvider } 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 { IDatabaseDataOptions } from '../DatabaseDataModel/IDatabaseDataOptions'; import type { IDatabaseResultSet } from '../DatabaseDataModel/IDatabaseResultSet'; -export abstract class ResultSetDataSource extends DatabaseDataSource { +export abstract class ResultSetDataSource extends DatabaseDataSource { + executionContext: IConnectionExecutionContext | null; + totalCountRequestTask: ITask | null; + private keepExecutionContextOnDispose: boolean; + constructor( - readonly serviceInjector: IServiceInjector, + readonly serviceProvider: IServiceProvider, protected graphQLService: GraphQLService, protected asyncTaskInfoService: AsyncTaskInfoService, ) { - super(serviceInjector); + super(serviceProvider); + this.totalCountRequestTask = null; + this.executionContext = null; + this.keepExecutionContextOnDispose = false; + + makeObservable(this, { + totalCountRequestTask: observable.ref, + executionContext: observable, + }); } isReadonly(resultIndex: number): boolean { - return super.isReadonly(resultIndex) || this.getResult(resultIndex)?.data?.hasRowIdentifier === false; + return super.isReadonly(resultIndex) || !this.executionContext?.context || this.getResult(resultIndex)?.data?.hasRowIdentifier === false; } async cancel(): Promise { @@ -87,11 +102,24 @@ export abstract class ResultSetDataSource extends DatabaseDataSource { - if (keepExecutionContext) { + async dispose(): Promise { + await super.dispose(); + if (this.keepExecutionContextOnDispose) { await this.closeResults(this.results); + } else { + await this.executionContext?.destroy(); } - return super.dispose(keepExecutionContext); + } + + setKeepExecutionContextOnDispose(keep: boolean): this { + this.keepExecutionContextOnDispose = keep; + return this; + } + + setExecutionContext(context: IConnectionExecutionContext | null): this { + this.executionContext = context; + this.setOutdated(); + return this; } protected getPreviousResultId(prevResults: IDatabaseResultSet[], context: IConnectionExecutionContextInfo) { @@ -109,6 +137,15 @@ export abstract class ResultSetDataSource extends DatabaseDataSource { if (!this.executionContext?.context) { return; @@ -132,3 +169,7 @@ export abstract class ResultSetDataSource extends DatabaseDataSource(dataSource: any): dataSource is ResultSetDataSource { + return dataSource instanceof ResultSetDataSource; +} diff --git a/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetTableFooterMenuService.ts b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetTableFooterMenuService.ts index e569c008df..7f9f4d9f02 100644 --- a/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetTableFooterMenuService.ts +++ b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetTableFooterMenuService.ts @@ -8,14 +8,18 @@ import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { ActionService, menuExtractItems, MenuService } from '@cloudbeaver/core-view'; +import { ActionService, MenuService } from '@cloudbeaver/core-view'; import { DatabaseDataConstraintAction } from '../DatabaseDataModel/Actions/DatabaseDataConstraintAction'; import { DatabaseMetadataAction } from '../DatabaseDataModel/Actions/DatabaseMetadataAction'; import { DATA_CONTEXT_DV_DDM } from '../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM'; import { DATA_CONTEXT_DV_DDM_RESULT_INDEX } from '../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM_RESULT_INDEX'; +import { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel'; +import { IDatabaseDataOptions } from '../DatabaseDataModel/IDatabaseDataOptions'; import { DATA_VIEWER_DATA_MODEL_ACTIONS_MENU } from '../TableViewer/TableFooter/TableFooterMenu/DATA_VIEWER_DATA_MODEL_ACTIONS_MENU'; import { ACTION_COUNT_TOTAL_ELEMENTS } from './ACTION_COUNT_TOTAL_ELEMENTS'; +import { isResultSetDataModel } from './isResultSetDataModel'; +import { ResultSetDataSource } from './ResultSetDataSource'; interface IResultSetActionsMetadata { totalCount: { @@ -38,7 +42,7 @@ export class ResultSetTableFooterMenuService { isApplicable(context) { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - const result = model.getResult(resultIndex); + const result = model.source.getResult(resultIndex); return !!result; }, @@ -52,9 +56,14 @@ export class ResultSetTableFooterMenuService { actions: [ACTION_COUNT_TOTAL_ELEMENTS], contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], isActionApplicable(context, action) { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as any; + + if (!isResultSetDataModel(model)) { + return false; + } + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - const result = model.getResult(resultIndex); + const result = model.source.getResult(resultIndex); if (!result) { return false; @@ -69,10 +78,10 @@ export class ResultSetTableFooterMenuService { return true; }, isDisabled: (context, action) => { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - if (model.isLoading() || model.isDisabled(resultIndex) || !model.getResult(resultIndex)) { + if (model.isLoading() || model.isDisabled(resultIndex) || !model.source.getResult(resultIndex)) { return true; } @@ -98,13 +107,13 @@ export class ResultSetTableFooterMenuService { return false; }, getActionInfo: (context, action) => { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const metadata = this.getState(context); switch (action) { case ACTION_COUNT_TOTAL_ELEMENTS: { - const result = model.getResult(resultIndex); + const result = model.source.getResult(resultIndex); if (!result) { return action.info; } @@ -128,7 +137,7 @@ export class ResultSetTableFooterMenuService { return action.info; }, handler: async (context, action) => { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const metadata = this.getState(context); diff --git a/webapp/packages/plugin-data-viewer/src/ResultSet/isResultSetDataModel.ts b/webapp/packages/plugin-data-viewer/src/ResultSet/isResultSetDataModel.ts new file mode 100644 index 0000000000..1b25f9fb3e --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/ResultSet/isResultSetDataModel.ts @@ -0,0 +1,17 @@ +/* + * 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 { DatabaseDataModel } from '../DatabaseDataModel/DatabaseDataModel'; +import { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel'; +import { IDatabaseDataOptions } from '../DatabaseDataModel/IDatabaseDataOptions'; +import { ResultSetDataSource } from './ResultSetDataSource'; + +export function isResultSetDataModel( + dataModel: IDatabaseDataModel | undefined | null, +): dataModel is IDatabaseDataModel> { + return dataModel instanceof DatabaseDataModel && dataModel.source instanceof ResultSetDataSource; +} diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/IDataTableActions.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/IDataTableActions.ts index c361c13807..02bc2ee970 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/IDataTableActions.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/IDataTableActions.ts @@ -12,7 +12,7 @@ import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel export interface IDataTableActions { presentationId: string | undefined; valuePresentationId: string | null | undefined; - dataModel: IDatabaseDataModel | undefined; + dataModel: IDatabaseDataModel | undefined; setPresentation: (id: string) => void; setValuePresentation: (id: string | null) => void; diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx index ccac876d24..cc2f7b0373 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx @@ -15,7 +15,7 @@ import { TableFooterMenu } from './TableFooterMenu/TableFooterMenu'; interface Props { resultIndex: number; - model: IDatabaseDataModel; + model: IDatabaseDataModel; simple: boolean; } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx index bd3092ca3f..dad9253b68 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { Container, CRegistry, s, useS } from '@cloudbeaver/core-blocks'; +import { CRegistry, s, useS } from '@cloudbeaver/core-blocks'; import { useDataContextLink } from '@cloudbeaver/core-data-context'; import { MenuBar, MenuBarItemStyles, MenuBarStyles } from '@cloudbeaver/core-ui'; import { useMenu } from '@cloudbeaver/core-view'; @@ -21,7 +21,7 @@ import { REFRESH_MENU_ITEM_REGISTRY } from './RefreshAction/RefreshMenuAction'; interface Props { resultIndex: number; - model: IDatabaseDataModel; + model: IDatabaseDataModel; simple: boolean; className?: string; } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts index c084a7db95..29a284252b 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts @@ -89,7 +89,7 @@ export class TableFooterMenuService { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - if (model.isLoading() || model.isDisabled(resultIndex) || !model.getResult(resultIndex)) { + if (model.isLoading() || model.isDisabled(resultIndex) || !model.source.getResult(resultIndex)) { return true; } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx index bbadcaf9e9..188e265238 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx @@ -15,7 +15,7 @@ import classes from './TableHeader.module.css'; import { TableHeaderService } from './TableHeaderService'; interface Props { - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; simple: boolean; className?: string; diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts index 93cf38a5f6..5bcc9b2c96 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts @@ -16,6 +16,8 @@ import { DATA_VIEWER_CONSTRAINTS_DELETE_ACTION } from '../../DatabaseDataModel/A import { DATA_CONTEXT_DV_DDM } from '../../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM'; import { DATA_CONTEXT_DV_DDM_RESULT_INDEX } from '../../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM_RESULT_INDEX'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; +import { IDatabaseDataOptions } from '../../DatabaseDataModel/IDatabaseDataOptions'; +import { isResultSetDataSource, ResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; import { DATA_VIEWER_DATA_MODEL_TOOLS_MENU } from './DATA_VIEWER_DATA_MODEL_TOOLS_MENU'; export const TableWhereFilter = React.lazy(async () => { @@ -28,7 +30,7 @@ export const TableHeaderMenu = React.lazy(async () => { }); export interface ITableHeaderPlaceholderProps { - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; simple: boolean; } @@ -52,9 +54,10 @@ export class TableHeaderService extends Bootstrap { id: 'table-header-menu-base-handler', contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], isActionApplicable(context) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; const menu = context.hasValue(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_TOOLS_MENU); - if (!menu) { + if (!menu || !isResultSetDataSource(model.source)) { return false; } @@ -63,7 +66,7 @@ export class TableHeaderService extends Bootstrap { handler: async (context, action) => { switch (action) { case DATA_VIEWER_CONSTRAINTS_DELETE_ACTION: { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const constraints = model.source.tryGetAction(resultIndex, DatabaseDataConstraintAction); @@ -83,7 +86,7 @@ export class TableHeaderService extends Bootstrap { return action.info; }, isDisabled: (context, action) => { - const model = context.get(DATA_CONTEXT_DV_DDM)!; + const model = context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; if (model.isLoading() || model.isDisabled(resultIndex)) { diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx index d2352ebb58..c1329f59a2 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx @@ -18,6 +18,10 @@ export const TableWhereFilter: PlaceholderComponent ; + model: IDatabaseDataModel; resultIndex: number; + readonly supported: boolean; readonly filter: string; readonly constraints: DatabaseDataConstraintAction | null; readonly disabled: boolean; @@ -24,32 +27,42 @@ interface IState { apply: () => Promise; } -export function useWhereFilter(model: IDatabaseDataModel, resultIndex: number): Readonly { +export function useWhereFilter(model: IDatabaseDataModel, resultIndex: number): Readonly { return useObservableRef( () => ({ + get supported() { + return isResultSetDataSource(this.model.source); + }, get filter() { + const source = this.model.source; + if (!isResultSetDataSource(source)) { + return ''; + } + if (this.constraints?.filterConstraints.length && this.model.source.requestInfo.requestFilter) { return this.model.requestInfo.requestFilter; } - return this.model.source.options?.whereFilter ?? ''; + return source.options?.whereFilter ?? ''; }, get constraints() { - if (!this.model.source.hasResult(this.resultIndex)) { + const model = this.model as any; + if (!model.source.hasResult(this.resultIndex) || !isResultSetDataModel(model)) { return null; } - return this.model.source.tryGetAction(this.resultIndex, DatabaseDataConstraintAction) ?? null; + return model.source.tryGetAction(this.resultIndex, DatabaseDataConstraintAction) ?? null; }, get disabled() { const supported = this.constraints?.supported ?? false; return !supported || this.model.isLoading() || this.model.isDisabled(resultIndex); }, get applicableFilter() { - return ( - this.model.source.prevOptions?.whereFilter !== this.model.source.options?.whereFilter || - this.model.source.options?.whereFilter !== this.model.source.requestInfo.requestFilter - ); + const source = this.model.source; + if (!isResultSetDataSource(source)) { + return false; + } + return source.prevOptions?.whereFilter !== source.options?.whereFilter || source.options?.whereFilter !== source.requestInfo.requestFilter; }, set(value: string) { if (!this.constraints) { diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableStatistics.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableStatistics.tsx index a5eecd56f4..fa17457cbd 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableStatistics.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableStatistics.tsx @@ -10,6 +10,8 @@ import { observer } from 'mobx-react-lite'; import { s, useS, useTranslate } from '@cloudbeaver/core-blocks'; import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel'; +import { IDatabaseResultSet } from '../DatabaseDataModel/IDatabaseResultSet'; +import { isResultSetDataSource } from '../ResultSet/ResultSetDataSource'; import classes from './TableStatistics.module.css'; interface Props { @@ -21,7 +23,12 @@ export const TableStatistics = observer(function TableStatistics({ model, const styles = useS(classes); const translate = useTranslate(); const source = model.source; - const result = model.getResult(resultIndex); + let updatedRows: number | null = null; + + if (isResultSetDataSource(source)) { + const result = source.getResult(resultIndex) as IDatabaseResultSet | null; + updatedRows = result?.updateRowCount ?? null; + } return (
@@ -29,8 +36,12 @@ export const TableStatistics = observer(function TableStatistics({ model,
{translate('data_viewer_statistics_duration')} {source.requestInfo.requestDuration} ms
- {translate('data_viewer_statistics_updated_rows')} {result?.updateRowCount || 0} -
+ {updatedRows !== null && ( + <> + {translate('data_viewer_statistics_updated_rows')} {updatedRows} +
+ + )}
{source.requestInfo.source}
diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableToolsPanel.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableToolsPanel.tsx index 4b282c6e72..b660a69a23 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableToolsPanel.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableToolsPanel.tsx @@ -16,7 +16,7 @@ import type { IDataTableActions } from './IDataTableActions'; import styles from './TableToolsPanel.module.css'; interface Props { - model: IDatabaseDataModel; + model: IDatabaseDataModel; actions: IDataTableActions; dataFormat: ResultDataFormat; presentation: IDataPresentationOptions | null; @@ -28,7 +28,7 @@ export const TableToolsPanel = observer(function TableToolsPanel({ model, const translate = useTranslate(); const style = useS(styles); - const result = model.getResult(resultIndex); + const result = model.source.getResult(resultIndex); if (!presentation || (presentation.dataFormat !== undefined && dataFormat !== presentation.dataFormat)) { if (model.isLoading()) { diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx index 245df0b205..470431cd37 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx @@ -28,7 +28,9 @@ import { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { CaptureView } from '@cloudbeaver/core-view'; import { DatabaseDataConstraintAction } from '../DatabaseDataModel/Actions/DatabaseDataConstraintAction'; +import { IDatabaseDataOptions } from '../DatabaseDataModel/IDatabaseDataOptions'; import { DataPresentationService, DataPresentationType } from '../DataPresentationService'; +import { isResultSetDataModel } from '../ResultSet/isResultSetDataModel'; import { DataPresentation } from './DataPresentation'; import { DataViewerViewService } from './DataViewerViewService'; import type { IDataTableActionsPrivate } from './IDataTableActions'; @@ -63,13 +65,17 @@ export const TableViewer = observer( const dataPresentationService = useService(DataPresentationService); const tableViewerStorageService = useService(TableViewerStorageService); const dataModel = tableViewerStorageService.get(tableId); - const result = dataModel?.getResult(resultIndex); + const result = dataModel?.source.getResult(resultIndex); const loading = dataModel?.isLoading() ?? true; const dataFormat = result?.dataFormat || ResultDataFormat.Resultset; const splitState = useSplitUserState('table-viewer'); const localActions = useObjectRef({ clearConstraints() { + if (!isResultSetDataModel(dataModel)) { + return; + } + const constraints = dataModel?.source.tryGetAction(resultIndex, DatabaseDataConstraintAction); if (constraints) { @@ -146,7 +152,7 @@ export const TableViewer = observer( ['setPresentation', 'setValuePresentation', 'switchValuePresentation', 'closeValuePresentation'], ); - const needRefresh = getComputed(() => dataModel?.isDisabled(resultIndex) && dataModel.source.isOutdated() && dataModel.source.isLoadable()); + const needRefresh = getComputed(() => !dataModel?.isDisabled(resultIndex) && dataModel?.source.isOutdated() && dataModel.source.isLoadable()); useEffect(() => { if (needRefresh) { @@ -169,7 +175,7 @@ export const TableViewer = observer( // }, [dataFormat]); if (!dataModel) { - return ; + return {translate('plugin_data_viewer_no_available_presentation')}; } const presentation = dataPresentationService.getSupported(DataPresentationType.main, dataFormat, presentationId, dataModel, resultIndex); @@ -229,7 +235,7 @@ export const TableViewer = observer( presentation={presentation} resultIndex={resultIndex} simple={simple} - isStatistics={isStatistics} + isStatistics={!!isStatistics} /> diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerStorageService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerStorageService.ts index f2a3892bea..5362c9505e 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerStorageService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerStorageService.ts @@ -11,20 +11,20 @@ import { injectable } from '@cloudbeaver/core-di'; import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseDataResult } from '../DatabaseDataModel/IDatabaseDataResult'; +import { IDatabaseDataSource } from '../DatabaseDataModel/IDatabaseDataSource'; import type { IDataViewerTableStorage } from '../IDataViewerTableStorage'; export interface ITableViewerStorageChangeEventData { type: 'add' | 'remove'; - model: IDatabaseDataModel; + model: IDatabaseDataModel; } @injectable() export class TableViewerStorageService implements IDataViewerTableStorage { readonly onChange: ISyncExecutor; - private readonly tableModelMap: Map> = new Map(); + private readonly tableModelMap: Map> = new Map(); - get values(): Array> { + get values(): Array> { return Array.from(this.tableModelMap.values()); } @@ -41,11 +41,11 @@ export class TableViewerStorageService implements IDataViewerTableStorage { return this.tableModelMap.has(tableId); } - get>(tableId: string): T | undefined { + get = IDatabaseDataModel>(tableId: string): T | undefined { return this.tableModelMap.get(tableId) as any; } - add(model: IDatabaseDataModel): IDatabaseDataModel { + add = IDatabaseDataSource>(model: IDatabaseDataModel): IDatabaseDataModel { if (this.tableModelMap.has(model.id)) { return model; } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelBootstrap.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelBootstrap.ts index c9a0afd66c..487c7114b4 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelBootstrap.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelBootstrap.ts @@ -9,7 +9,7 @@ import React from 'react'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { ResultSetDataAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataAction'; +import { DatabaseDataResultAction } from '../../DatabaseDataModel/Actions/DatabaseDataResultAction'; import { DataPresentationService, DataPresentationType } from '../../DataPresentationService'; import { DataValuePanelService } from './DataValuePanelService'; @@ -20,7 +20,10 @@ export const ValuePanel = React.lazy(async () => { @injectable() export class DataValuePanelBootstrap extends Bootstrap { - constructor(private readonly dataPresentationService: DataPresentationService, private readonly dataValuePanelService: DataValuePanelService) { + constructor( + private readonly dataPresentationService: DataPresentationService, + private readonly dataValuePanelService: DataValuePanelService, + ) { super(); } @@ -35,12 +38,10 @@ export class DataValuePanelBootstrap extends Bootstrap { return true; } - const data = model.source.tryGetAction(resultIndex, ResultSetDataAction); + const data = model.source.getActionImplementation(resultIndex, DatabaseDataResultAction); return data?.empty || this.dataValuePanelService.getDisplayed({ model, resultIndex, dataFormat }).length === 0; }, getPresentationComponent: () => ValuePanel, }); } - - load(): void {} } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelService.ts index a615e5001e..b05339a072 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/DataValuePanelService.ts @@ -10,31 +10,30 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { ITabInfo, ITabInfoOptions, TabsContainer } from '@cloudbeaver/core-ui'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseDataResult } from '../../DatabaseDataModel/IDatabaseDataResult'; export interface IDataValuePanelOptions { dataFormat: ResultDataFormat[]; } -export interface IDataValuePanelProps { +export interface IDataValuePanelProps { dataFormat: ResultDataFormat | null; - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; } @injectable() export class DataValuePanelService { - readonly tabs: TabsContainer, IDataValuePanelOptions>; + readonly tabs: TabsContainer; constructor() { this.tabs = new TabsContainer('Value Panel'); } - get(tabId: string): ITabInfo, IDataValuePanelOptions> | undefined { + get(tabId: string): ITabInfo | undefined { return this.tabs.getTabInfo(tabId); } - getDisplayed(props?: IDataValuePanelProps): Array, IDataValuePanelOptions>> { + getDisplayed(props?: IDataValuePanelProps): Array> { return this.tabs.tabInfoList.filter( info => (props?.dataFormat === undefined || props.dataFormat === null || info.options?.dataFormat.includes(props.dataFormat)) && @@ -42,7 +41,7 @@ export class DataValuePanelService { ); } - add(tabInfo: ITabInfoOptions, IDataValuePanelOptions>): void { + add(tabInfo: ITabInfoOptions): void { this.tabs.add(tabInfo); } } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/ValuePanel.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/ValuePanel.tsx index a210b9d7ca..f5f2c86b01 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/ValuePanel.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/ValuePanel/ValuePanel.tsx @@ -16,7 +16,6 @@ import { MetadataMap } from '@cloudbeaver/core-utils'; import { DatabaseDataResultAction } from '../../DatabaseDataModel/Actions/DatabaseDataResultAction'; import { DatabaseMetadataAction } from '../../DatabaseDataModel/Actions/DatabaseMetadataAction'; import { DatabaseSelectAction } from '../../DatabaseDataModel/Actions/DatabaseSelectAction'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; import type { DataPresentationComponent } from '../../DataPresentationService'; import { DataValuePanelService } from './DataValuePanelService'; import styles from './shared/ValuePanel.module.css'; @@ -31,7 +30,7 @@ const tabPanelListRegistry: StyleRegistry = [ [TabPanelStyles, { mode: 'append', styles: [ValuePanelEditorTabPanel] }], ]; -export const ValuePanel: DataPresentationComponent = observer(function ValuePanel({ dataFormat, model, resultIndex }) { +export const ValuePanel: DataPresentationComponent = observer(function ValuePanel({ dataFormat, model, resultIndex }) { const service = useService(DataValuePanelService); const selectAction = model.source.getActionImplementation(resultIndex, DatabaseSelectAction); const dataResultAction = model.source.getActionImplementation(resultIndex, DatabaseDataResultAction); diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx index 5d97727c6e..9e94b85561 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx @@ -15,73 +15,78 @@ import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/R import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; +import { isResultSetDataModel } from '../../ResultSet/isResultSetDataModel'; import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService'; import classes from './BooleanValuePresentation.module.css'; import { preprocessBooleanValue } from './preprocessBooleanValue'; -export const BooleanValuePresentation: TabContainerPanelComponent> = observer( - function BooleanValuePresentation({ model, resultIndex }) { - const translate = useTranslate(); +export const BooleanValuePresentation: TabContainerPanelComponent = observer(function BooleanValuePresentation({ + model: unknownModel, + resultIndex, +}) { + const model = unknownModel as any; + if (!isResultSetDataModel(model)) { + throw new Error('BooleanValuePresentation can be used only with ResultSetDataSource'); + } + const translate = useTranslate(); - const selectAction = model.source.getAction(resultIndex, ResultSetSelectAction); - const viewAction = model.source.getAction(resultIndex, ResultSetViewAction); - const editAction = model.source.getAction(resultIndex, ResultSetEditAction); - const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction); + const selectAction = model.source.getAction(resultIndex, ResultSetSelectAction); + const viewAction = model.source.getAction(resultIndex, ResultSetViewAction); + const editAction = model.source.getAction(resultIndex, ResultSetEditAction); + const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction); - const activeElements = selectAction.getActiveElements(); + const activeElements = selectAction.getActiveElements(); - if (activeElements.length === 0) { - return {translate('data_viewer_presentation_value_no_active_elements')}; - } + if (activeElements.length === 0) { + return {translate('data_viewer_presentation_value_no_active_elements')}; + } - const firstSelectedCell = activeElements[0]; - const cellValue = viewAction.getCellValue(firstSelectedCell); - const value = preprocessBooleanValue(cellValue); + const firstSelectedCell = activeElements[0]; + const cellValue = viewAction.getCellValue(firstSelectedCell); + const value = preprocessBooleanValue(cellValue); - if (!isDefined(value)) { - return {translate('data_viewer_presentation_value_boolean_placeholder')}; - } + if (!isDefined(value)) { + return {translate('data_viewer_presentation_value_boolean_placeholder')}; + } - const column = viewAction.getColumn(firstSelectedCell.column); - const nullable = column?.required === false; - const readonly = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || formatAction.isReadOnly(firstSelectedCell); + const column = viewAction.getColumn(firstSelectedCell.column); + const nullable = column?.required === false; + const readonly = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || formatAction.isReadOnly(firstSelectedCell); - return ( -
+ return ( +
+ editAction.set(firstSelectedCell, true)} + > + TRUE + + editAction.set(firstSelectedCell, false)} + > + FALSE + + {nullable && ( editAction.set(firstSelectedCell, true)} + onClick={() => editAction.set(firstSelectedCell, null)} > - TRUE + NULL - editAction.set(firstSelectedCell, false)} - > - FALSE - - {nullable && ( - editAction.set(firstSelectedCell, null)} - > - NULL - - )} -
- ); - }, -); + )} +
+ ); +}); diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentationBootstrap.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentationBootstrap.ts index b12d9126aa..feccb0fe5c 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentationBootstrap.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentationBootstrap.ts @@ -5,15 +5,18 @@ * 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 { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; +import { isResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; import { DataValuePanelService } from '../../TableViewer/ValuePanel/DataValuePanelService'; -import { BooleanValuePresentation } from './BooleanValuePresentation'; import { isBooleanValuePresentationAvailable } from './isBooleanValuePresentationAvailable'; +const BooleanValuePresentation = importLazyComponent(() => import('./BooleanValuePresentation').then(module => module.BooleanValuePresentation)); + @injectable() export class BooleanValuePresentationBootstrap extends Bootstrap { constructor(private readonly dataValuePanelService: DataValuePanelService) { @@ -23,21 +26,24 @@ export class BooleanValuePresentationBootstrap extends Bootstrap { register(): void { this.dataValuePanelService.add({ key: 'boolean-presentation', - options: { dataFormat: [ResultDataFormat.Resultset] }, + options: { + dataFormat: [ResultDataFormat.Resultset], + }, name: 'boolean', order: 1, panel: () => BooleanValuePresentation, isHidden: (_, context) => { - if (!context || !context.model.source.hasResult(context.resultIndex)) { + const source = context?.model.source as any; + if (!context || !isResultSetDataSource(source) || !source?.hasResult(context.resultIndex)) { return true; } - const selection = context.model.source.getAction(context.resultIndex, ResultSetSelectAction); + const selection = source.getAction(context.resultIndex, ResultSetSelectAction); const activeElements = selection.getActiveElements(); if (activeElements.length > 0) { - const view = context.model.source.getAction(context.resultIndex, ResultSetViewAction); + const view = source.getAction(context.resultIndex, ResultSetViewAction); const firstSelectedCell = activeElements[0]; const cellValue = view.getCellValue(firstSelectedCell); const column = view.getColumn(firstSelectedCell.column); @@ -49,6 +55,4 @@ export class BooleanValuePresentationBootstrap extends Bootstrap { }, }); } - - load(): void {} } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx index 4e89471c42..bdbab40fd9 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx @@ -14,99 +14,103 @@ import { type TabContainerPanelComponent, useTabLocalState } from '@cloudbeaver/ import { blobToBase64, bytesToSize, throttle } from '@cloudbeaver/core-utils'; import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; +import { isResultSetDataModel } from '../../ResultSet/isResultSetDataModel'; import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService'; import { QuotaPlaceholder } from '../QuotaPlaceholder'; import styles from './ImageValuePresentation.module.css'; import { useValuePanelImageValue } from './useValuePanelImageValue'; -export const ImageValuePresentation: TabContainerPanelComponent> = observer( - function ImageValuePresentation({ model, resultIndex }) { - const translate = useTranslate(); - const suspense = useSuspense(); - const style = useS(styles); - const state = useTabLocalState(() => - observable( - { - stretch: false, - toggleStretch() { - this.stretch = !this.stretch; - }, - }, - { - stretch: observable.ref, - toggleStretch: action.bound, +export const ImageValuePresentation: TabContainerPanelComponent = observer(function ImageValuePresentation({ + model, + resultIndex, +}) { + if (!isResultSetDataModel(model)) { + throw new Error('ImageValuePresentation can be used only with ResultSetDataSource'); + } + const translate = useTranslate(); + const suspense = useSuspense(); + const style = useS(styles); + const state = useTabLocalState(() => + observable( + { + stretch: false, + toggleStretch() { + this.stretch = !this.stretch; }, - ), - ); - const data = useValuePanelImageValue({ model, resultIndex }); - const loading = model.isLoading(); - const valueSize = bytesToSize(isResultSetContentValue(data.cellValue) ? data.cellValue.contentLength ?? 0 : 0); - const isTruncatedMessageDisplay = !!data.truncated && !data.src; - const isDownloadable = isTruncatedMessageDisplay && !!data.selectedCell && data.contentAction.isDownloadable(data.selectedCell); - const isCacheDownloading = isDownloadable && data.contentAction.isLoading(data.selectedCell); - const debouncedDownload = useMemo(() => throttle(() => data.download(), 1000, false), []); - const srcGetter = suspense.observedValue( - 'src', - () => data.src, - async src => { - if (src instanceof Blob) { - return await blobToBase64(src); - } - return src; }, - ); - - function imageContextMenuHandler(event: React.MouseEvent) { - if (!data.canSave) { - event.preventDefault(); + { + stretch: observable.ref, + toggleStretch: action.bound, + }, + ), + ); + const data = useValuePanelImageValue({ model, resultIndex }); + const loading = model.isLoading(); + const valueSize = bytesToSize(isResultSetContentValue(data.cellValue) ? data.cellValue.contentLength ?? 0 : 0); + const isTruncatedMessageDisplay = !!data.truncated && !data.src; + const isDownloadable = isTruncatedMessageDisplay && !!data.selectedCell && data.contentAction.isDownloadable(data.selectedCell); + const isCacheDownloading = isDownloadable && data.contentAction.isLoading(data.selectedCell); + const debouncedDownload = useMemo(() => throttle(() => data.download(), 1000, false), []); + const srcGetter = suspense.observedValue( + 'src', + () => data.src, + async src => { + if (src instanceof Blob) { + return await blobToBase64(src); } + return src; + }, + ); + + function imageContextMenuHandler(event: React.MouseEvent) { + if (!data.canSave) { + event.preventDefault(); } + } - return ( - - - - {data.src && ( - - )} - {isTruncatedMessageDisplay && ( - - {isDownloadable && ( - - )} - - )} - - - - - {data.canSave && ( - - )} - {data.canUpload && ( - - )} - - - - + + + {data.src && ( + - + )} + {isTruncatedMessageDisplay && ( + + {isDownloadable && ( + + )} + + )} + + + + + {data.canSave && ( + + )} + {data.canUpload && ( + + )} + + + + - ); - }, -); + + ); +}); interface ImageRendererProps { className?: string; diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts index 2c051e0b86..c537e44d18 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts @@ -5,15 +5,18 @@ * 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 { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; +import { isResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; import { DataValuePanelService } from '../../TableViewer/ValuePanel/DataValuePanelService'; -import { ImageValuePresentation } from './ImageValuePresentation'; import { isImageValuePresentationAvailable } from './isImageValuePresentationAvailable'; +const ImageValuePresentation = importLazyComponent(() => import('./ImageValuePresentation').then(module => module.ImageValuePresentation)); + @injectable() export class ImageValuePresentationBootstrap extends Bootstrap { constructor(private readonly dataValuePanelService: DataValuePanelService) { @@ -23,21 +26,24 @@ export class ImageValuePresentationBootstrap extends Bootstrap { register(): void | Promise { this.dataValuePanelService.add({ key: 'image-presentation', - options: { dataFormat: [ResultDataFormat.Resultset] }, + options: { + dataFormat: [ResultDataFormat.Resultset], + }, name: 'data_viewer_presentation_value_image_title', order: 1, panel: () => ImageValuePresentation, isHidden: (_, context) => { - if (!context?.model.source.hasResult(context.resultIndex)) { + const source = context?.model.source as any; + if (!context?.model.source.hasResult(context.resultIndex) || !isResultSetDataSource(source)) { return true; } - const selection = context.model.source.getAction(context.resultIndex, ResultSetSelectAction); + const selection = source.getAction(context.resultIndex, ResultSetSelectAction); const activeElements = selection.getActiveElements(); if (activeElements.length > 0) { - const view = context.model.source.getAction(context.resultIndex, ResultSetViewAction); + const view = source.getAction(context.resultIndex, ResultSetViewAction); const firstSelectedCell = activeElements[0]; @@ -50,6 +56,4 @@ export class ImageValuePresentationBootstrap extends Bootstrap { }, }); } - - load(): void {} } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/useValuePanelImageValue.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/useValuePanelImageValue.ts index 1990675532..856d0c52d9 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/useValuePanelImageValue.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/useValuePanelImageValue.ts @@ -23,11 +23,11 @@ import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/R import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; import { DataViewerService } from '../../DataViewerService'; +import { ResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; interface Props { - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/QuotaPlaceholder.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/QuotaPlaceholder.tsx index 76c5ccf76d..4d3726ec9a 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/QuotaPlaceholder.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/QuotaPlaceholder.tsx @@ -14,13 +14,13 @@ import { WebsiteLinks } from '@cloudbeaver/core-website'; import type { IResultSetElementKey } from '../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey'; import { ResultSetDataContentAction } from '../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction'; import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseResultSet } from '../DatabaseDataModel/IDatabaseResultSet'; +import { ResultSetDataSource } from '../ResultSet/ResultSetDataSource'; import styles from './QuotaPlaceholder.module.css'; interface Props { className?: string; elementKey: IResultSetElementKey | undefined; - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; keepSize?: boolean; } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx index 7cbe46a391..1b959c2017 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx @@ -17,8 +17,8 @@ import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/Resu import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction'; import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; import { DataViewerService } from '../../DataViewerService'; +import { isResultSetDataModel } from '../../ResultSet/isResultSetDataModel'; import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService'; import { getDefaultLineWrapping } from './getDefaultLineWrapping'; import { isTextValueReadonly } from './isTextValueReadonly'; @@ -32,130 +32,136 @@ import { useTextValueGetter } from './useTextValueGetter'; const tabRegistry: StyleRegistry = [[TabStyles, { mode: 'append', styles: [TextValuePresentationTab] }]]; -export const TextValuePresentation: TabContainerPanelComponent> = observer( - function TextValuePresentation({ model, resultIndex, dataFormat }) { - const translate = useTranslate(); - const notificationService = useService(NotificationService); - const textValuePresentationService = useService(TextValuePresentationService); - const dataViewerService = useService(DataViewerService); - const style = useS(styles, TextValuePresentationTab); - const selectAction = model.source.getAction(resultIndex, ResultSetSelectAction); - const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction); - const activeElements = selectAction.getActiveElements(); - const firstSelectedCell = activeElements.length ? activeElements[0] : undefined; - const contentAction = model.source.getAction(resultIndex, ResultSetDataContentAction); - const editAction = model.source.getAction(resultIndex, ResultSetEditAction); +export const TextValuePresentation: TabContainerPanelComponent = observer(function TextValuePresentation({ + model: unknownModel, + resultIndex, + dataFormat, +}) { + const model = unknownModel as any; + if (!isResultSetDataModel(model)) { + throw new Error('TextValuePresentation can be used only with ResultSetDataSource'); + } + const translate = useTranslate(); + const notificationService = useService(NotificationService); + const textValuePresentationService = useService(TextValuePresentationService); + const dataViewerService = useService(DataViewerService); + const style = useS(styles, TextValuePresentationTab); + const selectAction = model.source.getAction(resultIndex, ResultSetSelectAction); + const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction); + const activeElements = selectAction.getActiveElements(); + const firstSelectedCell = activeElements.length ? activeElements[0] : undefined; + const contentAction = model.source.getAction(resultIndex, ResultSetDataContentAction); + const editAction = model.source.getAction(resultIndex, ResultSetEditAction); - const state = useTabLocalState(() => - observable({ - lineWrapping: null as boolean | null, - currentContentType: null as string | null, + const state = useTabLocalState(() => + observable({ + lineWrapping: null as boolean | null, + currentContentType: null as string | null, - setContentType(contentType: string | null) { - this.currentContentType = contentType; - }, - setLineWrapping(lineWrapping: boolean | null) { - this.lineWrapping = lineWrapping; - }, - }), - ); - const contentType = useAutoContentType({ - dataFormat, - model, - resultIndex, - currentContentType: state.currentContentType, - elementKey: firstSelectedCell, - formatAction, - }); - const textValueGetter = useTextValueGetter({ - contentAction, - editAction, - formatAction, - dataFormat, - contentType, - elementKey: firstSelectedCell, - }); - const autoLineWrapping = getDefaultLineWrapping(contentType); - const lineWrapping = state.lineWrapping ?? autoLineWrapping; - const isReadonly = isTextValueReadonly({ model, resultIndex, contentAction, cell: firstSelectedCell, formatAction }); - const canSave = firstSelectedCell && contentAction.isDownloadable(firstSelectedCell) && dataViewerService.canExportData; + setContentType(contentType: string | null) { + this.currentContentType = contentType; + }, + setLineWrapping(lineWrapping: boolean | null) { + this.lineWrapping = lineWrapping; + }, + }), + ); + const contentType = useAutoContentType({ + dataFormat, + model, + resultIndex, + currentContentType: state.currentContentType, + elementKey: firstSelectedCell, + formatAction, + }); + const textValueGetter = useTextValueGetter({ + contentAction, + editAction, + formatAction, + dataFormat, + contentType, + elementKey: firstSelectedCell, + }); + const autoLineWrapping = getDefaultLineWrapping(contentType); + const lineWrapping = state.lineWrapping ?? autoLineWrapping; + const isReadonly = isTextValueReadonly({ model, resultIndex, contentAction, cell: firstSelectedCell, formatAction }); + const canSave = firstSelectedCell && contentAction.isDownloadable(firstSelectedCell) && dataViewerService.canExportData; - function valueChangeHandler(newValue: string) { - if (firstSelectedCell && !isReadonly) { - editAction.set(firstSelectedCell, newValue); - } + function valueChangeHandler(newValue: string) { + if (firstSelectedCell && !isReadonly) { + editAction.set(firstSelectedCell, newValue); } + } - async function saveHandler() { - if (!firstSelectedCell) { - return; - } - - try { - await contentAction.downloadFileData(firstSelectedCell); - } catch (exception) { - notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); - } + async function saveHandler() { + if (!firstSelectedCell) { + return; } - async function selectTabHandler(tabId: string) { - // currentContentType may be selected automatically we don't want to change state in this case - if (tabId !== contentType) { - state.setContentType(tabId); - } + try { + await contentAction.downloadFileData(firstSelectedCell); + } catch (exception) { + notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); } + } - function toggleLineWrappingHandler() { - state.setLineWrapping(!lineWrapping); + async function selectTabHandler(tabId: string) { + // currentContentType may be selected automatically we don't want to change state in this case + if (tabId !== contentType) { + state.setContentType(tabId); } + } + + function toggleLineWrappingHandler() { + state.setLineWrapping(!lineWrapping); + } - return ( - - - - selectTabHandler(tab.tabId)} - > - - - - - + return ( + + + + selectTabHandler(tab.tabId)} + > + + + + - - - - - - {firstSelectedCell && } - - - {canSave && ( - + + + + + + + {firstSelectedCell && } + + + {canSave && ( + + )} + - + name={`/icons/plugin_data_viewer_${lineWrapping ? 'no_wrap' : 'wrap'}_lines.svg`} + img + onClick={toggleLineWrappingHandler} + /> - ); - }, -); + + ); +}); diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationBootstrap.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationBootstrap.ts index 30d0b744e8..8aeaad69fa 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationBootstrap.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationBootstrap.ts @@ -5,19 +5,18 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import React, { lazy } from 'react'; +import React from 'react'; +import { importLazyComponent } from '@cloudbeaver/core-blocks'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; +import { isResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; import { DataValuePanelService } from '../../TableViewer/ValuePanel/DataValuePanelService'; import { isBlobPresentationAvailable } from './isTextValuePresentationAvailable'; import { TextValuePresentationService } from './TextValuePresentationService'; -const TextValuePresentation = lazy(async () => { - const { TextValuePresentation } = await import('./TextValuePresentation'); - return { default: TextValuePresentation }; -}); +const TextValuePresentation = importLazyComponent(() => import('./TextValuePresentation').then(module => module.TextValuePresentation)); @injectable() export class TextValuePresentationBootstrap extends Bootstrap { @@ -28,13 +27,18 @@ export class TextValuePresentationBootstrap extends Bootstrap { super(); } - register(): void | Promise { + register(): void { this.dataValuePanelService.add({ key: 'text-presentation', - options: { dataFormat: [ResultDataFormat.Resultset] }, + options: { + dataFormat: [ResultDataFormat.Resultset], + }, name: 'data_viewer_presentation_value_text_title', order: Number.MAX_SAFE_INTEGER, panel: () => TextValuePresentation, + isHidden(_, props) { + return !props || !props.model.source.hasResult(props.resultIndex) || !isResultSetDataSource(props.model.source); + }, }); this.textValuePresentationService.add({ @@ -80,6 +84,4 @@ export class TextValuePresentationBootstrap extends Bootstrap { isHidden: (_, context) => !isBlobPresentationAvailable(context), }); } - - load(): void {} } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts index 967df1c357..2c6429590f 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts @@ -8,21 +8,27 @@ import { injectable } from '@cloudbeaver/core-di'; import { ITabInfo, ITabInfoOptions, TabsContainer } from '@cloudbeaver/core-ui'; +import { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; +import { ResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; import type { IDataValuePanelOptions, IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService'; +export interface ITextValuePanelProps extends Omit { + model: IDatabaseDataModel; +} + @injectable() export class TextValuePresentationService { - readonly tabs: TabsContainer, IDataValuePanelOptions>; + readonly tabs: TabsContainer; constructor() { this.tabs = new TabsContainer('Value presentation'); } - get(tabId: string): ITabInfo, IDataValuePanelOptions> | undefined { + get(tabId: string): ITabInfo | undefined { return this.tabs.getTabInfo(tabId); } - add(tabInfo: ITabInfoOptions, IDataValuePanelOptions>): void { + add(tabInfo: ITabInfoOptions): void { this.tabs.add(tabInfo); } } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueTruncatedMessage.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueTruncatedMessage.tsx index fbbbf9c517..53b3f2eca2 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueTruncatedMessage.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueTruncatedMessage.tsx @@ -18,13 +18,13 @@ import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultS import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction'; import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; +import { ResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; import { QuotaPlaceholder } from '../QuotaPlaceholder'; import { MAX_BLOB_PREVIEW_SIZE } from './MAX_BLOB_PREVIEW_SIZE'; interface Props { resultIndex: number; - model: IDatabaseDataModel; + model: IDatabaseDataModel; elementKey: IResultSetElementKey; } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValuePresentationAvailable.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValuePresentationAvailable.ts index e65ce49ba7..5809a7c16d 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValuePresentationAvailable.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValuePresentationAvailable.ts @@ -9,20 +9,20 @@ import { isResultSetBinaryValue } from '../../DatabaseDataModel/Actions/ResultSe import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; -import type { IDatabaseDataResult } from '../../DatabaseDataModel/IDatabaseDataResult'; -import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService'; +import { ITextValuePanelProps } from './TextValuePresentationService'; -export function isBlobPresentationAvailable(context: IDataValuePanelProps | undefined): boolean { - if (!context?.model.source.hasResult(context.resultIndex)) { +export function isBlobPresentationAvailable(context: ITextValuePanelProps | undefined): boolean { + const source = context?.model.source; + if (!context || !source?.hasResult(context.resultIndex)) { return true; } - const selection = context.model.source.getAction(context.resultIndex, ResultSetSelectAction); + const selection = source.getAction(context.resultIndex, ResultSetSelectAction); const activeElements = selection.getActiveElements(); if (activeElements.length > 0) { - const view = context.model.source.getAction(context.resultIndex, ResultSetViewAction); + const view = source.getAction(context.resultIndex, ResultSetViewAction); const firstSelectedCell = activeElements[0]; diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts index c781d0c696..6a124c8bf5 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts @@ -9,12 +9,12 @@ import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/Resul import type { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction'; import type { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; +import { ResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; interface Args { contentAction: ResultSetDataContentAction; formatAction: ResultSetFormatAction; - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; cell: IResultSetElementKey | undefined; } diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/useAutoContentType.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/useAutoContentType.ts index f8c111f8d6..d4e74ce7fd 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/useAutoContentType.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/useAutoContentType.ts @@ -13,12 +13,12 @@ import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/ import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; import type { IResultSetValue, ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; -import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; +import { ResultSetDataSource } from '../../ResultSet/ResultSetDataSource'; import { TextValuePresentationService } from './TextValuePresentationService'; interface Args { resultIndex: number; - model: IDatabaseDataModel; + model: IDatabaseDataModel; dataFormat: ResultDataFormat | null; currentContentType: string | null; elementKey?: IResultSetElementKey; diff --git a/webapp/packages/plugin-data-viewer/src/index.ts b/webapp/packages/plugin-data-viewer/src/index.ts index aa2c7834bd..ec00ff030d 100644 --- a/webapp/packages/plugin-data-viewer/src/index.ts +++ b/webapp/packages/plugin-data-viewer/src/index.ts @@ -82,6 +82,7 @@ export * from './TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService' export * from './ContainerDataSource'; export * from './ResultSet/ResultSetDataSource'; +export * from './ResultSet/isResultSetDataModel'; export * from './DataPresentationService'; export * from './DataViewerDataChangeConfirmationService'; export * from './ValuePanelPresentation/BooleanValue/isBooleanValuePresentationAvailable'; diff --git a/webapp/packages/plugin-devtools/src/PluginBootstrap.ts b/webapp/packages/plugin-devtools/src/PluginBootstrap.ts index d58521a82d..b52a1042f0 100644 --- a/webapp/packages/plugin-devtools/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-devtools/src/PluginBootstrap.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 { App, Bootstrap, DIService, injectable, IServiceConstructor } from '@cloudbeaver/core-di'; +import { App, Bootstrap, injectable, IServiceConstructor, IServiceProvider } from '@cloudbeaver/core-di'; import { CachedResource } from '@cloudbeaver/core-resource'; import { EAdminPermission, PermissionsService } from '@cloudbeaver/core-root'; import { ActionService, DATA_CONTEXT_SUBMENU_ITEM, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; @@ -30,7 +30,7 @@ import { ResourceSubMenuItem } from './menu/ResourceSubMenuItem'; export class PluginBootstrap extends Bootstrap { constructor( private readonly app: App, - private readonly diService: DIService, + private readonly serviceProvider: IServiceProvider, private readonly menuService: MenuService, private readonly actionService: ActionService, private readonly devToolsService: DevToolsService, @@ -171,7 +171,7 @@ export class PluginBootstrap extends Bootstrap { }, { onSelect: () => { - const instance = this.diService.serviceInjector.getServiceByClass>(item.resource); + const instance = this.serviceProvider.getService>(item.resource); instance.markOutdated(undefined); }, }, diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx index dcf29a1e41..f599970393 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx @@ -13,9 +13,9 @@ import wellknown, { GeoJSONGeometry } from 'wellknown'; import { TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks'; import { IDatabaseDataModel, - IDatabaseResultSet, IResultSetElementKey, ResultSetDataKeysUtils, + ResultSetDataSource, ResultSetSelectAction, ResultSetViewAction, } from '@cloudbeaver/plugin-data-viewer'; @@ -73,7 +73,7 @@ function getTransformedGeometry(from: CrsKey, to: CrsKey, geometry: GeoJSONGeome } interface Props { - model: IDatabaseDataModel; + model: IDatabaseDataModel; resultIndex: number; } diff --git a/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx b/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx index 0348cd38b9..1922232e62 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx @@ -6,10 +6,14 @@ * you may not use this file except in compliance with the License. */ import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; -import type { IDatabaseResultSet, IDataValuePanelProps } from '@cloudbeaver/plugin-data-viewer'; +import { type IDataValuePanelProps, isResultSetDataModel } from '@cloudbeaver/plugin-data-viewer'; import { GISValuePresentation } from './GISValuePresentation'; -export const GISViewer: TabContainerPanelComponent> = function GISViewer({ model, resultIndex }) { +export const GISViewer: TabContainerPanelComponent = function GISViewer({ model, resultIndex }) { + if (!isResultSetDataModel(model)) { + throw new Error('GISViewer can only be used with ResultSetDataSource'); + } + return ; }; diff --git a/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts b/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts index e210e679c6..f432b31c79 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts +++ b/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts @@ -9,7 +9,7 @@ import { lazy } from 'react'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; -import { DataValuePanelService, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; +import { DataValuePanelService, isResultSetDataSource, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; import { ResultSetGISAction } from './ResultSetGISAction'; @@ -28,17 +28,20 @@ export class GISViewerBootstrap extends Bootstrap { register(): void | Promise { this.dataValuePanelService.add({ key: 'gis-presentation', - options: { dataFormat: [ResultDataFormat.Resultset] }, + options: { + dataFormat: [ResultDataFormat.Resultset], + }, name: 'gis_presentation_title', order: 10, panel: () => GISViewer, isHidden: (_, context) => { - if (!context || !context.model.source.hasResult(context.resultIndex)) { + const source = context?.model.source as any; + if (!isResultSetDataSource(source) || !context || !source.hasResult(context.resultIndex)) { return true; } - const selection = context.model.source.getAction(context.resultIndex, ResultSetSelectAction); - const gis = context.model.source.getAction(context.resultIndex, ResultSetGISAction); + const selection = source.getAction(context.resultIndex, ResultSetSelectAction); + const gis = source.getAction(context.resultIndex, ResultSetGISAction); const activeElements = selection.getActiveElements(); diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.test.ts b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.test.ts index c4b2d8bcfe..3e6108127d 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.test.ts +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.test.ts @@ -70,8 +70,8 @@ const newSettings = { }; test('New settings override deprecated settings', async () => { - const settings = app.injector.getServiceByClass(LogViewerSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(LogViewerSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(newSettings))); @@ -86,8 +86,8 @@ test('New settings override deprecated settings', async () => { }); test('Deprecated settings are used if new settings are not defined', async () => { - const settings = app.injector.getServiceByClass(LogViewerSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(LogViewerSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings))); diff --git a/webapp/packages/plugin-object-viewer/package.json b/webapp/packages/plugin-object-viewer/package.json index 0782ec046c..66731ad140 100644 --- a/webapp/packages/plugin-object-viewer/package.json +++ b/webapp/packages/plugin-object-viewer/package.json @@ -28,7 +28,6 @@ "@cloudbeaver/core-projects": "^0", "@cloudbeaver/core-resource": "^0", "@cloudbeaver/core-sdk": "^0", - "@cloudbeaver/core-theming": "^0", "@cloudbeaver/core-ui": "^0", "@cloudbeaver/core-utils": "^0", "@cloudbeaver/core-view": "^0", @@ -43,7 +42,6 @@ }, "peerDependencies": {}, "devDependencies": { - "@cloudbeaver/core-theming": "^0", "@types/react": "^18", "typescript": "^5", "typescript-plugin-css-modules": "^5" diff --git a/webapp/packages/plugin-object-viewer/tsconfig.json b/webapp/packages/plugin-object-viewer/tsconfig.json index 525e2a26ab..d23307a25a 100644 --- a/webapp/packages/plugin-object-viewer/tsconfig.json +++ b/webapp/packages/plugin-object-viewer/tsconfig.json @@ -39,12 +39,6 @@ { "path": "../core-sdk/tsconfig.json" }, - { - "path": "../core-theming/tsconfig.json" - }, - { - "path": "../core-theming/tsconfig.json" - }, { "path": "../core-ui/tsconfig.json" }, diff --git a/webapp/packages/plugin-react-data-grid/package.json b/webapp/packages/plugin-react-data-grid/package.json index f6357763eb..8ac4b0139a 100644 --- a/webapp/packages/plugin-react-data-grid/package.json +++ b/webapp/packages/plugin-react-data-grid/package.json @@ -17,13 +17,10 @@ "update-ts-references": "yarn run clean && typescript-resolve-references" }, "dependencies": { - "@cloudbeaver/core-blocks": "^0", - "react": "^18", "react-data-grid": "file:react-data-grid-dist" }, "peerDependencies": {}, "devDependencies": { - "@types/react": "^18", "typescript": "^5" } } diff --git a/webapp/packages/plugin-react-data-grid/tsconfig.json b/webapp/packages/plugin-react-data-grid/tsconfig.json index ade5299fb3..48c85dec3b 100644 --- a/webapp/packages/plugin-react-data-grid/tsconfig.json +++ b/webapp/packages/plugin-react-data-grid/tsconfig.json @@ -10,11 +10,7 @@ "outDir": "dist", "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" }, - "references": [ - { - "path": "../core-blocks/tsconfig.json" - } - ], + "references": [], "include": [ "__custom_mocks__/**/*", "src/**/*", diff --git a/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.test.ts b/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.test.ts index 1909024ac5..38d8c48c97 100644 --- a/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.test.ts +++ b/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.test.ts @@ -50,8 +50,8 @@ const newSettings = { }; test('New settings equal deprecated settings A', async () => { - const settings = app.injector.getServiceByClass(ResourceManagerSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(ResourceManagerSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(newSettings))); @@ -62,8 +62,8 @@ test('New settings equal deprecated settings A', async () => { }); test('New settings equal deprecated settings B', async () => { - const settings = app.injector.getServiceByClass(ResourceManagerSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(ResourceManagerSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings))); diff --git a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts index 7ea7017958..e505bd1cc7 100644 --- a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts @@ -8,7 +8,7 @@ import { makeObservable, observable } from 'mobx'; import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; -import type { IServiceInjector } from '@cloudbeaver/core-di'; +import type { IServiceProvider } from '@cloudbeaver/core-di'; import type { ITask } from '@cloudbeaver/core-executor'; import { AsyncTaskInfoService, @@ -50,11 +50,11 @@ export class QueryDataSource { await super.cancel(); await this.currentTask?.cancel(); @@ -154,23 +158,6 @@ export class QueryDataSource { const options = this.options; const executionContext = this.executionContext; @@ -221,7 +208,7 @@ export class QueryDataSource((result, index) => ({ id: result.resultSet?.id || null, diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts index bcf54266d4..8dc6b2345e 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts @@ -10,9 +10,9 @@ import { action, computed, makeObservable, observable, toJS } from 'mobx'; import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import { isContainsException, isValuesEqual, staticImplements } from '@cloudbeaver/core-utils'; -import type { IDatabaseDataModel, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer'; +import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer'; -import type { IDataQueryOptions } from '../QueryDataSource'; +import type { QueryDataSource } from '../QueryDataSource'; import { ESqlDataSourceFeatures } from './ESqlDataSourceFeatures'; import type { ISetScriptData, ISqlDataSource, ISqlDataSourceKey, ISqlEditorCursor } from './ISqlDataSource'; import type { ISqlDataSourceHistory } from './SqlDataSourceHistory/ISqlDataSourceHistory'; @@ -32,7 +32,7 @@ export abstract class BaseSqlDataSource implements ISqlDataSource { abstract get baseExecutionContext(): IConnectionExecutionContextInfo | undefined; abstract get executionContext(): IConnectionExecutionContextInfo | undefined; - databaseModels: IDatabaseDataModel[]; + databaseModels: IDatabaseDataModel[]; incomingScript: string | undefined; incomingExecutionContext: IConnectionExecutionContextInfo | undefined | null; exception?: Error | Error[] | null | undefined; @@ -81,7 +81,7 @@ export abstract class BaseSqlDataSource implements ISqlDataSource { readonly history: ISqlDataSourceHistory; readonly onUpdate: ISyncExecutor; readonly onSetScript: ISyncExecutor; - readonly onDatabaseModelUpdate: ISyncExecutor[]>; + readonly onDatabaseModelUpdate: ISyncExecutor[]>; protected outdated: boolean; protected editing: boolean; diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts index efa60817e7..8ad62380b4 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts @@ -8,9 +8,9 @@ import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; import type { ISyncExecutor } from '@cloudbeaver/core-executor'; import type { ILoadableState } from '@cloudbeaver/core-utils'; -import type { IDatabaseDataModel, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer'; +import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer'; -import type { IDataQueryOptions } from '../QueryDataSource'; +import type { QueryDataSource } from '../QueryDataSource'; import type { ESqlDataSourceFeatures } from './ESqlDataSourceFeatures'; import type { ISqlDataSourceHistory } from './SqlDataSourceHistory/ISqlDataSourceHistory'; @@ -42,7 +42,7 @@ export interface ISqlDataSource extends ILoadableState { readonly incomingScript?: string; readonly history: ISqlDataSourceHistory; - readonly databaseModels: IDatabaseDataModel[]; + readonly databaseModels: IDatabaseDataModel[]; readonly executionContext?: IConnectionExecutionContextInfo; readonly features: ESqlDataSourceFeatures[]; @@ -55,7 +55,7 @@ export interface ISqlDataSource extends ILoadableState { readonly onUpdate: ISyncExecutor; readonly onSetScript: ISyncExecutor; - readonly onDatabaseModelUpdate: ISyncExecutor[]>; + readonly onDatabaseModelUpdate: ISyncExecutor[]>; isOpened(): boolean; isReadonly(): boolean; diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorSettingsService.test.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorSettingsService.test.ts index d8175b1806..c8338da91c 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorSettingsService.test.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorSettingsService.test.ts @@ -87,8 +87,8 @@ const newSettings = { }; test('New settings override deprecated settings', async () => { - const settings = app.injector.getServiceByClass(SqlEditorSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(SqlEditorSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(newSettings))); @@ -99,8 +99,8 @@ test('New settings override deprecated settings', async () => { }); test('Deprecated settings are used if new settings are not defined', async () => { - const settings = app.injector.getServiceByClass(SqlEditorSettingsService); - const config = app.injector.getServiceByClass(ServerConfigResource); + const settings = app.serviceProvider.getService(SqlEditorSettingsService); + const config = app.serviceProvider.getService(ServerConfigResource); server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings))); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryResultService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryResultService.ts index 7ae8e5468f..fb92fa5158 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryResultService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryResultService.ts @@ -7,10 +7,10 @@ */ import { injectable } from '@cloudbeaver/core-di'; import { uuid } from '@cloudbeaver/core-utils'; -import { IDatabaseDataModel, IDatabaseResultSet, TableViewerStorageService } from '@cloudbeaver/plugin-data-viewer'; +import { IDatabaseDataModel, TableViewerStorageService } from '@cloudbeaver/plugin-data-viewer'; import type { IResultGroup, IResultTab, ISqlEditorTabState, IStatisticsTab } from '../ISqlEditorTabState'; -import type { IDataQueryOptions } from '../QueryDataSource'; +import type { QueryDataSource } from '../QueryDataSource'; @injectable() export class SqlQueryResultService { @@ -41,7 +41,7 @@ export class SqlQueryResultService { updateGroupTabs( editorState: ISqlEditorTabState, - model: IDatabaseDataModel, + model: IDatabaseDataModel, groupId: string, selectFirstResult?: boolean, resultCount?: number, @@ -155,7 +155,7 @@ export class SqlQueryResultService { state.resultGroups.splice(state.resultGroups.indexOf(group), 1); const model = this.tableViewerStorageService.get(group.modelId); - model?.dispose(true).then(() => { + model?.dispose().then(() => { this.tableViewerStorageService.remove(group.modelId); }); } @@ -181,12 +181,7 @@ export class SqlQueryResultService { editorState.currentTabId = mainTab[0].tabId; } - private createTabsForGroup( - state: ISqlEditorTabState, - group: IResultGroup, - model: IDatabaseDataModel, - resultCount?: number, - ) { + private createTabsForGroup(state: ISqlEditorTabState, group: IResultGroup, model: IDatabaseDataModel, resultCount?: number) { this.createResultTabForGroup(state, group, model, 0, resultCount); for (let i = 1; i < model.source.results.length; i++) { @@ -197,7 +192,7 @@ export class SqlQueryResultService { private createResultTabForGroup( state: ISqlEditorTabState, group: IResultGroup, - model: IDatabaseDataModel, + model: IDatabaseDataModel, indexInResultSet: number, resultCount?: number, ) { diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts index fb8beb4f6c..74e72a97fc 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts @@ -8,7 +8,7 @@ import { makeObservable, observable } from 'mobx'; import { ConnectionExecutionContextService, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; -import { App, injectable } from '@cloudbeaver/core-di'; +import { injectable, IServiceProvider } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk'; import { @@ -19,12 +19,11 @@ import { DataViewerService, DataViewerSettingsService, IDatabaseDataModel, - IDatabaseResultSet, TableViewerStorageService, } from '@cloudbeaver/plugin-data-viewer'; import type { IResultGroup, ISqlEditorTabState } from '../ISqlEditorTabState'; -import { IDataQueryOptions, QueryDataSource } from '../QueryDataSource'; +import { QueryDataSource } from '../QueryDataSource'; import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService'; import { SqlQueryResultService } from './SqlQueryResultService'; @@ -46,7 +45,7 @@ export class SqlQueryService { private readonly statisticsMap: Map; constructor( - private readonly app: App, + private readonly serviceProvider: IServiceProvider, private readonly tableViewerStorageService: TableViewerStorageService, private readonly graphQLService: GraphQLService, private readonly notificationService: NotificationService, @@ -121,7 +120,7 @@ export class SqlQueryService { } let source: QueryDataSource; - let model: IDatabaseDataModel; + let model: IDatabaseDataModel; let isNewTabCreated = false; const connectionKey = createConnectionParam(contextInfo.projectId, contextInfo.connectionId); @@ -130,7 +129,7 @@ export class SqlQueryService { let tabGroup = this.sqlQueryResultService.getSelectedGroup(editorState); if (inNewTab || !tabGroup) { - source = new QueryDataSource(this.app.getServiceInjector(), this.graphQLService, this.asyncTaskInfoService); + source = new QueryDataSource(this.serviceProvider, this.graphQLService, this.asyncTaskInfoService); model = this.tableViewerStorageService.add(new DatabaseDataModel(source)); this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id); tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query); @@ -148,22 +147,23 @@ export class SqlQueryService { model .setAccess(editable ? DatabaseDataAccessMode.Default : DatabaseDataAccessMode.Readonly) + .source.setExecutionContext(executionContext) + .setSupportedDataFormats(connectionInfo.supportedDataFormats) + .setKeepExecutionContextOnDispose(true) .setOptions({ query: query, connectionKey, constraints: [], whereFilter: '', readLogs: isOutputLogsTabOpened, - }) - .source.setExecutionContext(executionContext) - .setSupportedDataFormats(connectionInfo.supportedDataFormats); + }); this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, true); try { await model.setCountGain(this.dataViewerSettingsService.getDefaultRowsCount()).setSlice(0).request(); - model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.getResults().length)); + model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.source.getResults().length)); this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId); } catch (exception: any) { // remove group if execution was cancelled @@ -207,7 +207,7 @@ export class SqlQueryService { const statistics = this.getStatistics(statisticsTab.tabId)!; let source: QueryDataSource | undefined; - let model: IDatabaseDataModel | undefined; + let model: IDatabaseDataModel | undefined; let resultCount = 0; for (let i = 0; i < queries.length; i++) { @@ -216,7 +216,7 @@ export class SqlQueryService { options?.onQueryExecutionStart?.(query, i); if (!model || !source) { - source = new QueryDataSource(this.app.getServiceInjector(), this.graphQLService, this.asyncTaskInfoService); + source = new QueryDataSource(this.serviceProvider, this.graphQLService, this.asyncTaskInfoService); model = this.tableViewerStorageService.add(new DatabaseDataModel(source)); this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id); } @@ -227,15 +227,16 @@ export class SqlQueryService { model .setAccess(editable ? DatabaseDataAccessMode.Default : DatabaseDataAccessMode.Readonly) + .source.setExecutionContext(executionContext) + .setSupportedDataFormats(connectionInfo.supportedDataFormats) + .setKeepExecutionContextOnDispose(true) .setOptions({ query: query, connectionKey, constraints: [], whereFilter: '', readLogs: isOutputLogsTabOpened, - }) - .source.setExecutionContext(executionContext) - .setSupportedDataFormats(connectionInfo.supportedDataFormats); + }); try { await model.setCountGain(this.dataViewerSettingsService.getDefaultRowsCount()).setSlice(0).request(); @@ -253,7 +254,7 @@ export class SqlQueryService { resultCount = resultCount + 1; const tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query, groupNameOrder); - model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.getResults().length, resultCount)); + model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.source.getResults().length, resultCount)); this.switchTabToActiveRequest(editorState, tabGroup, model); this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, false, resultCount); @@ -290,21 +291,17 @@ export class SqlQueryService { this.statisticsMap.delete(tabId); } - private switchTabToActiveRequest( - editorState: ISqlEditorTabState, - tabGroup: IResultGroup, - model: IDatabaseDataModel, - ) { + private switchTabToActiveRequest(editorState: ISqlEditorTabState, tabGroup: IResultGroup, model: IDatabaseDataModel) { model.onRequest.addPostHandler(({ stage }) => { if (stage === 'request') { const activeGroupId = this.sqlQueryResultService.getSelectedGroup(editorState)?.groupId; - for (const result of model.getResults()) { + for (const result of model.source.getResults()) { const editor = model.source.getActionImplementation(result, DatabaseEditAction); const edited = editor?.isEdited() && model.source.executionContext?.context; if (edited && activeGroupId !== tabGroup.groupId) { - this.sqlQueryResultService.selectResult(editorState, tabGroup.groupId, model.getResults().indexOf(result)); + this.sqlQueryResultService.selectResult(editorState, tabGroup.groupId, model.source.getResults().indexOf(result)); return; } } diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlScriptStatisticsPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlScriptStatisticsPanel.tsx index 1d7c7b32e8..e794a46610 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlScriptStatisticsPanel.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlScriptStatisticsPanel.tsx @@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite'; import { Loader, s, TextPlaceholder, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; -import { TableViewerStorageService } from '@cloudbeaver/plugin-data-viewer'; +import { IDatabaseDataModel, TableViewerStorageService } from '@cloudbeaver/plugin-data-viewer'; import type { IStatisticsTab } from '../ISqlEditorTabState'; import type { QueryDataSource } from '../QueryDataSource'; @@ -31,9 +31,7 @@ export const SqlScriptStatisticsPanel = observer(function SqlScriptStati return {translate('sql_editor_sql_statistics_unavailable')}; } - const source: QueryDataSource | undefined = statistics.modelId - ? (tableViewerStorageService.get(statistics.modelId)?.source as QueryDataSource) - : undefined; + const source = statistics.modelId ? tableViewerStorageService.get>(statistics.modelId)?.source : undefined; return (
diff --git a/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts b/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts index b4b31375f1..86e77dfc8a 100644 --- a/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts +++ b/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts @@ -15,6 +15,9 @@ import { DATA_VIEWER_DATA_MODEL_ACTIONS_MENU, DatabaseEditAction, DataViewerPresentationType, + IDatabaseDataModel, + isResultSetDataSource, + ResultSetDataSource, } from '@cloudbeaver/plugin-data-viewer'; import { ACTION_SQL_GENERATE } from './actions/ACTION_SQL_GENERATE'; @@ -55,6 +58,10 @@ export class GeneratorMenuBootstrap extends Bootstrap { menus: [DATA_VIEWER_DATA_MODEL_ACTIONS_MENU], actions: [ACTION_SQL_GENERATE], contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], + isActionApplicable(context) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + return isResultSetDataSource(model.source); + }, isDisabled(context) { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; @@ -65,7 +72,10 @@ export class GeneratorMenuBootstrap extends Bootstrap { return !editor?.isEdited(); }, handler: context => { - this.scriptPreviewService.open(context.get(DATA_CONTEXT_DV_DDM)!, context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!); + this.scriptPreviewService.open( + context.get(DATA_CONTEXT_DV_DDM)! as unknown as IDatabaseDataModel, + context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!, + ); }, }); } diff --git a/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewDialog.tsx b/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewDialog.tsx index 5319bf662b..498547bda3 100644 --- a/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewDialog.tsx +++ b/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewDialog.tsx @@ -20,41 +20,33 @@ import { useS, useTranslate, } from '@cloudbeaver/core-blocks'; -import { ConnectionDialectResource, ConnectionExecutionContextService, createConnectionParam } from '@cloudbeaver/core-connections'; -import { useService } from '@cloudbeaver/core-di'; +import { ConnectionDialectResource, IConnectionInfoParams } from '@cloudbeaver/core-connections'; import type { DialogComponentProps } from '@cloudbeaver/core-dialogs'; import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6'; -import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer'; import { SQLCodeEditorLoader, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-new'; import style from './ScriptPreviewDialog.module.css'; interface Payload { script: string; - model: IDatabaseDataModel; + connectionKey: IConnectionInfoParams | null; + onApply: () => Promise; } -export const ScriptPreviewDialog = observer>(function ScriptPreviewDialog({ rejectDialog, payload }) { +export const ScriptPreviewDialog = observer>(function ScriptPreviewDialog({ rejectDialog, resolveDialog, payload }) { const translate = useTranslate(); const copy = useClipboard(); const styles = useS(style); - const connectionExecutionContextService = useService(ConnectionExecutionContextService); - const context = connectionExecutionContextService.get(payload.model.source.executionContext?.context?.id ?? ''); - const contextInfo = context?.context; - const dialect = useResource( - ScriptPreviewDialog, - ConnectionDialectResource, - contextInfo ? createConnectionParam(contextInfo.projectId, contextInfo.connectionId) : null, - ); + const dialect = useResource(ScriptPreviewDialog, ConnectionDialectResource, payload.connectionKey); const sqlDialect = useSqlDialectExtension(dialect.data); const extensions = useCodemirrorExtensions(); extensions.set(...sqlDialect); const apply = async () => { try { - await payload.model.save(); - rejectDialog(); + await payload.onApply(); + resolveDialog(); } catch {} }; diff --git a/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewService.ts b/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewService.ts index 4d2e69e2fd..d6e434fa1a 100644 --- a/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewService.ts +++ b/webapp/packages/plugin-sql-generator/src/ScriptPreview/ScriptPreviewService.ts @@ -6,11 +6,12 @@ * you may not use this file except in compliance with the License. */ import { importLazyComponent } from '@cloudbeaver/core-blocks'; +import { createConnectionParam, IConnectionInfoParams } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; import { GraphQLService, ResultDataFormat, UpdateResultsDataBatchScriptMutationVariables } from '@cloudbeaver/core-sdk'; -import { DocumentEditAction, type IDatabaseDataModel, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer'; +import { DocumentEditAction, type IDatabaseDataModel, ResultSetDataSource, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer'; const ScriptPreviewDialog = importLazyComponent(() => import('./ScriptPreviewDialog').then(m => m.ScriptPreviewDialog)); @@ -22,24 +23,30 @@ export class ScriptPreviewService { private readonly notificationService: NotificationService, ) {} - async open(model: IDatabaseDataModel, resultIndex: number): Promise { + async open(model: IDatabaseDataModel, resultIndex: number): Promise { try { const script = await model.source.runOperation(() => this.tryGetScript(model, resultIndex)); if (script === null) { throw new Error('Script is not provided'); } + let connectionKey: IConnectionInfoParams | null = null; - this.commonDialogService.open(ScriptPreviewDialog, { + if (model.source.executionContext?.context) { + connectionKey = createConnectionParam(model.source.executionContext.context.projectId, model.source.executionContext.context.connectionId); + } + + await this.commonDialogService.open(ScriptPreviewDialog, { script, - model, + connectionKey, + onApply: () => model.save(), }); } catch (exception: any) { this.notificationService.logException(exception, 'data_viewer_script_preview_error_title'); } } - private async tryGetScript(model: IDatabaseDataModel, resultIndex: number): Promise { + private async tryGetScript(model: IDatabaseDataModel, resultIndex: number): Promise { const executionContext = model.source.executionContext?.context; if (!executionContext) { diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/getUserProfileFormAuthenticationPart.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/getUserProfileFormAuthenticationPart.ts index ed8073f81c..13fb8077f5 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/getUserProfileFormAuthenticationPart.ts +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/getUserProfileFormAuthenticationPart.ts @@ -17,8 +17,8 @@ const DATA_CONTEXT_USER_PROFILE_FORM_AUTHENTICATION_PART = createDataContext): UserProfileFormAuthenticationPart { return formState.getPart(DATA_CONTEXT_USER_PROFILE_FORM_AUTHENTICATION_PART, context => { const di = context.get(DATA_CONTEXT_DI_PROVIDER)!; - const userInfoResource = di.getServiceByClass(UserInfoResource); - const passwordPolicyService = di.getServiceByClass(PasswordPolicyService); + const userInfoResource = di.getService(UserInfoResource); + const passwordPolicyService = di.getService(PasswordPolicyService); return new UserProfileFormAuthenticationPart(formState, userInfoResource, passwordPolicyService); }); diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/getUserProfileFormInfoPart.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/getUserProfileFormInfoPart.ts index 1a92fa6115..585f3ae815 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/getUserProfileFormInfoPart.ts +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/getUserProfileFormInfoPart.ts @@ -17,7 +17,7 @@ const DATA_CONTEXT_USER_PROFILE_FORM_INFO_PART = createDataContext): UserProfileFormInfoPart { return formState.getPart(DATA_CONTEXT_USER_PROFILE_FORM_INFO_PART, context => { const di = context.get(DATA_CONTEXT_DI_PROVIDER)!; - const userInfoResource = di.getServiceByClass(UserInfoResource); + const userInfoResource = di.getService(UserInfoResource); return new UserProfileFormInfoPart(formState, userInfoResource); }); diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormPanel.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormPanel.tsx index a77a50f0e3..4d1669ce63 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormPanel.tsx +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormPanel.tsx @@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite'; import { useState } from 'react'; import { ConfirmationDialog, useExecutor } from '@cloudbeaver/core-blocks'; -import { App, useService } from '@cloudbeaver/core-di'; +import { IServiceProvider, useService } from '@cloudbeaver/core-di'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { ExecutorInterrupter } from '@cloudbeaver/core-executor'; import { FormMode, TabContainerPanelComponent } from '@cloudbeaver/core-ui'; @@ -21,13 +21,13 @@ import { UserProfileFormService } from './UserProfileFormService'; import { UserProfileFormState } from './UserProfileFormState'; export const UserProfileFormPanel: TabContainerPanelComponent = observer(function UserProfileFormPanel({ tabId }) { - const appService = useService(App); + const serviceProvider = useService(IServiceProvider); const userProfileFormService = useService(UserProfileFormService); const userProfileOptionsPanelService = useService(UserProfileOptionsPanelService); const commonDialogService = useService(CommonDialogService); const [state] = useState(() => { - const state = new UserProfileFormState(appService, userProfileFormService, {}); + const state = new UserProfileFormState(serviceProvider, userProfileFormService, {}); state.setMode(FormMode.Edit); return state; diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormState.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormState.ts index 48a22200ea..bdf4de4405 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormState.ts +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormState.ts @@ -5,13 +5,13 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import type { App } from '@cloudbeaver/core-di'; +import type { IServiceProvider } from '@cloudbeaver/core-di'; import { FormState } from '@cloudbeaver/core-ui'; import type { IUserProfileFormState, UserProfileFormService } from './UserProfileFormService'; export class UserProfileFormState extends FormState { - constructor(app: App, service: UserProfileFormService, config: IUserProfileFormState) { - super(app, service, config); + constructor(serviceProvider: IServiceProvider, service: UserProfileFormService, config: IUserProfileFormState) { + super(serviceProvider, service, config); } } diff --git a/webapp/packages/tests-runner/src/createApp.ts b/webapp/packages/tests-runner/src/createApp.ts index 3a61c6e78f..9565b62f4d 100644 --- a/webapp/packages/tests-runner/src/createApp.ts +++ b/webapp/packages/tests-runner/src/createApp.ts @@ -7,13 +7,13 @@ */ import { configure } from 'mobx'; -import { App, IServiceInjector, PluginManifest } from '@cloudbeaver/core-di'; +import { App, IServiceProvider, PluginManifest } from '@cloudbeaver/core-di'; import './__custom_mocks__/mockKnownConsoleMessages'; export interface IApplication { app: App; - injector: IServiceInjector; + serviceProvider: IServiceProvider; init(): Promise; dispose(): void; } @@ -24,7 +24,6 @@ export function createApp(...plugins: PluginManifest[]): IApplication { configure({ enforceActions: 'never' }); const app = new App(plugins); - const injector = app.getServiceInjector(); beforeAll(async () => { await app.start(); @@ -35,7 +34,9 @@ export function createApp(...plugins: PluginManifest[]): IApplication { return { app, - injector, + get serviceProvider() { + return app.getServiceProvider(); + }, async init() { await app.start(); }, diff --git a/webapp/packages/tests-runner/src/getService.ts b/webapp/packages/tests-runner/src/getService.ts index 9a40abaf4b..17d4ecb050 100644 --- a/webapp/packages/tests-runner/src/getService.ts +++ b/webapp/packages/tests-runner/src/getService.ts @@ -10,5 +10,5 @@ import type { IServiceConstructor } from '@cloudbeaver/core-di'; import type { IApplication } from './createApp'; export function getService(app: IApplication, ctor: IServiceConstructor): T { - return app.injector.getServiceByClass(ctor); + return app.serviceProvider.getService(ctor); } diff --git a/webapp/packages/tests-runner/src/renderInApp.tsx b/webapp/packages/tests-runner/src/renderInApp.tsx index 3acddc2cc1..a8310371d6 100644 --- a/webapp/packages/tests-runner/src/renderInApp.tsx +++ b/webapp/packages/tests-runner/src/renderInApp.tsx @@ -8,14 +8,14 @@ import { queries, Queries, render, RenderOptions, RenderResult } from '@testing-library/react'; import { Suspense } from 'react'; -import { AppContext, IServiceInjector } from '@cloudbeaver/core-di'; +import { IServiceProvider, ServiceProviderContext } from '@cloudbeaver/core-di'; import type { IApplication } from './createApp'; -function ApplicationWrapper(serviceInjector: IServiceInjector): React.FC { +function ApplicationWrapper(serviceInjector: IServiceProvider): React.FC { return ({ children }) => ( - {children} + {children} ); } @@ -28,5 +28,5 @@ export function renderInApp< app: IApplication, options?: Omit, 'queries' | 'wrapper'>, ): RenderResult { - return render(ui, { wrapper: ApplicationWrapper(app.injector), ...options }); + return render(ui, { wrapper: ApplicationWrapper(app.serviceProvider), ...options }); }