CB-5197 create main connection fields dynamically (#2693)

* CB-5238 add main properties for connections

* CB-5197 render main properties

* CB-5197 render main properties

* CB-5238 set default values to main properties

* CB-5197 fix(ci/cd): respect base and target branches

* CB-5238 fixes after review

* CB-5197 review fixes

* CB-5197 switch to new model

* CB-5238 fixes after review

* CB-5197 remove custom configuration type

* CB-5238 remove custom config type from gql schema

* CB-5197 do not check configuration type for creds

* CB-5197 make argument optional

* CB-5238 fix creating connection with url

* CB-5238 rename param

* CB-5238 rename param

* CB-5197 rename field

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Alexey <wrouds@gmail.com>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
Ainur
2024-07-01 12:45:57 +00:00
committed by GitHub
co-authored by naumov Alexey Evgenia Bezborodova
parent 4433018ed2
commit 49875e4dc1
21 changed files with 305 additions and 159 deletions
+11
View File
@@ -32,6 +32,17 @@ jobs:
sudo chmod 777 ../
shell: bash
- name: Determine branches
id: determine-branch
run: |
echo "pr_branch=${{ github.head_ref }}" >> $GITHUB_ENV
echo "base_branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_ENV
- name: Clone dbeaver/dbeaver
id: clone-repo
run: |
git clone -b ${{ env.pr_branch }} https://github.com/dbeaver/dbeaver.git ../dbeaver || git clone -b ${{ env.base_branch }} https://github.com/dbeaver/dbeaver.git ../dbeaver
- name: Run build script
run: ./build-backend.sh
shell: bash
@@ -384,6 +384,16 @@ public class WebConnectionInfo {
}
}
@Property
public Map<String, String> getMainPropertyValues() {
Map<String, String> mainProperties = new LinkedHashMap<>();
mainProperties.put(DBConstants.PROP_HOST, getHost());
mainProperties.put(DBConstants.PROP_PORT, getPort());
mainProperties.put(DBConstants.PROP_DATABASE, getDatabaseName());
mainProperties.put(DBConstants.PROP_SERVER, getServerName());
return mainProperties;
}
@Property
public Map<String, String> getProviderProperties() {
return dataSourceContainer.getConnectionConfiguration().getProviderProperties();
@@ -229,6 +229,7 @@ type DriverInfo {
enabled: Boolean!
requiresServerName: Boolean
requiresDatabaseName: Boolean
useCustomPage: Boolean! @since(version: "24.1.2") # if host, port, database, server name fields are custom
licenseRequired: Boolean
license: String
@@ -248,6 +249,10 @@ type DriverInfo {
# Driver parameters (map name->value)
driverParameters: Object!
# Main driver properties
# Contains info about main fields (host, port, database, server name)
mainProperties: [ObjectPropertyInfo!]! @since(version: "24.1.2")
# Additional driver provider properties
# These properties can be configured by user on main connection page
# to provide important connection settings
@@ -340,6 +345,8 @@ type ConnectionInfo {
databaseName: String
url: String
mainPropertyValues: Object @since(version: "24.1.2")
keepAliveInterval: Int!
autocommit: Boolean
@@ -499,6 +506,10 @@ input ConnectionConfig {
port: String
serverName: String
databaseName: String
# Host, port, serverName, databaseName are also stored in mainPropertyValues for custom pages
mainPropertyValues: Object @since(version: "24.1.2")
# Connection url jdbc:{driver}://{host}[:{port}]/[{database}]
url: String
# Properties
@@ -33,6 +33,7 @@ import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
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.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
@@ -150,22 +151,7 @@ public class WebServiceUtils extends WebCommonUtils {
}
public static void setConnectionConfiguration(DBPDriver driver, DBPConnectionConfiguration dsConfig, WebConnectionConfig config) {
if (!CommonUtils.isEmpty(config.getUrl())) {
dsConfig.setUrl(config.getUrl());
} else {
if (config.getHost() != null) {
dsConfig.setHostName(config.getHost());
}
if (config.getPort() != null) {
dsConfig.setHostPort(config.getPort());
}
if (config.getDatabaseName() != null) {
dsConfig.setDatabaseName(config.getDatabaseName());
}
if (config.getServerName() != null) {
dsConfig.setServerName(config.getServerName());
}
}
setMainProperties(dsConfig, config);
if (config.getProperties() != null) {
Map<String, String> newProps = new LinkedHashMap<>();
for (Map.Entry<String, Object> pe : config.getProperties().entrySet()) {
@@ -222,6 +208,37 @@ public class WebServiceUtils extends WebCommonUtils {
}
}
private static void setMainProperties(DBPConnectionConfiguration dsConfig, WebConnectionConfig config) {
if (CommonUtils.isNotEmpty(config.getUrl())) {
dsConfig.setUrl(config.getUrl());
return;
}
if (config.getMainPropertyValues() != null) {
for (Map.Entry<String, Object> e : config.getMainPropertyValues().entrySet()) {
switch (e.getKey()) {
case DBConstants.PROP_HOST -> dsConfig.setHostName(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_PORT -> dsConfig.setHostPort(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_DATABASE -> dsConfig.setDatabaseName(CommonUtils.toString(e.getValue()));
case DBConstants.PROP_SERVER -> dsConfig.setServerName(CommonUtils.toString(e.getValue()));
default -> throw new IllegalStateException("Unexpected value: " + e.getKey());
}
}
return;
}
if (config.getHost() != null) {
dsConfig.setHostName(config.getHost());
}
if (config.getPort() != null) {
dsConfig.setHostPort(config.getPort());
}
if (config.getDatabaseName() != null) {
dsConfig.setDatabaseName(config.getDatabaseName());
}
if (config.getServerName() != null) {
dsConfig.setServerName(config.getServerName());
}
}
public static void saveAuthProperties(
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@@ -57,6 +57,7 @@ public class WebConnectionConfig {
private Map<String, Object> credentials;
private boolean saveCredentials;
private boolean sharedCredentials;
private Map<String, Object> mainPropertyValues;
private Map<String, Object> providerProperties;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
@@ -102,6 +103,7 @@ public class WebConnectionConfig {
saveCredentials = JSONUtils.getBoolean(params, "saveCredentials");
sharedCredentials = JSONUtils.getBoolean(params, "sharedCredentials");
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
String configType = JSONUtils.getString(params, "configurationType");
@@ -227,6 +229,11 @@ public class WebConnectionConfig {
this.saveCredentials = saveCredentials;
}
@Property
public Map<String, Object> getMainPropertyValues() {
return mainPropertyValues;
}
@Property
public Map<String, Object> getProviderProperties() {
return providerProperties;
@@ -36,9 +36,11 @@ import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.runtime.properties.PropertySourceCustom;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -233,6 +235,22 @@ public class WebDatabaseDriverInfo {
return AuthModelDatabaseNative.ID;
}
@Property
public WebPropertyInfo[] getMainProperties() {
DBPPropertyDescriptor[] properties = driver.getMainPropertyDescriptors();
// set default values to main properties
Map<String, String> defaultValues = new LinkedHashMap<>();
defaultValues.put(DBConstants.PROP_HOST, getDefaultHost());
defaultValues.put(DBConstants.PROP_PORT, getDefaultPort());
defaultValues.put(DBConstants.PROP_DATABASE, getDefaultDatabase());
defaultValues.put(DBConstants.PROP_SERVER, getDefaultServer());
PropertySourceCustom propertySource = new PropertySourceCustom(properties, defaultValues);
return Arrays.stream(properties)
.map(p -> new WebPropertyInfo(webSession, p, propertySource))
.toArray(WebPropertyInfo[]::new);
}
@Property
public WebPropertyInfo[] getProviderProperties() {
return Arrays.stream(driver.getProviderPropertyDescriptors())
@@ -276,4 +294,9 @@ public class WebDatabaseDriverInfo {
.map(dbpDriverLibrary -> new WebDriverLibraryInfo(webSession, dbpDriverLibrary))
.toArray(WebDriverLibraryInfo[]::new);
}
@Property
public boolean getUseCustomPage() {
return !ArrayUtils.isEmpty(driver.getMainPropertyDescriptors());
}
}
@@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
import { getObjectPropertyType, type ObjectPropertyInfo, type ObjectPropertyType } from '@cloudbeaver/core-sdk';
import { getObjectPropertyType, getObjectPropertyValueType, type ObjectPropertyInfo, type ObjectPropertyType } from '@cloudbeaver/core-sdk';
import { removeMetadataFromDataURL } from '@cloudbeaver/core-utils';
import { FieldCheckbox } from '../../FormControls/Checkboxes/FieldCheckbox';
@@ -70,7 +70,8 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
const translate = useTranslate();
const controlType = getObjectPropertyType(property);
const password = property.features.includes('password');
const type = getObjectPropertyValueType(property);
const isPassword = type === 'password';
const required = property.required && !readOnly;
const value = getValue(property.value, controlType);
@@ -172,7 +173,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
);
}
const passwordSaved = showRememberTip && ((password && !!property.value) || saved);
const passwordSaved = showRememberTip && ((isPassword && !!property.value) || saved);
const passwordSavedMessage = passwordSaved ? translate('core_blocks_object_property_info_password_saved') : undefined;
if (controlType === 'file' && state) {
@@ -233,8 +234,8 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
return (
<InputField
required={required}
type={password ? 'password' : 'text'}
title={password ? property.description || property.displayName : undefined}
type={type}
title={isPassword ? property.description || property.displayName : undefined}
labelTooltip={property.description || property.displayName}
name={property.id!}
state={state}
@@ -257,8 +258,8 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
return (
<InputField
required={required}
type={password ? 'password' : 'text'}
title={password ? property.description || property.displayName : undefined}
type={type}
title={isPassword ? property.description || property.displayName : undefined}
labelTooltip={property.description || property.displayName}
name={property.id!}
value={value}
@@ -112,6 +112,7 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, DBDriv
includeDriverLibraries: false,
includeDriverParameters: false,
includeDriverProperties: false,
includeMainProperties: false,
includeProviderProperties: false,
};
}
@@ -7,7 +7,7 @@
*/
import type { ObjectPropertyInfo } from './sdk';
export type ObjectPropertyType = 'checkbox' | 'selector' | 'link' | 'input' | 'textarea' | 'file';
export type ObjectPropertyType = 'checkbox' | 'selector' | 'link' | 'textarea' | 'file' | 'input';
export function getObjectPropertyType(property: ObjectPropertyInfo): ObjectPropertyType {
const dataType = property.dataType?.toLowerCase();
@@ -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 type { ObjectPropertyInfo } from './sdk';
export type ObjectPropertyValueType = 'password' | 'text' | 'number';
export function getObjectPropertyValueType(property: ObjectPropertyInfo): ObjectPropertyValueType | undefined {
const dataType = property.dataType?.toLowerCase();
if (property.features.includes('password')) {
return 'password';
}
if (dataType === 'integer') {
return 'number';
}
return dataType === 'string' ? 'text' : undefined;
}
+1
View File
@@ -14,6 +14,7 @@ export * from './EnvironmentService';
export * from './EServerErrorCode';
export * from './getGQLResponse';
export * from './getObjectPropertyType';
export * from './getObjectPropertyValueType';
export * from './GQLError';
export * from './GQLErrorCatcher';
export * from './GraphQLService';
@@ -1,4 +1,11 @@
query driverList($driverId: ID, $includeProviderProperties: Boolean!, $includeDriverProperties: Boolean!, $includeDriverParameters: Boolean!, $includeDriverLibraries: Boolean!) {
query driverList(
$driverId: ID
$includeProviderProperties: Boolean!
$includeMainProperties: Boolean!
$includeDriverProperties: Boolean!
$includeDriverParameters: Boolean!
$includeDriverLibraries: Boolean!
) {
drivers: driverList(id: $driverId) {
...DatabaseDriver
}
@@ -18,6 +18,8 @@ fragment DatabaseConnection on ConnectionInfo {
folder
nodePath
mainPropertyValues
configurationType @include(if: $customIncludeOptions)
useUrl @include(if: $customIncludeOptions)
host @include(if: $customIncludeOptions)
@@ -11,6 +11,7 @@ fragment DatabaseDriver on DriverInfo {
embedded
enabled
requiresServerName
useCustomPage
anonymousAccess
promotedScore
providerId
@@ -22,8 +23,12 @@ fragment DatabaseDriver on DriverInfo {
applicableNetworkHandlers
configurationTypes
mainProperties @include(if: $includeMainProperties) {
...DriverPropertyInfo
}
providerProperties @include(if: $includeProviderProperties) {
...DriverProviderPropertyInfo
...DriverPropertyInfo
}
driverProperties @include(if: $includeDriverProperties) {
@@ -42,4 +47,4 @@ fragment DatabaseDriver on DriverInfo {
name
icon
}
}
}
@@ -0,0 +1,4 @@
fragment DriverPropertyInfo on ObjectPropertyInfo {
...ObjectPropertyInfo
supportedConfigurationTypes
}
@@ -1,14 +0,0 @@
fragment DriverProviderPropertyInfo on ObjectPropertyInfo {
id
displayName
description
category
dataType
defaultValue
validValues
length
features
order
supportedConfigurationTypes
required
}
@@ -21,7 +21,6 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { isSharedProject, ProjectInfoResource } from '@cloudbeaver/core-projects';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { DriverConfigurationType, isObjectPropertyInfoStateEqual, ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import { formStateContext } from '@cloudbeaver/core-ui';
import { getUniqueName, isNotNullDefined, isValuesEqual } from '@cloudbeaver/core-utils';
@@ -32,16 +31,21 @@ import { connectionConfigContext } from '../Contexts/connectionConfigContext';
import { connectionCredentialsStateContext } from '../Contexts/connectionCredentialsStateContext';
import type { IConnectionFormFillConfigData, IConnectionFormState, IConnectionFormSubmitData } from '../IConnectionFormProps';
import { getConnectionName } from './getConnectionName';
import { getDefaultConfigurationType } from './getDefaultConfigurationType';
export const Options = React.lazy(async () => {
const { Options } = await import('./Options');
return { default: Options };
});
const MAIN_PROPERTY_DATABASE_KEY = 'database';
const MAIN_PROPERTY_HOST_KEY = 'host';
const MAIN_PROPERTY_PORT_KEY = 'port';
const MAIN_PROPERTY_SERVER_KEY = 'server';
@injectable()
export class ConnectionOptionsTabService extends Bootstrap {
constructor(
private readonly serverConfigResource: ServerConfigResource,
private readonly projectInfoResource: ProjectInfoResource,
private readonly connectionFormService: ConnectionFormService,
private readonly dbDriverResource: DBDriverResource,
@@ -174,10 +178,7 @@ export class ConnectionOptionsTabService extends Bootstrap {
const driver = await this.dbDriverResource.load(state.config.driverId, ['includeProviderProperties']);
state.config.authModelId = driver?.defaultAuthModel;
state.config.configurationType = driver?.configurationTypes.includes(DriverConfigurationType.Manual)
? DriverConfigurationType.Manual
: DriverConfigurationType.Url;
state.config.configurationType = getDefaultConfigurationType(driver);
state.config.host = driver?.defaultServer || 'localhost';
state.config.port = driver?.defaultPort;
@@ -205,6 +206,10 @@ export class ConnectionOptionsTabService extends Bootstrap {
state.config.providerProperties = {};
}
if (!state.config.mainPropertyValues || updated) {
state.config.mainPropertyValues = {};
}
if (!state.info) {
await this.setDefaults(state);
return;
@@ -218,10 +223,11 @@ export class ConnectionOptionsTabService extends Bootstrap {
state.config.template = state.info.template;
state.config.driverId = state.info.driverId;
state.config.host = state.info.host;
state.config.port = state.info.port;
state.config.serverName = state.info.serverName;
state.config.databaseName = state.info.databaseName;
state.config.host = state.info.mainPropertyValues[MAIN_PROPERTY_HOST_KEY];
state.config.port = state.info.mainPropertyValues[MAIN_PROPERTY_PORT_KEY];
state.config.serverName = state.info.mainPropertyValues[MAIN_PROPERTY_SERVER_KEY];
state.config.databaseName = state.info.mainPropertyValues[MAIN_PROPERTY_DATABASE_KEY];
state.config.url = state.info.url;
state.config.folder = state.info.folder;
@@ -243,6 +249,10 @@ export class ConnectionOptionsTabService extends Bootstrap {
if (state.info.providerProperties) {
state.config.providerProperties = { ...state.info.providerProperties };
}
if (state.info.mainPropertyValues) {
state.config.mainPropertyValues = { ...state.info.mainPropertyValues };
}
}
private configure(data: IConnectionFormState, contexts: IExecutionContextProvider<IConnectionFormState>) {
@@ -251,19 +261,6 @@ export class ConnectionOptionsTabService extends Bootstrap {
configuration.include('includeOrigin', 'includeAuthProperties', 'includeCredentialsSaved', 'customIncludeOptions');
}
private getTrimmedPropertiesConfig(authProperties: ObjectPropertyInfo[], credentials: Record<string, any>): Record<string, any> {
const trimmedProperties: Record<string, any> = toJS(credentials);
for (const property of authProperties) {
const value = credentials?.[property.id!];
if (typeof value === 'string' && value) {
trimmedProperties[property.id!] = value?.trim();
}
}
return trimmedProperties;
}
private async prepareConfig({ state }: IConnectionFormSubmitData, contexts: IExecutionContextProvider<IConnectionFormSubmitData>) {
const config = contexts.getContext(connectionConfigContext);
const credentialsState = contexts.getContext(connectionCredentialsStateContext);
@@ -272,7 +269,7 @@ export class ConnectionOptionsTabService extends Bootstrap {
return;
}
const driver = await this.dbDriverResource.load(state.config.driverId, ['includeProviderProperties']);
const driver = await this.dbDriverResource.load(state.config.driverId, ['includeProviderProperties', 'includeMainProperties']);
const tempConfig = toJS(config);
if (state.mode === 'edit') {
@@ -280,7 +277,6 @@ export class ConnectionOptionsTabService extends Bootstrap {
}
tempConfig.configurationType = state.config.configurationType;
tempConfig.name = state.config.name?.trim();
if (tempConfig.name && state.mode === 'create') {
@@ -291,11 +287,8 @@ export class ConnectionOptionsTabService extends Bootstrap {
}
tempConfig.description = state.config.description?.trim();
tempConfig.template = state.config.template;
tempConfig.driverId = state.config.driverId;
tempConfig.keepAliveInterval = Number(state.config.keepAliveInterval);
tempConfig.autocommit = state.config.autocommit;
@@ -305,15 +298,21 @@ export class ConnectionOptionsTabService extends Bootstrap {
if (tempConfig.configurationType === DriverConfigurationType.Url) {
tempConfig.url = state.config.url?.trim();
} else {
}
tempConfig.mainPropertyValues = toJS(state.config.mainPropertyValues);
if (tempConfig.configurationType === DriverConfigurationType.Manual && !driver.useCustomPage) {
tempConfig.mainPropertyValues[MAIN_PROPERTY_DATABASE_KEY] = state.config.databaseName?.trim();
if (!driver.embedded) {
tempConfig.host = state.config.host?.trim();
tempConfig.port = state.config.port?.trim();
tempConfig.mainPropertyValues[MAIN_PROPERTY_HOST_KEY] = state.config.host?.trim();
tempConfig.mainPropertyValues[MAIN_PROPERTY_PORT_KEY] = state.config.port?.trim();
}
if (driver.requiresServerName) {
tempConfig.serverName = state.config.serverName?.trim();
tempConfig.mainPropertyValues[MAIN_PROPERTY_SERVER_KEY] = state.config.serverName?.trim();
}
tempConfig.databaseName = state.config.databaseName?.trim();
}
if ((state.config.authModelId || driver.defaultAuthModel) && !driver.anonymousAccess) {
@@ -324,7 +323,7 @@ export class ConnectionOptionsTabService extends Bootstrap {
const properties = await this.getConnectionAuthModelProperties(tempConfig.authModelId, state.info);
if (this.isCredentialsChanged(properties, state.config.credentials)) {
tempConfig.credentials = this.getTrimmedPropertiesConfig(properties, { ...state.config.credentials });
tempConfig.credentials = this.prepareDynamicProperties(properties, toJS(state.config.credentials));
}
if (!tempConfig.saveCredentials) {
@@ -333,32 +332,19 @@ export class ConnectionOptionsTabService extends Bootstrap {
}
if (driver.providerProperties.length > 0) {
const providerProperties: Record<string, any> = { ...state.config.providerProperties };
tempConfig.providerProperties = this.prepareDynamicProperties(
driver.providerProperties,
toJS(state.config.providerProperties),
tempConfig.configurationType,
);
}
for (const providerProperty of driver.providerProperties) {
if (!providerProperty.id) {
continue;
}
const supported = providerProperty.supportedConfigurationTypes?.some(t => t === tempConfig.configurationType);
if (!supported) {
delete providerProperties[providerProperty.id];
} else {
const isDefault = isNotNullDefined(providerProperty.defaultValue);
if (!(providerProperty.id in providerProperties) && isDefault) {
providerProperties[providerProperty.id] = providerProperty.defaultValue;
}
}
}
tempConfig.providerProperties = providerProperties;
for (const key of Object.keys(tempConfig.providerProperties)) {
if (typeof tempConfig.providerProperties[key] === 'string') {
tempConfig.providerProperties[key] = tempConfig.providerProperties[key]?.trim();
}
}
if (driver.useCustomPage && driver.mainProperties.length > 0) {
tempConfig.mainPropertyValues = this.prepareDynamicProperties(
driver.mainProperties,
tempConfig.mainPropertyValues,
tempConfig.configurationType,
);
}
runInAction(() => {
@@ -366,11 +352,44 @@ export class ConnectionOptionsTabService extends Bootstrap {
});
}
private prepareDynamicProperties(
propertiesInfo: ObjectPropertyInfo[],
properties: Record<string, any>,
configurationType?: DriverConfigurationType,
) {
const result: Record<string, any> = { ...properties };
for (const propertyInfo of propertiesInfo) {
if (!propertyInfo.id) {
continue;
}
const supported = configurationType === undefined || propertyInfo.supportedConfigurationTypes?.some(type => type === configurationType);
if (!supported) {
delete result[propertyInfo.id];
} else {
const isDefault = isNotNullDefined(propertyInfo.defaultValue);
if (!(propertyInfo.id in result) && isDefault) {
result[propertyInfo.id] = propertyInfo.defaultValue;
}
}
}
for (const key of Object.keys(result)) {
if (typeof result[key] === 'string') {
result[key] = result[key]?.trim();
}
}
return result;
}
private async formAuthState(data: IConnectionFormState, contexts: IExecutionContextProvider<IConnectionFormState>) {
const config = contexts.getContext(connectionConfigContext);
const stateContext = contexts.getContext(formStateContext);
const driver = await this.dbDriverResource.load(config.driverId!, ['includeProviderProperties']);
const driver = await this.dbDriverResource.load(config.driverId!, ['includeProviderProperties', 'includeMainProperties']);
const authModel = await this.databaseAuthModelsResource.load(config.authModelId ?? data.info?.authModel ?? driver.defaultAuthModel);
const providerId = authModel.requiredAuth ?? data.info?.requiredAuth ?? AUTH_PROVIDER_LOCAL_ID;
@@ -394,7 +413,7 @@ export class ConnectionOptionsTabService extends Bootstrap {
const config = contexts.getContext(connectionConfigContext);
const stateContext = contexts.getContext(formStateContext);
const driver = await this.dbDriverResource.load(data.config.driverId!, ['includeProviderProperties']);
const driver = await this.dbDriverResource.load(data.config.driverId!, ['includeProviderProperties', 'includeMainProperties']);
if (
!isValuesEqual(config.name, data.info.name, '') ||
@@ -414,6 +433,8 @@ export class ConnectionOptionsTabService extends Bootstrap {
(config.sharedCredentials !== undefined && config.sharedCredentials !== data.info.sharedCredentials) ||
(config.providerProperties !== undefined &&
!isObjectPropertyInfoStateEqual(driver.providerProperties, config.providerProperties, data.info.providerProperties)) ||
(config.mainPropertyValues !== undefined &&
!isObjectPropertyInfoStateEqual(driver.mainProperties, config.mainPropertyValues, data.info.mainPropertyValues)) ||
(config.keepAliveInterval !== undefined && !isValuesEqual(config.keepAliveInterval, data.info.keepAliveInterval)) ||
(config.autocommit !== undefined && !isValuesEqual(config.autocommit, data.info.autocommit))
) {
@@ -21,6 +21,7 @@ import {
GroupTitle,
InputField,
Link,
ObjectPropertyInfoForm,
Radio,
RadioGroup,
s,
@@ -31,11 +32,12 @@ import {
useS,
useTranslate,
} from '@cloudbeaver/core-blocks';
import { DatabaseAuthModelsResource, DBDriverResource, isLocalConnection } from '@cloudbeaver/core-connections';
import { DatabaseAuthModelsResource, DBDriver, DBDriverResource, isLocalConnection } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { DriverConfigurationType } from '@cloudbeaver/core-sdk';
import { type TabContainerPanelComponent, TabsContext, useAuthenticationAction } from '@cloudbeaver/core-ui';
import { EMPTY_ARRAY } from '@cloudbeaver/core-utils';
import { ProjectSelect } from '@cloudbeaver/plugin-projects';
import { ConnectionAuthModelCredentialsForm } from '../ConnectionAuthModelCredentials/ConnectionAuthModelCredentialsForm';
@@ -57,16 +59,19 @@ interface IDriverConfiguration {
value: DriverConfigurationType;
description?: string;
icon?: string;
isVisible: (driver: DBDriver) => boolean;
}
const driverConfiguration: IDriverConfiguration[] = [
{
name: 'Manual',
value: DriverConfigurationType.Manual,
isVisible: driver => driver.configurationTypes.includes(DriverConfigurationType.Manual),
},
{
name: 'URL',
value: DriverConfigurationType.Url,
isVisible: driver => driver.configurationTypes.includes(DriverConfigurationType.Url),
},
];
@@ -90,7 +95,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
const driverMap = useResource(
Options,
DBDriverResource,
{ key: config.driverId || null, includes: ['includeProviderProperties'] as const },
{ key: config.driverId || null, includes: ['includeProviderProperties', 'includeMainProperties'] as const },
{
onData: data => {
optionsHook.setDefaults(data);
@@ -99,7 +104,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
);
const driver = driverMap.data;
const configurationTypes = driverConfiguration.filter(conf => driver?.configurationTypes.includes(conf.value));
const configurationTypes = driverConfiguration.filter(configuration => driver && configuration.isVisible(driver));
function handleFormChange(value?: unknown, name?: string) {
if (name !== 'name' && optionsHook.isNameAutoFill()) {
@@ -142,7 +147,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
providerId: authModel?.requiredAuth ?? info?.requiredAuth ?? AUTH_PROVIDER_LOCAL_ID,
});
const isURLConfiguration = config.configurationType === DriverConfigurationType.Url;
const edit = state.mode === 'edit';
const originLocal = !info || isLocalConnection(info);
@@ -185,44 +189,28 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
{translate('connections_connection_driver')}
</Combobox>
{configurationTypes.length > 1 && (
<>
{/*<Combobox
name='configurationType'
state={config}
items={configurationTypes}
keySelector={conf => conf.value}
valueSelector={conf => conf.name}
titleSelector={conf => conf.description}
readOnly={readonly || configurationTypes.length < 2}
disabled={disabled}
tiny
fill
>
{translate('connections_connection_configuration')}
</Combobox>*/}
<FormFieldDescription label={translate('connections_connection_configuration')} tiny>
<Container gap>
<RadioGroup name="configurationType" state={config}>
{driverConfiguration.map(conf => (
<Radio
key={conf.value}
id={conf.value}
value={conf.value}
mod={['primary', 'small']}
readOnly={readonly || configurationTypes.length < 2}
disabled={readonly}
keepSize
>
{conf.name}
</Radio>
))}
</RadioGroup>
</Container>
</FormFieldDescription>
</>
<FormFieldDescription label={translate('connections_connection_configuration')} tiny>
<Container gap>
<RadioGroup name="configurationType" state={config}>
{configurationTypes.map(conf => (
<Radio
key={conf.value}
id={conf.value}
value={conf.value}
mod={['primary', 'small']}
readOnly={readonly || configurationTypes.length < 2}
disabled={readonly}
keepSize
>
{conf.name}
</Radio>
))}
</RadioGroup>
</Container>
</FormFieldDescription>
)}
</Container>
{isURLConfiguration ? (
{config.configurationType === DriverConfigurationType.Url && (
<InputField
type="text"
name="url"
@@ -233,16 +221,26 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
>
{translate('customConnection_url_JDBC')}
</InputField>
) : (
<ParametersForm
config={config}
embedded={driver?.embedded}
requiresServerName={driver?.requiresServerName}
disabled={disabled}
readOnly={readonly}
originLocal={originLocal}
/>
)}
{config.configurationType === DriverConfigurationType.Manual &&
(driver?.useCustomPage ? (
<ObjectPropertyInfoForm
state={config.mainPropertyValues}
properties={driver.mainProperties ?? EMPTY_ARRAY}
disabled={disabled}
readOnly={readonly}
/>
) : (
<ParametersForm
config={config}
embedded={driver?.embedded}
requiresServerName={driver?.requiresServerName}
disabled={disabled}
readOnly={readonly}
originLocal={originLocal}
/>
))}
</Group>
<Group form gap>
<Container wrap gap>
@@ -16,13 +16,13 @@ import {
useObjectPropertyCategories,
useTranslate,
} from '@cloudbeaver/core-blocks';
import { type ConnectionConfig, type DriverProviderPropertyInfoFragment, getObjectPropertyType } from '@cloudbeaver/core-sdk';
import { type ConnectionConfig, type DriverPropertyInfoFragment, getObjectPropertyType } from '@cloudbeaver/core-sdk';
type DriverProviderPropertyInfo = DriverProviderPropertyInfoFragment;
type DriverPropertyInfo = DriverPropertyInfoFragment;
interface Props {
config: ConnectionConfig;
properties: DriverProviderPropertyInfo[];
properties: DriverPropertyInfo[];
disabled?: boolean;
readonly?: boolean;
}
@@ -93,6 +93,6 @@ export const ProviderPropertiesForm = observer<Props>(function ProviderPropertie
);
});
function isOnlyBooleans(properties: DriverProviderPropertyInfo[], category?: string): boolean {
function isOnlyBooleans(properties: DriverPropertyInfo[], category?: string): boolean {
return properties.filter(property => !category || property.category === category).every(property => property.dataType === 'Boolean');
}
@@ -0,0 +1,18 @@
/*
* 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 { DBDriver } from '@cloudbeaver/core-connections';
import { DriverConfigurationType } from '@cloudbeaver/core-sdk';
export function getDefaultConfigurationType(driver: DBDriver | undefined) {
if (!driver) {
return DriverConfigurationType.Url;
}
const supportManual = driver.configurationTypes.includes(DriverConfigurationType.Manual);
return supportManual ? DriverConfigurationType.Manual : DriverConfigurationType.Url;
}
@@ -10,10 +10,11 @@ import { runInAction } from 'mobx';
import { useObjectRef } from '@cloudbeaver/core-blocks';
import { DBDriver, DBDriverResource, isJDBCConnection } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { DatabaseAuthModel, DriverConfigurationType } from '@cloudbeaver/core-sdk';
import type { DatabaseAuthModel } from '@cloudbeaver/core-sdk';
import type { IConnectionFormState } from '../IConnectionFormProps';
import { getConnectionName } from './getConnectionName';
import { getDefaultConfigurationType } from './getDefaultConfigurationType';
export function useOptions(state: IConnectionFormState) {
const dbDriverResource = useService(DBDriverResource);
@@ -71,9 +72,7 @@ export function useOptions(state: IConnectionFormState) {
refObject.prevDriverId = driver?.id || null;
if (!config.configurationType || !driver?.configurationTypes.includes(config.configurationType)) {
config.configurationType = driver?.configurationTypes.includes(DriverConfigurationType.Manual)
? DriverConfigurationType.Manual
: DriverConfigurationType.Url;
state.config.configurationType = getDefaultConfigurationType(driver);
}
if ((!prevDriver && config.host === undefined) || config.host === prevDriver?.defaultServer) {