mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
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:
co-authored by
Ainur
Evgenia Bezborodova
parent
f5149c6324
commit
b48684b65d
Vendored
+4
-15
@@ -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",
|
||||
|
||||
+38
-10
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-23
@@ -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)} />;
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -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');
|
||||
@@ -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';
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
+3
-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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
});
|
||||
|
||||
+3
-3
@@ -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);
|
||||
});
|
||||
|
||||
+3
-3
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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",
|
||||
|
||||
+48
-31
@@ -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);
|
||||
|
||||
+77
-39
@@ -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();
|
||||
|
||||
+29
-15
@@ -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();
|
||||
});
|
||||
|
||||
+16
-8
@@ -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;
|
||||
}
|
||||
|
||||
+4
@@ -23,4 +23,8 @@
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+18
-6
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+7
-1
@@ -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[];
|
||||
|
||||
+8
-3
@@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
+2
-2
@@ -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"
|
||||
},
|
||||
|
||||
+11
-5
@@ -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,
|
||||
|
||||
+5
-2
@@ -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,
|
||||
|
||||
+16
-17
@@ -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;
|
||||
}
|
||||
|
||||
+10
-14
@@ -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"
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+21
-17
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+5
-4
@@ -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,
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
|
||||
+2
-6
@@ -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();
|
||||
}
|
||||
|
||||
+3
-1
@@ -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;
|
||||
}
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
+1
-2
@@ -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
Reference in New Issue
Block a user