CB-3823 refactor: introduce service provider (#2774)

* CB-3823 refactor: introduce service provider

* CB-3823 fix: tests

* CB-5361 api for db tools

* CB-3823 feat: table group

* CB-3823 feat: connection tools resource

* CB-3823 refactor: database result action

* CB-3823 feat: session manager

* CB-3823 fix: data viewer error tracking

* CB-5361 add tools for rm admin

* CB-3823 fix: review

* CB-5361 fixes after review

* CB-5361 add comment

* CB-5361 fix permissions

* CB-3823 fix: service provider

* CB-3823 fix: useController services fallback

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
Alexey
2024-07-16 17:38:00 +02:00
committed by GitHub
co-authored by Ainur Evgenia Bezborodova
parent f5149c6324
commit b48684b65d
164 changed files with 1534 additions and 1255 deletions
+4 -15
View File
@@ -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",
@@ -24,12 +24,11 @@ import io.cloudbeaver.service.sql.WebDataFormat;
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.DBNBrowseSettings;
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 class WebConnectionInfo {
List<String> 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 class WebConnectionInfo {
.collect(Collectors.toList());
}
@NotNull
@Property
public List<String> getTools() {
if (!session.hasPermission(RMConstants.PERMISSION_DATABASE_DEVELOPER)) {
return List.of();
}
List<String> 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;
}
}
@@ -401,6 +401,8 @@ type ConnectionInfo {
projectId: ID!
requiredAuth: String
tools: [String!]! @since(version: "24.1.3")
}
type ConnectionFolderInfo {
@@ -21,6 +21,7 @@ import com.google.gson.GsonBuilder;
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.registry.WebAuthProviderRegistry;
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.code.Nullable;
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.DBNBrowseSettings;
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.DataSourceProviderRegistry;
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 class WebServiceUtils extends WebCommonUtils {
.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<WebPropertyInfo> 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]);
}
}
@@ -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 class WebDatabaseObjectInfo {
@Property
public WebPropertyInfo[] filterProperties(@Nullable WebPropertyFilter filter) {
PropertyCollector propertyCollector = new PropertyCollector(object, true);
propertyCollector.setLocale(session.getLocale());
propertyCollector.collectProperties();
List<WebPropertyInfo> 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);
}
///////////////////////////////////
@@ -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)));
@@ -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<TableItemGroupProps>(function TableItemGroup({ expanded = true, children }) {
const [internalExpanded, setExpanded] = useState(expanded);
return <TableItemGroupContext.Provider value={{ expanded: internalExpanded, setExpanded }}>{children}</TableItemGroupContext.Provider>;
});
@@ -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<TableItemGroupContentProps>(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}</>;
});
@@ -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<ITableItemGroupContext | undefined>(undefined);
@@ -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);
}
}
@@ -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<TableItemGroupExpandProps>(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 (
<div className={s(styles, { box: true }, className)}>
<Icon className={s(styles, { icon: true, expanded: context.expanded })} name="arrow" viewBox="0 0 16 16" onClick={handleClick} />
</div>
);
});
@@ -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<TableItemGroupExpandSpaceProps>(function TableItemGroupExpandSpaceSpace({ className }) {
const styles = useS(classes);
return <div className={s(styles, { box: true }, className)} />;
});
+5
View File
@@ -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';
@@ -26,7 +26,7 @@ export async function bootstrap(plugins: PluginManifest[]): Promise<App> {
}
const { renderLayout } = await import('./renderLayout');
const render = renderLayout(app.getServiceInjector());
const render = renderLayout(app.getServiceProvider());
const unmountExecutor = new SyncExecutor();
unmountExecutor.addHandler(() => render.unmount());
@@ -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(
<ErrorBoundary fallback={<HideAppLoadingScreen />} simple>
<AppContext app={serviceInjector}>
<ServiceProviderContext serviceProvider={serviceProvider}>
<ErrorBoundary fallback={<HideAppLoadingScreen />} root>
<Suspense fallback={<Loader className={s(styles, { loader: true })} />}>
<BodyLazy />
<HideAppLoadingScreen />
</Suspense>
</ErrorBoundary>
</AppContext>
</ServiceProviderContext>
</ErrorBoundary>,
);
},
@@ -65,10 +65,10 @@ export function renderLayout(serviceInjector: IServiceInjector): IRender {
}
this.initRoot().render(
<ErrorBoundary fallback={<HideAppLoadingScreen />} simple>
<AppContext app={serviceInjector}>
<ServiceProviderContext serviceProvider={serviceProvider}>
<DisplayError error={exception} root />
<HideAppLoadingScreen />
</AppContext>
</ServiceProviderContext>
</ErrorBoundary>,
);
},
@@ -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)));
@@ -641,9 +641,13 @@ export function compareNewConnectionsInfo(a: DatabaseConnection, b: DatabaseConn
return 0;
}
export function createConnectionParam(connection: Pick<Connection, 'id' | 'projectId'>): 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<Connection, 'id' | 'projectId'>,
connectionId?: string,
): IConnectionInfoParams {
if (typeof projectIdOrConnection === 'object') {
connectionId = projectIdOrConnection.id;
projectIdOrConnection = projectIdOrConnection.projectId;
@@ -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<IConnectionInfoParams, ConnectionTools> {
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<IConnectionInfoParams>): Promise<Map<IConnectionInfoParams, ConnectionTools>> {
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;
}
}
@@ -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';
@@ -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),
@@ -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<IServiceInjector>('DI Provider');
export const DATA_CONTEXT_DI_PROVIDER = createDataContext<IServiceProvider>('DI Provider');
@@ -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;
}
+8 -2
View File
@@ -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<void> {
if (!this.isAppServiceBound) {
this.getServiceCollection().addServiceByClass(App, this);
this.getServiceCollection().addServiceByClass(IServiceProvider, new IServiceProvider(this.diWrapper.injector));
this.isAppServiceBound = true;
}
@@ -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<IServiceInjector>(undefined as any);
export interface AppContextProps extends React.PropsWithChildren {
app: IServiceInjector;
}
export const AppContext: React.FC<AppContextProps> = function AppContext({ app, children }) {
return (
//<StrictMode> // problems with TabState when empty -> displayed state switch
<appContext.Provider value={app}>{children}</appContext.Provider>
//</StrictMode>
);
};
@@ -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<T>(ctor);
}
getServiceByToken<T>(token: InjectionToken<T>): T {
return this.container.get<T>(token);
}
resolveServiceByClass<T>(ctor: IServiceConstructor<T>): T {
return this.container.resolve(ctor);
}
@@ -115,12 +109,4 @@ export class DIContainer implements IServiceInjector, IServiceCollection {
this.container.bind(Ctor).toSelf();
}
}
addServiceByToken<T extends Record<string, any>>(token: InjectionToken<T>, value: T | IServiceConstructor<T>): void {
if (isConstructor(value)) {
this.container.bind(token).to(value as IServiceConstructor<T>);
} else {
this.container.bind(token).toConstantValue(value);
}
}
}
+4 -8
View File
@@ -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<any>): void {
context.addContextCreator(dependencyInjectorContext, this.dependencyInjectorContext);
}
private readonly dependencyInjectorContext: ISyncContextLoader<<T>(ctor: IServiceConstructor<T>) => T> = () =>
this.serviceInjector.getServiceByClass.bind(this);
this.serviceProvider.getService.bind(this.serviceProvider);
}
-3
View File
@@ -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: <T extends Record<string, any>>(token: any, value: T) => void;
addServiceByClass: (ctor: IServiceConstructor<any>, value?: any) => void;
unbindAll: () => void;
}
@@ -29,6 +27,5 @@ export type IServiceConstructor<T> = ITypedConstructor<T>;
export interface IServiceInjector {
hasServiceByClass: <T>(ctor: IServiceConstructor<T>) => boolean;
getServiceByClass: <T>(ctor: IServiceConstructor<T>) => T;
getServiceByToken: <T>(token: ValueToken<T>) => T;
resolveServiceByClass: <T>(ctor: IServiceConstructor<T>) => T;
}
@@ -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<T>(service: IServiceConstructor<T>): T {
return this.injector.getServiceByClass(service);
}
}
@@ -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> = () => T;
/**
* there are two types of tokens in the application: tokens for classes and tokens for values (class instances)
*/
export type InjectionToken<T> = ITypedConstructor<T> | ValueToken<T>;
export function createValueToken<T extends Record<string, any>>(obj: string | ITypedConstructor<T> | T): ValueToken<T> {
// 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';
}
@@ -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<IServiceProvider>(undefined as any);
export interface ServiceProviderContextProps extends React.PropsWithChildren {
serviceProvider: IServiceProvider;
}
export const ServiceProviderContext: React.FC<ServiceProviderContextProps> = function ServiceProviderContext({ serviceProvider, children }) {
return (
//<StrictMode> // problems with TabState when empty -> displayed state switch
<serviceProviderContext.Provider value={serviceProvider}>{children}</serviceProviderContext.Provider>
//</StrictMode>
);
};
@@ -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 })));
@@ -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);
@@ -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>('IServiceInjector');
+2 -3
View File
@@ -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';
+1 -8
View File
@@ -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<T>(token: ValueToken<T>): (target: DecoratorTarget<T>, targetKey: string, index?: number) => void {
return inversifyInject<T>(token);
}
@@ -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<T>(ctor: IServiceConstructor<T>): T {
return mainContainer.getServiceByClass(ctor);
},
getServiceByToken<T>(token: any): T {
return mainContainer.getServiceByToken(token);
},
resolveServiceByClass<T>(ctor: IServiceConstructor<T>): T {
return mainContainer.resolveServiceByClass(ctor);
},
@@ -37,10 +33,6 @@ export const inversifyWrapper: IDiWrapper = {
addServiceByClass(Ctor: IServiceConstructor<any>, value?: any): void {
mainContainer.addServiceByClass(Ctor, value);
},
addServiceByToken<T extends Record<string, unknown>>(token: ValueToken<T> | IServiceConstructor<T>, value: T): void {
mainContainer.addServiceByToken(token, value);
},
unbindAll() {
mainContainer.unbindAll();
},
+5 -4
View File
@@ -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<T>(ctor: IServiceConstructor<T>): T;
* @deprecated use hooks instead
*/
export function useController<T>(ctor: IServiceConstructor<T>, ...args: any[]): T {
const app = useContext(appContext);
const appService = useService(App);
const controllerRef = useRef<T>();
useMemo(() => {
@@ -30,7 +31,7 @@ export function useController<T>(ctor: IServiceConstructor<T>, ...args: any[]):
controllerRef.current.destruct();
}
const controller = app.resolveServiceByClass(ctor);
const controller = appService.getServiceInjector().resolveServiceByClass(ctor);
if (isInitializableController(controller)) {
controller.init(...args);
+9 -11
View File
@@ -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<T>(ctor: IServiceConstructor<T>): T;
export function useService<T>(ctor: IServiceConstructor<T>, optional: true): T | undefined;
export function useService<T>(ctor: IServiceConstructor<T>, 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<T>(token: ValueToken<T>): T {
const app = useContext(appContext);
return app.getServiceByToken(token);
return serviceProvider.getService(ctor);
}
@@ -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)));
@@ -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)));
@@ -0,0 +1,5 @@
query getConnectionsTools($projectId: ID, $connectionId: ID!) {
connections: userConnections(projectId: $projectId, id: $connectionId) {
...DatabaseConnectionTools
}
}
@@ -0,0 +1,5 @@
fragment DatabaseConnectionTools on ConnectionInfo {
id
projectId
tools
}
@@ -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)));
@@ -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)));
@@ -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<TState> implements IFormState<TState> {
readonly formatTask: IExecutor<IFormState<TState>>;
readonly validationTask: IExecutor<IFormState<TState>>;
constructor(app: App, service: FormBaseService<TState, any>, state: TState) {
constructor(serviceProvider: IServiceProvider, service: FormBaseService<TState, any>, state: TState) {
this.id = uuid();
this.service = service;
this.dataContext = new DataContext();
@@ -84,7 +84,7 @@ export class FormState<TState> implements IFormState<TState> {
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>(this, {
mode: observable,
-2
View File
@@ -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"
}
@@ -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<IUserFormState> {
constructor(app: App, service: AdministrationUserFormService, config: IUserFormState) {
super(app, service, config);
constructor(serviceProvider: IServiceProvider, service: AdministrationUserFormService, config: IUserFormState) {
super(serviceProvider, service, config);
}
}
@@ -21,8 +21,8 @@ export function getUserFormConnectionAccessPart(formState: IFormState<IUserFormS
const userFormInfoPart = getUserFormInfoPart(formState);
const di = context.get(DATA_CONTEXT_DI_PROVIDER)!;
const usersResource = di.getServiceByClass(UsersResource);
const projectInfoResource = di.getServiceByClass(ProjectInfoResource);
const usersResource = di.getService(UsersResource);
const projectInfoResource = di.getService(ProjectInfoResource);
return new UserFormConnectionAccessPart(formState, usersResource, projectInfoResource, userFormInfoPart);
});
@@ -18,9 +18,9 @@ const DATA_CONTEXT_USER_FORM_INFO_PART = createDataContext<UserFormInfoPart>('Us
export function getUserFormInfoPart(formState: IFormState<IUserFormState>): 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);
});
@@ -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<IToolsContainerProps>;
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();
}
@@ -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<TableItemExpandProps<string>>(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);
@@ -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"
@@ -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<IDataContainerOptions & IDataQueryOptions>(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<IDataContainerOptions & IDataQueryOptions>(source)) {
throw new Error('Result must be provided');
}
const source = model.source as IDatabaseDataSource<IDataContainerOptions & IDataQueryOptions>;
if (!source.options) {
throw new Error('Source options must be provided');
}
@@ -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": [
@@ -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"
@@ -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"
}
@@ -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');
@@ -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",
@@ -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);
@@ -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<IDatabaseDataOptions, IDatabaseResultSet>,
model: IDatabaseDataModel<ResultSetDataSource>,
resultIndex: number,
column: IResultSetColumnKey,
operator: string,
@@ -80,7 +83,8 @@ export class DataGridContextMenuFilterService {
isHidden?: (context: IMenuContext<IDataGridCellMenuContext>) => boolean,
): Array<IContextMenuItem<IDataGridCellMenuContext>> {
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<ResultSetDataSource>, 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<ResultSetDataSource>,
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<ResultSetDataSource>,
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<ResultSetDataSource>,
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();
@@ -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<IDatabaseDataOptions>(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();
});
@@ -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);
},
@@ -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<IDataPresentationProps<any, IDatabaseResultSet>>(function DataGridTable({
model,
actions,
resultIndex,
simple,
className,
}) {
export const DataGridTable = observer<IDataPresentationProps>(function DataGridTable({ model, actions, resultIndex, simple, className }) {
const translate = useTranslate();
const styles = useS(classes);
@@ -81,11 +77,11 @@ export const DataGridTable = observer<IDataPresentationProps<any, IDatabaseResul
);
const [columnResize] = useState(() => new Executor<IColumnResizeInfo>());
const selectionAction = model.source.getAction(resultIndex, ResultSetSelectAction);
const selectionAction = (model.source as unknown as ResultSetDataSource).getAction(resultIndex, ResultSetSelectAction);
const focusSyncRef = useRef<CellPosition | null>(null);
const tableData = useTableData(model, resultIndex, dataGridDivRef);
const tableData = useTableData(model as unknown as IDatabaseDataModel<ResultSetDataSource>, resultIndex, dataGridDivRef);
const editingContext = useEditing({
readonly: model.isReadonly(resultIndex) || model.isDisabled(resultIndex),
onEdit: (position, code, key) => {
@@ -176,7 +172,7 @@ export const DataGridTable = observer<IDataPresentationProps<any, IDatabaseResul
},
}));
const gridSelectedCellCopy = useGridSelectedCellsCopy(tableData, selectionAction, gridSelectionContext);
const gridSelectedCellCopy = useGridSelectedCellsCopy(tableData, selectionAction as unknown as DatabaseSelectAction, gridSelectionContext);
const { onMouseDownHandler, onMouseMoveHandler } = useGridDragging({
onDragStart: startPosition => {
hamdlers.selectCell({ idx: startPosition.colIdx, rowIdx: startPosition.rowIdx });
@@ -403,7 +399,7 @@ export const DataGridTable = observer<IDataPresentationProps<any, IDatabaseResul
return;
}
const result = model.getResult(resultIndex);
const result = model.source.getResult(resultIndex);
if (result?.loadedFully) {
return;
}
@@ -23,4 +23,8 @@
height: 100%;
box-sizing: border-box;
overflow: hidden;
&:empty {
display: none;
}
}
@@ -8,12 +8,12 @@
import { observer } from 'mobx-react-lite';
import { IconOrImage, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { DatabaseDataConstraintAction, EOrder, getNextOrder, IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
import { DatabaseDataConstraintAction, EOrder, getNextOrder, IDatabaseDataModel, ResultSetDataSource } from '@cloudbeaver/plugin-data-viewer';
import style from './OrderButton.module.css';
interface Props {
model: IDatabaseDataModel;
model: IDatabaseDataModel<ResultSetDataSource>;
resultIndex: number;
attributePosition: number;
className?: string;
@@ -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;
}
@@ -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<RenderHeaderCellProps<any>>(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<RenderHeaderCellProps<any>>(function T
}
}, [calculatedColumn]);
const hasIcon = icon || (!dataReadonly && columnReadOnly);
return (
<div ref={dnd.setRef} data-s-rearrange={dnd.side} className={s(styles, { header: true, dragging: dnd.data.state.isDragging })}>
<div title={columnTooltip} className={s(styles, { container: true })} onClick={handleClick}>
<div className={s(styles, { icon: true })}>
{icon && <StaticImage icon={icon} className={s(styles, { staticImage: true })} />}
{!dataReadonly && columnReadOnly && <div className={s(styles, { readonlyStatus: true }, 'rdg-table-header__readonly-status')} />}
</div>
{hasIcon && (
<div className={s(styles, { icon: true })}>
{icon && <StaticImage icon={icon} className={s(styles, { staticImage: true })} />}
{!dataReadonly && columnReadOnly && <div className={s(styles, { readonlyStatus: true }, 'rdg-table-header__readonly-status')} />}
</div>
)}
<div className={s(styles, { name: true })}>{columnName}</div>
</div>
{!sortingDisabled && resultColumn && <OrderButton model={model} resultIndex={resultIndex} attributePosition={resultColumn.position} />}
{!sortingDisabled && resultColumn && isResultSetDataModel(model) && (
<OrderButton model={model} resultIndex={resultIndex} attributePosition={resultColumn.position} />
)}
</div>
);
});
@@ -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);
@@ -43,7 +43,6 @@ export interface ITableData {
data: ResultSetDataAction;
editor: ResultSetEditAction;
view: ResultSetViewAction;
constraints: DatabaseDataConstraintAction;
columns: Array<Column<IResultSetRowKey, any>>;
columnKeys: IResultSetColumnKey[];
rows: IResultSetRowKey[];
@@ -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<string,
export function useGridSelectedCellsCopy(
tableData: ITableData,
resultSetSelectAction: ResultSetSelectAction,
selectAction: DatabaseSelectAction | undefined,
selectionContext: IDataGridSelectionContext,
) {
const dataViewerService = useService(DataViewerService);
const props = useObjectRef({ tableData, selectionContext, resultSetSelectAction });
const props = useObjectRef({ tableData, selectionContext, selectAction });
const copyEventHandler = useDataViewerCopyHandler();
const onKeydownHandler = useCallback((event: React.KeyboardEvent) => {
@@ -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) {
@@ -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<IResultSetRowKey, any> = {
};
export function useTableData(
model: IDatabaseDataModel<any, IDatabaseResultSet>,
model: IDatabaseDataModel<ResultSetDataSource>,
resultIndex: number,
gridDIVElement: React.RefObject<HTMLDivElement | null>,
): 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<ITableData & { gridDIVElement: React.RefObject<HTMLDivElement | null> }>(
() => ({
@@ -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,
},
);
@@ -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<any, IDatabaseResultSet>, resultIndex: number): ITableDataMeasurements {
export function useTableDataMeasurements(model: IDatabaseDataModel<ResultSetDataSource>, resultIndex: number): ITableDataMeasurements {
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
const view = model.source.getAction(resultIndex, ResultSetViewAction);
@@ -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)));
@@ -84,12 +84,6 @@
{
"path": "../core-storage/tsconfig.json"
},
{
"path": "../core-theming/tsconfig.json"
},
{
"path": "../core-theming/tsconfig.json"
},
{
"path": "../core-ui/tsconfig.json"
},
@@ -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<ResultSetDataSource>;
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<ResultSetDataSource>;
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,
@@ -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<any, IDatabaseResultSet> = 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<IDVResultSetGroupingPresentationState>(() => ({
presentationId: '',
valuePresentationId: null,
@@ -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<IDataGroupingOptions, IDatabaseResultSet>;
model: IDatabaseDataModel<GroupingDataSource>;
}
export function useGroupingDataModel(
sourceModel: IDatabaseDataModel<any, IDatabaseResultSet>,
sourceModel: IDatabaseDataModel<ResultSetDataSource>,
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;
}
@@ -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<any, IDatabaseResultSet>,
sourceModel: IDatabaseDataModel<any>,
groupingModel: IGroupingDataModel,
): IGroupingQueryResult {
async function dropItem(
model: IDatabaseDataModel<any, IDatabaseResultSet>,
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);
},
});
@@ -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"
@@ -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,
@@ -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<DynamicTraceProperty>[] = [
},
];
export const DVResultTraceDetailsPresentation: DataPresentationComponent<any, IDatabaseResultSet> = 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<IDatabaseDataOptions>(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 <TextPlaceholder>{translate('plugin_data_viewer_result_trace_no_data_placeholder')}</TextPlaceholder>;
}
if (!state.trace?.length) {
return <TextPlaceholder>{translate('plugin_data_viewer_result_trace_no_data_placeholder')}</TextPlaceholder>;
}
return (
<div className={s(styles, { container: true })}>
<DataGrid rows={state.trace} rowKeyGetter={row => row.name} columns={COLUMNS} rowHeight={30} />
</div>
);
},
);
return (
<div className={s(styles, { container: true })}>
<DataGrid rows={state.trace} rowKeyGetter={row => row.name} columns={COLUMNS} rowHeight={30} />
</div>
);
});
@@ -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<any, IDatabaseResultSet>;
model: IDatabaseDataModel<ResultSetDataSource>;
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<any, IDatabaseResultSet>, resultIndex: number) {
export function useResultTraceDetails(model: IDatabaseDataModel<ResultSetDataSource>, 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<any, IDatabaseRe
return this.trace !== undefined;
},
async load() {
const result = this.model.getResult(this.resultIndex);
const result = this.model.source.getResult(this.resultIndex);
try {
if (!result?.id) {
@@ -108,6 +108,7 @@ export function useResultTraceDetails(model: IDatabaseDataModel<any, IDatabaseRe
promise: computed,
exception: computed,
trace: computed,
model: observable.ref,
},
{ model, resultIndex, cache, metadataState },
);
@@ -18,12 +18,6 @@
{
"path": "../core-sdk/tsconfig.json"
},
{
"path": "../core-theming/tsconfig.json"
},
{
"path": "../core-theming/tsconfig.json"
},
{
"path": "../core-utils/tsconfig.json"
},
@@ -8,7 +8,7 @@
import { computed, makeObservable, observable } from 'mobx';
import type { ConnectionExecutionContextService, IConnectionExecutionContext, 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 {
AsyncTask,
@@ -44,12 +44,12 @@ export class ContainerDataSource extends ResultSetDataSource<IDataContainerOptio
}
constructor(
serviceInjector: IServiceInjector,
serviceProvider: IServiceProvider,
graphQLService: GraphQLService,
asyncTaskInfoService: AsyncTaskInfoService,
protected connectionExecutionContextService: ConnectionExecutionContextService,
) {
super(serviceInjector, graphQLService, asyncTaskInfoService);
super(serviceProvider, graphQLService, asyncTaskInfoService);
this.currentTask = null;
this.executionContext = null;
@@ -10,12 +10,11 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { TabProps } from '@cloudbeaver/core-ui';
import type { IDatabaseDataModel } from './DatabaseDataModel/IDatabaseDataModel';
import type { IDatabaseDataResult } from './DatabaseDataModel/IDatabaseDataResult';
import type { IDataTableActions } from './TableViewer/IDataTableActions';
export interface IDataPresentationProps<TOptions = any, TResult extends IDatabaseDataResult = IDatabaseDataResult> {
export interface IDataPresentationProps {
dataFormat: ResultDataFormat;
model: IDatabaseDataModel<TOptions, TResult>;
model: IDatabaseDataModel;
actions: IDataTableActions;
resultIndex: number;
simple: boolean;
@@ -27,9 +26,7 @@ export enum DataPresentationType {
toolsPanel,
}
export type DataPresentationComponent<TOptions = any, TResult extends IDatabaseDataResult = IDatabaseDataResult> = React.FunctionComponent<
IDataPresentationProps<TOptions, TResult>
>;
export type DataPresentationComponent = React.FunctionComponent<IDataPresentationProps>;
export type PresentationTabProps = TabProps & {
presentation: IDataPresentationOptions;
@@ -35,7 +35,7 @@ export class DataViewerDataChangeConfirmationService {
}
}
private async checkUnsavedData({ stage, model }: IRequestEventData<any, any>, contexts: IExecutionContextProvider<IRequestEventData<any, any>>) {
private async checkUnsavedData({ stage, model }: IRequestEventData, contexts: IExecutionContextProvider<IRequestEventData>) {
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 {
@@ -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<IObjectViewerTabState>) {
return;
}
let model = tableViewerStorageService.get(tab.handlerState.tableId || '');
let model = tableViewerStorageService.get<IDatabaseDataModel<ContainerDataSource>>(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();
}
@@ -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)));
@@ -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<IDataContainerOptions, IDatabaseResultSet> {
create(connection: Connection, node: NavNode | undefined): IDatabaseDataModel<ContainerDataSource> {
const nodeInfo = this.navNodeManagerService.getNodeContainerInfo(node?.id ?? '');
const source = new ContainerDataSource(
this.app.getServiceInjector(),
this.serviceProvider,
this.graphQLService,
this.asyncTaskInfoService,
this.connectionExecutionContextService,
@@ -20,6 +20,10 @@ export abstract class DatabaseDataResultAction<TKey, TResult extends IDatabaseDa
{
static dataFormat: ResultDataFormat[] | null = null;
get empty(): boolean {
return !this.result.data;
}
constructor(source: IDatabaseDataSource<any, TResult>) {
super(source);
}
@@ -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<TKey, TResult extends IDatabaseDataResult>
extends DatabaseDataAction<any, TResult>
implements IDatabaseDataMetadataAction<TKey, TResult>
{
export class DatabaseMetadataAction<TKey> extends DatabaseDataAction<any, any> implements IDatabaseDataMetadataAction<TKey, any> {
static dataFormat: ResultDataFormat[] | null = null;
readonly metadata: MetadataMap<string, any>;
constructor(source: IDatabaseDataSource<any, TResult>) {
constructor(source: IDatabaseDataSource<any, any>) {
super(source);
this.metadata = new MetadataMap();
}
@@ -15,7 +15,7 @@ import { databaseDataAction } from './DatabaseDataActionDecorator';
import type { DatabaseDataSelectActionsData, IDatabaseDataSelectAction } from './IDatabaseDataSelectAction';
@databaseDataAction()
export abstract class DatabaseSelectAction<TKey, TResult extends IDatabaseDataResult>
export abstract class DatabaseSelectAction<TKey = unknown, TResult extends IDatabaseDataResult = IDatabaseDataResult>
extends DatabaseDataAction<any, TResult>
implements IDatabaseDataSelectAction<TKey, TResult>
{
@@ -29,9 +29,11 @@ export abstract class DatabaseSelectAction<TKey, TResult extends IDatabaseDataRe
abstract isSelected(): boolean;
abstract isElementSelected(key: TKey): boolean;
abstract isFocused(key: TKey): boolean;
abstract getFocusedElement(): TKey | null;
abstract getSelectedElements(): TKey[];
abstract getActiveElements(): TKey[];
abstract set(key: TKey, selected: boolean): void;
abstract focus(key: TKey | null): void;
abstract clear(): void;
}
@@ -9,6 +9,7 @@ import type { IDatabaseDataAction } from '../IDatabaseDataAction';
import type { IDatabaseDataResult } from '../IDatabaseDataResult';
export interface IDatabaseDataResultAction<TKey, TResult extends IDatabaseDataResult> extends IDatabaseDataAction<any, TResult> {
readonly empty: boolean;
getIdentifier(key: TKey): string;
serialize(key: TKey): string;
}
@@ -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<IDatabaseDataModel<IDatabaseDataOptions>>('data-viewer-database-data-model');
export const DATA_CONTEXT_DV_DDM = createDataContext<IDatabaseDataModel>('data-viewer-database-data-model');
@@ -15,10 +15,6 @@ export abstract class DatabaseDataAction<TOptions, TResult extends IDatabaseData
result!: TResult;
resultIndex: number;
get empty(): boolean {
return !this.result.data;
}
readonly source: IDatabaseDataSource<TOptions, TResult>;
constructor(source: IDatabaseDataSource<TOptions, TResult>) {
@@ -59,7 +59,7 @@ export class DatabaseDataActions<TOptions, TResult extends IDatabaseDataResult>
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));
}
}
@@ -8,5 +8,5 @@
import type { DatabaseDataModel } from './DatabaseDataModel';
export interface DatabaseDataFormat<T> {
processResult: (model: DatabaseDataModel<any, any>, data: T) => void;
processResult: (model: DatabaseDataModel<any>, data: T) => void;
}
@@ -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<TOptions, TResult extends IDatabaseDataResult = IDatabaseDataResult> implements IDatabaseDataModel<TOptions, TResult> {
export class DatabaseDataModel<TSource extends IDatabaseDataSource<any, any> = IDatabaseDataSource> implements IDatabaseDataModel<TSource> {
id: string;
name: string | null;
source: IDatabaseDataSource<TOptions, TResult>;
source: TSource;
countGain: number;
get requestInfo(): IRequestInfo {
@@ -31,9 +30,9 @@ export class DatabaseDataModel<TOptions, TResult extends IDatabaseDataResult = I
readonly onDispose: IExecutor;
readonly onOptionsChange: IExecutor;
readonly onRequest: IExecutor<IRequestEventData<TOptions, TResult>>;
readonly onRequest: IExecutor<IRequestEventData<TSource>>;
constructor(source: IDatabaseDataSource<TOptions, TResult>) {
constructor(source: TSource) {
this.id = uuid();
this.name = null;
this.source = source;
@@ -52,7 +51,7 @@ export class DatabaseDataModel<TOptions, TResult extends IDatabaseDataResult = I
return this.source.isLoading();
}
isDisabled(resultIndex: number): boolean {
isDisabled(resultIndex?: number): boolean {
return this.source.isDisabled(resultIndex);
}
@@ -64,24 +63,11 @@ export class DatabaseDataModel<TOptions, TResult extends IDatabaseDataResult = I
return this.source.isDataAvailable(offset, count);
}
getResults(): TResult[] {
return this.source.results;
}
getResult(index: number): TResult | null {
return this.source.getResult(index);
}
setName(name: string | null) {
this.name = name;
return this;
}
setResults(results: TResult[]): this {
this.source.setResults(results);
return this;
}
setAccess(access: DatabaseDataAccessMode): this {
this.source.setAccess(access);
return this;
@@ -107,11 +93,6 @@ export class DatabaseDataModel<TOptions, TResult extends IDatabaseDataResult = I
return this;
}
setOptions(options: TOptions): this {
this.source.setOptions(options);
return this;
}
async requestOptionsChange(): Promise<boolean> {
const contexts = await this.onOptionsChange.execute();
@@ -152,8 +133,8 @@ export class DatabaseDataModel<TOptions, TResult extends IDatabaseDataResult = I
this.source.resetData();
}
async dispose(keepExecutionContext = false): Promise<void> {
async dispose(): Promise<void> {
await this.onDispose.execute();
await this.source.dispose(keepExecutionContext);
await this.source.dispose();
}
}
@@ -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<TOptions, TResult extends IDatabaseData
options: TOptions | null;
requestInfo: IRequestInfo;
error: Error | null;
executionContext: IConnectionExecutionContext | null;
totalCountRequestTask: ITask<number> | null;
get canCancel(): boolean {
if (this.activeOperation instanceof Task) {
@@ -56,7 +53,7 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
return false;
}
readonly serviceInjector: IServiceInjector;
readonly serviceProvider: IServiceProvider;
protected disabled: boolean;
private lastAction: () => Promise<void>;
private outdated: boolean;
@@ -67,9 +64,8 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
}
private readonly activeOperationStack: Array<Promise<any>>;
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<TOptions, TResult extends IDatabaseData
this.prevOptions = null;
this.options = null;
this.disabled = false;
this.outdated = false;
this.outdated = true;
this.constraintsAvailable = true;
this.executionContext = null;
this.onOperation = new Executor();
this.dataFormat = ResultDataFormat.Resultset;
this.supportedDataFormats = [];
@@ -105,9 +100,7 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
prevOptions: observable,
options: observable,
requestInfo: observable,
totalCountRequestTask: observable.ref,
error: observable.ref,
executionContext: observable,
disabled: observable,
constraintsAvailable: observable.ref,
outdated: observable.ref,
@@ -118,18 +111,9 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
});
}
tryGetAction<T extends IDatabaseDataAction<TOptions, TResult>>(
resultIndex: number,
action: IDatabaseDataActionClass<TOptions, TResult, T>,
): T | undefined;
tryGetAction<T extends IDatabaseDataAction<TOptions, TResult>>(
result: TResult,
action: IDatabaseDataActionClass<TOptions, TResult, T>,
): T | undefined;
tryGetAction<T extends IDatabaseDataAction<TOptions, TResult>>(
resultIndex: number | TResult,
action: IDatabaseDataActionClass<TOptions, TResult, T>,
): T | undefined {
tryGetAction<T extends IDatabaseDataActionClass<TOptions, TResult, any>>(resultIndex: number, action: T): InstanceType<T> | undefined;
tryGetAction<T extends IDatabaseDataActionClass<TOptions, TResult, any>>(result: TResult, action: T): InstanceType<T> | undefined;
tryGetAction<T extends IDatabaseDataActionClass<TOptions, TResult, any>>(resultIndex: number | TResult, action: T): InstanceType<T> | undefined {
if (typeof resultIndex === 'number') {
if (!this.hasResult(resultIndex)) {
return undefined;
@@ -140,12 +124,9 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
return this.actions.tryGet(resultIndex, action);
}
getAction<T extends IDatabaseDataAction<TOptions, TResult>>(resultIndex: number, action: IDatabaseDataActionClass<TOptions, TResult, T>): T;
getAction<T extends IDatabaseDataAction<TOptions, TResult>>(result: TResult, action: IDatabaseDataActionClass<TOptions, TResult, T>): T;
getAction<T extends IDatabaseDataAction<TOptions, TResult>>(
resultIndex: number | TResult,
action: IDatabaseDataActionClass<TOptions, TResult, T>,
): T {
getAction<T extends IDatabaseDataActionClass<TOptions, TResult, any>>(resultIndex: number, action: T): InstanceType<T>;
getAction<T extends IDatabaseDataActionClass<TOptions, TResult, any>>(result: TResult, action: T): InstanceType<T>;
getAction<T extends IDatabaseDataActionClass<TOptions, TResult, any>>(resultIndex: number | TResult, action: T): InstanceType<T> {
if (typeof resultIndex === 'number') {
if (!this.hasResult(resultIndex)) {
throw new Error('Result index out of range');
@@ -156,18 +137,15 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
return this.actions.get(resultIndex, action);
}
getActionImplementation<T extends IDatabaseDataAction<TOptions, TResult>>(
getActionImplementation<T extends IDatabaseDataActionInterface<TOptions, TResult, any>>(
resultIndex: number,
action: IDatabaseDataActionInterface<TOptions, TResult, T>,
): T | undefined;
getActionImplementation<T extends IDatabaseDataAction<TOptions, TResult>>(
result: TResult,
action: IDatabaseDataActionInterface<TOptions, TResult, T>,
): T | undefined;
getActionImplementation<T extends IDatabaseDataAction<TOptions, TResult>>(
action: T,
): InstanceType<T> | undefined;
getActionImplementation<T extends IDatabaseDataActionInterface<TOptions, TResult, any>>(result: TResult, action: T): InstanceType<T> | undefined;
getActionImplementation<T extends IDatabaseDataActionInterface<TOptions, TResult, any>>(
resultIndex: number | TResult,
action: IDatabaseDataActionInterface<TOptions, TResult, T>,
): T | undefined {
action: T,
): InstanceType<T> | undefined {
if (typeof resultIndex === 'number') {
if (!this.hasResult(resultIndex)) {
return undefined;
@@ -196,6 +174,10 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
return null;
}
getResults(): TResult[] {
return this.results;
}
setOutdated(): this {
this.outdated = true;
return this;
@@ -208,6 +190,10 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
return this;
}
isError(): boolean {
return this.error !== null;
}
isOutdated(): boolean {
return this.outdated;
}
@@ -221,15 +207,18 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
}
isReadonly(resultIndex: number): boolean {
return this.access === DatabaseDataAccessMode.Readonly || this.results.length > 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<TOptions, TResult extends IDatabaseData
return this;
}
setExecutionContext(context: IConnectionExecutionContext | null): this {
this.executionContext = context;
this.setOutdated();
return this;
}
setTotalCount(resultIndex: number, count: number): this {
const result = this.getResult(resultIndex);
if (result) {
result.totalCount = count;
}
setError(error: Error): this {
this.error = error;
return this;
}
@@ -354,29 +333,25 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
return this;
}
async dispose(keepExecutionContext = false): Promise<void> {
async dispose(): Promise<void> {
await this.cancel();
if (!keepExecutionContext) {
await this.executionContext?.destroy();
}
}
abstract request(prevResults: TResult[]): Promise<TResult[]>;
abstract save(prevResults: TResult[]): Promise<TResult[]>;
abstract loadTotalCount(resultIndex: number): Promise<ITask<number>>;
abstract cancelLoadTotalCount(): Promise<ITask<number> | null>;
private async requestDataAction(): Promise<TResult[]> {
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<T>(type: DatabaseDataSourceOperation, operation: () => Promise<T>): Promise<T | null> {
@@ -41,7 +41,6 @@ export type IDatabaseDataActionClass<
export interface IDatabaseDataAction<TOptions, TResult extends IDatabaseDataResult> {
readonly source: IDatabaseDataSource<TOptions, TResult>;
readonly empty: boolean;
result: TResult;
resultIndex: number;
updateResult: (result: TResult, index: number) => void;

Some files were not shown because too many files have changed in this diff Show More