diff --git a/webapp/.eslintrc.js b/webapp/.eslintrc.js index bd27bd7bad..10d3d312f1 100644 --- a/webapp/.eslintrc.js +++ b/webapp/.eslintrc.js @@ -34,6 +34,7 @@ module.exports = { }, rules: { + '@typescript-eslint/no-invalid-void-type': 'off', '@typescript-eslint/dot-notation': 'off', '@typescript-eslint/no-throw-literal': 'off', '@typescript-eslint/no-base-to-string': 'off', diff --git a/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts b/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts index 8a96e48963..ce10701df2 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/DBObjectService.ts @@ -20,7 +20,7 @@ export class DBObjectService extends CachedMapResource { private graphQLService: GraphQLService, private navNodeInfoResource: NavNodeInfoResource ) { - super(new Map()); + super(); this.navNodeInfoResource.onDataOutdated.addHandler(this.markOutdated.bind(this)); this.navNodeInfoResource.onItemDelete.addHandler(this.delete.bind(this)); } diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts index 7e85ee2f04..32febb19ff 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts @@ -15,8 +15,8 @@ import { ResourceKey, isResourceKeyList, NavNodeInfoFragment, - ICachedResourceMetadata, - ResourceKeyUtils + ResourceKeyUtils, + ICachedMapResourceMetadata } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -26,7 +26,7 @@ type NavNodeInfo = NavNodeInfoFragment; export const ROOT_NODE_PATH = ''; -interface INodeMetadata extends ICachedResourceMetadata { +interface INodeMetadata extends ICachedMapResourceMetadata { withDetails: boolean; } @@ -34,7 +34,7 @@ interface INodeMetadata extends ICachedResourceMetadata { export class NavNodeInfoResource extends CachedMapResource { protected metadata: MetadataMap; constructor(private graphQLService: GraphQLService) { - super(new Map()); + super(); makeObservable(this, { setDetails: action, @@ -44,6 +44,8 @@ export class NavNodeInfoResource extends CachedMapResource { outdated: true, loading: false, withDetails: false, + includes: [], + loadedIncludes: [], })); } diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts index c33cab8297..690d992a05 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -16,7 +16,9 @@ import { isResourceKeyList, ResourceKeyList, resourceKeyList, - NavNodeChildrenQuery as fake, ResourceKeyUtils, ICachedResourceMetadata + NavNodeChildrenQuery as fake, + ResourceKeyUtils, + ICachedMapResourceMetadata } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -29,7 +31,7 @@ export interface NodePath { type NavNodeChildrenQuery = fake & NodePath; -interface INodeMetadata extends ICachedResourceMetadata { +interface INodeMetadata extends ICachedMapResourceMetadata { withDetails: boolean; } @@ -41,7 +43,7 @@ export class NavTreeResource extends CachedMapResource { private graphQLService: GraphQLService, private navNodeInfoResource: NavNodeInfoResource ) { - super(new Map()); + super(); makeObservable(this, { setDetails: action, @@ -51,6 +53,8 @@ export class NavTreeResource extends CachedMapResource { outdated: true, loading: false, withDetails: false, + includes: [], + loadedIncludes: [], })); this.onDataOutdated.addHandler(navNodeInfoResource.markOutdated.bind(navNodeInfoResource)); } diff --git a/webapp/packages/core-authentication/src/RolesResource.ts b/webapp/packages/core-authentication/src/RolesResource.ts index 9d777da1a2..bde79805e4 100644 --- a/webapp/packages/core-authentication/src/RolesResource.ts +++ b/webapp/packages/core-authentication/src/RolesResource.ts @@ -19,7 +19,7 @@ import { MetadataMap } from '@cloudbeaver/core-utils'; export class RolesResource extends CachedMapResource { private loadedKeyMetadata: MetadataMap; constructor(private graphQLService: GraphQLService) { - super(new Map()); + super(); this.loadedKeyMetadata = new MetadataMap(() => false); } diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index b6c4a5c67c..95e2e7f0d0 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -42,7 +42,7 @@ export class UsersResource extends CachedMapResource { private authProviderService: AuthProviderService, private authInfoService: AuthInfoService ) { - super(new Map()); + super(); this.loadedKeyMetadata = new MetadataMap(() => false); } diff --git a/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx b/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx index ab28a3f871..add5f0b0b2 100644 --- a/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx +++ b/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx @@ -6,15 +6,15 @@ * you may not use this file except in compliance with the License. */ -import { computed } from 'mobx'; import { observer, useLocalStore } from 'mobx-react-lite'; -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import styled from 'reshadow'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; import { Button } from '../Button'; +import { useObjectRef } from '../useObjectRef'; import type { IProperty } from './IProperty'; import { PropertyItem } from './PropertyItem'; import { PROPERTIES_TABLE_STYLES } from './styles'; @@ -32,19 +32,14 @@ interface PropertiesTableProps { className?: string; } -export const PropertiesTable = observer(function PropertiesTable({ - properties, - propertiesState, - readOnly, - onKeyChange, - onChange, - onAdd, - onRemove, - className, -}: PropertiesTableProps) { +export const PropertiesTable = observer(function PropertiesTable(props: PropertiesTableProps) { + const { className, onAdd, readOnly, propertiesState } = props; + const propsRef = useObjectRef(props); const translate = useTranslate(); const state = useLocalStore(() => (propertiesState || {})); + const changeName = useCallback((id: string, key: string) => { + const { properties, onKeyChange } = propsRef; const property = properties.find(property => property.id === id); if (!property) { @@ -63,9 +58,10 @@ export const PropertiesTable = observer(function PropertiesTable({ onKeyChange(property.key, key); } property.key = key; - }, [properties]); + }, []); const changeValue = useCallback((id: string, value: string) => { + const { properties, onChange } = propsRef; const property = properties.find(property => property.id === id); if (!property) { @@ -77,9 +73,10 @@ export const PropertiesTable = observer(function PropertiesTable({ if (onChange) { onChange(state); } - }, [properties, onChange]); + }, []); const removeProperty = useCallback((id: string) => { + const { properties, onRemove } = propsRef; const property = properties.find(property => property.id === id); if (!property) { @@ -95,15 +92,15 @@ export const PropertiesTable = observer(function PropertiesTable({ onRemove(id); } properties.splice(properties.indexOf(property), 1); - }, [properties, onRemove]); + }, []); const isKeyUnique = useCallback( - (key: string) => properties.filter(property => property.key === key).length === 1, + (key: string) => propsRef.properties.filter(property => property.key === key).length === 1, [] ); - const alphabetOrderProperties = useMemo(() => computed(() => properties.slice().sort( - (a, b) => (a?.displayName ?? '').localeCompare(b?.displayName ?? ''))), [properties]); + const alphabetOrderProperties = propsRef.properties.slice().sort( + (a, b) => (a?.displayName ?? '').localeCompare(b?.displayName ?? '')); return styled(useStyles(PROPERTIES_TABLE_STYLES))( @@ -119,7 +116,7 @@ export const PropertiesTable = observer(function PropertiesTable({ - {alphabetOrderProperties.get().map(property => ( + {alphabetOrderProperties.map(property => ( { ) => Promise | any; } +interface KeyWithIncludes { + key: TKey | null; + includes: TIncludes; +} + interface IMapResourceResult< TKeyArg extends ResourceKey>, - TResource extends CachedMapResource + TResource extends CachedMapResource, + TIncludes extends Array> > { - data: CachedMapResourceGetter, CachedMapResourceValue>; + data: CachedMapResourceGetter, CachedMapResourceValue, TIncludes>; resource: TResource; isLoading: () => boolean; + isLoaded: () => boolean; } export function useMapResource< TResource extends CachedMapResource, TKeyArg extends ResourceKey>, + TIncludes extends Array> = [] >( ctor: IServiceConstructor, - key: TKeyArg | null, + keyObj: TKeyArg | null | KeyWithIncludes, actions?: IActions -): IMapResourceResult { +): IMapResourceResult { const resource = useService(ctor); const notifications = useService(NotificationService); + const key = keyObj && typeof keyObj === 'object' && 'includes' in keyObj ? keyObj.key : keyObj; + const includes = keyObj && typeof keyObj === 'object' && 'includes' in keyObj ? keyObj.includes : []; const refObj = useObjectRef({ resource, key, + includes, actions, prevData: (isResourceKeyList(key) ? [] : undefined) as CachedMapResourceValue, }, { resource, key, + includes, actions, }); - const [result] = useState>(() => ({ + const [result] = useState>(() => ({ get resource() { return refObj.resource; }, @@ -68,6 +80,13 @@ export function useMapResource< return resource.get(refObj.key); }, + isLoaded: () => { + if (refObj.key === null) { + return false; + } + + return resource.isLoaded(refObj.key, refObj.includes); + }, isLoading: () => { if (refObj.key === null) { return false; @@ -88,7 +107,7 @@ export function useMapResource< return; } - const newData = await resource.load(key); + const newData = await resource.load(key, includes); try { await actions?.onData?.( @@ -103,7 +122,7 @@ export function useMapResource< notifications.logException(exception, 'Can\'t load data'); } })(); - }, [key]); + }, [key, includes]); return result; } diff --git a/webapp/packages/core-bootstrap/src/manifest.ts b/webapp/packages/core-bootstrap/src/manifest.ts index 17243959d5..ce1aace605 100644 --- a/webapp/packages/core-bootstrap/src/manifest.ts +++ b/webapp/packages/core-bootstrap/src/manifest.ts @@ -59,7 +59,6 @@ import { ConnectionsAdministrationService, ConnectionsResource, ConnectionsLocaleService, - DriverPropertiesService, ConnectionsAdministrationNavService, CreateConnectionService, ConnectionManualService, @@ -140,7 +139,6 @@ export const coreManifest: PluginManifest = { ContainerResource, DBDriverResource, NetworkHandlerResource, - DriverPropertiesService, ConnectionsManagerService, ScreenService, AppScreenService, diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts index c4ca8a4c63..62dd617921 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts @@ -14,7 +14,7 @@ import { NotificationService } from '@cloudbeaver/core-events'; import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; import { uuid } from '@cloudbeaver/core-utils'; -import { DriverPropertiesService } from '../../../../DriverPropertiesService'; +import { DBDriverResource } from '../../../../DBDriverResource'; interface StaticId { staticId: string; @@ -35,7 +35,7 @@ export class DriverPropertiesController { private state!: Record; constructor( - private driverPropertiesService: DriverPropertiesService, + private dbDriverResource: DBDriverResource, private notificationService: NotificationService ) { makeObservable(this, { @@ -68,8 +68,8 @@ export class DriverPropertiesController { } this.isLoading = true; try { - const driverProperties = await this.driverPropertiesService.loadDriverProperties(this.driverId); - this.driverProperties = observable(driverProperties.map(property => ({ + const driver = await this.dbDriverResource.load(this.driverId, ['driverProperties']); + this.driverProperties = observable(driver.driverProperties.map(property => ({ id: property.id!, key: property.id!, keyPlaceholder: property.id, diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts index 592e90f15e..f70b91bd71 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts @@ -18,7 +18,7 @@ import type { IConnectionFormModel } from '../IConnectionFormModel'; @injectable() export class OptionsController - implements IInitializableController { +implements IInitializableController { get drivers(): DBDriver[] { return Array.from(this.dbDriverResource.data.values()) .filter(({ id }) => this.model.availableDrivers.includes(id)); @@ -173,7 +173,7 @@ export class OptionsController } try { - await this.dbDriverResource.load(driverId); + await this.dbDriverResource.load(driverId, ['providerProperties']); await this.dbAuthModelsResource.load( this.model.connection.authModel || this.driver!.defaultAuthModel ); diff --git a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts index 3308a6dc9a..e9ae185e0a 100644 --- a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts +++ b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts @@ -43,7 +43,7 @@ export class ConnectionsResource extends CachedMapResource; +export type DBDriver = DatabaseDriverFragment; + +const allKey = 'all'; @injectable() export class DBDriverResource extends CachedMapResource { private loadedKeyMetadata: MetadataMap; constructor(private graphQLService: GraphQLService) { - super(new Map()); + super(); this.loadedKeyMetadata = new MetadataMap(() => false); } @@ -51,7 +38,7 @@ export class DBDriverResource extends CachedMapResource { } async loadAll(): Promise> { - await this.load('all'); + await this.load(allKey); return this.data; } @@ -63,18 +50,30 @@ export class DBDriverResource extends CachedMapResource { return (driverB.promotedScore || 0) - (driverA.promotedScore || 0); } - protected async loader(key: string): Promise> { - const { driverList } = await this.graphQLService.sdk.driverList(); + protected async loader(key: ResourceKey): Promise> { + await ResourceKeyUtils.forEachAsync(key, async key => { + const { drivers } = await this.graphQLService.sdk.driverList({ + driverId: key === allKey ? undefined : key, + includeDriverParameters: false, + includeDriverProperties: false, + includeProviderProperties: false, + ...this.getIncludes(key === allKey ? undefined : key), + }); - this.data.clear(); + if (key === allKey) { + this.data.clear(); + } - for (const driver of driverList) { - this.set(driver.id, driver); - } + for (const driver of drivers) { + this.set(driver.id, driver); + } - // TODO: driverList must accept driverId, so we can update some drivers or all drivers, - // here we should check is it's was a full update - this.loadedKeyMetadata.set('all', true); + if (key === allKey) { + // TODO: driverList must accept driverId, so we can update some drivers or all drivers, + // here we should check is it's was a full update + this.loadedKeyMetadata.set(allKey, true); + } + }); return this.data; } diff --git a/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts b/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts index b6b502e602..ecd073e44e 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts +++ b/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts @@ -17,7 +17,7 @@ import { @injectable() export class DatabaseAuthModelsResource extends CachedMapResource { constructor(private graphQLService: GraphQLService) { - super(new Map()); + super(); } protected async loader(key: string): Promise> { diff --git a/webapp/packages/core-connections/src/DriverPropertiesService.ts b/webapp/packages/core-connections/src/DriverPropertiesService.ts deleted file mode 100644 index 66db31afd6..0000000000 --- a/webapp/packages/core-connections/src/DriverPropertiesService.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 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 { injectable } from '@cloudbeaver/core-di'; -import { GraphQLService, ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; - -@injectable() -export class DriverPropertiesService { - constructor( - private graphQLService: GraphQLService - ) { - } - - async loadDriverProperties(driverId: string): Promise { - const response = await this.graphQLService.sdk.driverProperties({ - driverId, - }); - - if (response.driver.length === 0) { - throw new Error('Driver properties loading failed'); - } - - return response.driver[0].driverProperties! as ObjectPropertyInfo[]; - } -} diff --git a/webapp/packages/core-connections/src/NetworkHandlerResource.ts b/webapp/packages/core-connections/src/NetworkHandlerResource.ts index 54d17b63c8..ca13d231f6 100644 --- a/webapp/packages/core-connections/src/NetworkHandlerResource.ts +++ b/webapp/packages/core-connections/src/NetworkHandlerResource.ts @@ -25,7 +25,7 @@ export class NetworkHandlerResource extends CachedMapResource false); } diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 544976d1b2..ba6e20ad67 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -14,7 +14,6 @@ export * from './extensions/IConnectionSetter'; export * from './ConnectionsManagerService'; export * from './ConnectionInfoResource'; export * from './ConnectionAuthService'; -export * from './DriverPropertiesService'; export * from './EConnectionFeature'; export * from './ContainerResource'; export * from './ConnectionsLocaleService'; diff --git a/webapp/packages/core-di/src/DIContainer.ts b/webapp/packages/core-di/src/DIContainer.ts index 39bd04a0c9..94a4991ffb 100644 --- a/webapp/packages/core-di/src/DIContainer.ts +++ b/webapp/packages/core-di/src/DIContainer.ts @@ -15,6 +15,7 @@ import { isConstructor } from './isConstructor'; export class DIContainer implements IServiceInjector, IServiceCollection { protected container = new Container({ defaultScope: 'Singleton', + skipBaseClassChecks: true, }); private parent: DIContainer | null = null; diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts index 54fa3c495f..396b767a23 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts @@ -6,11 +6,8 @@ * you may not use this file except in compliance with the License. */ -import { injectable } from '@cloudbeaver/core-di'; - import { CachedResource } from './CachedResource'; -@injectable() export abstract class CachedDataResource< TData, TParam, diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts index a6e7fb028a..2bad55e45f 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts @@ -8,28 +8,46 @@ import { computed, makeObservable } from 'mobx'; -import { injectable } from '@cloudbeaver/core-di'; import { Executor, IExecutor } from '@cloudbeaver/core-executor'; +import { MetadataMap } from '@cloudbeaver/core-utils'; -import { CachedResource } from './CachedResource'; +import { CachedResource, ICachedResourceMetadata } from './CachedResource'; import { ResourceKey, resourceKeyList, ResourceKeyList, ResourceKeyUtils } from './ResourceKeyList'; +export type CachedMapValueIncludes> = TValue +& (TKeys extends Array ? + { + [P in Extract]-?: Required[P] extends undefined ? TValue[P] : NonNullable; + } + // eslint-disable-next-line @typescript-eslint/ban-types + : { }); + export type CachedMapResourceGetter< TRealKey extends ResourceKey, TKey, - TValue + TValue, + TIncludes extends Array > = TRealKey extends ResourceKeyList - ? Array - : TValue | undefined; + ? Array | undefined> + : CachedMapValueIncludes | undefined; -@injectable() -export abstract class CachedMapResource extends CachedResource< -Map, -ResourceKey, -TKey -> { +export interface ICachedMapResourceMetadata extends ICachedResourceMetadata { + includes: Array; + loadedIncludes: Array; +} + +export abstract class CachedMapResource< + TKey, + TValue +> extends CachedResource< + Map, + ResourceKey, + TKey + > { readonly onItemAdd: IExecutor>; readonly onItemDelete: IExecutor>; + protected metadata: MetadataMap>; + private defaultIncludes: Array; get values(): TValue[] { return Array.from(this.data.values()); @@ -39,10 +57,18 @@ TKey return Array.from(this.data.keys()); } - constructor(defaultValue?: Map) { + constructor(defaultIncludes?: Array, defaultValue?: Map) { super(defaultValue || new Map()); this.onItemAdd = new Executor(null, this.includes); this.onItemDelete = new Executor(null, this.includes); + this.defaultIncludes = defaultIncludes || []; + + this.metadata = new MetadataMap(() => ({ + outdated: true, + loading: false, + includes: this.defaultIncludes, + loadedIncludes: this.defaultIncludes, + })); makeObservable(this, { values: computed, @@ -50,6 +76,14 @@ TKey }); } + isIncludes(key: ResourceKey, includes: Array): boolean { + return ResourceKeyUtils.every(key, key => { + const metadata = this.metadata.get(key); + + return includes.every(include => metadata.includes.includes(include)); + }); + } + isOutdated(key: ResourceKey): boolean { return ResourceKeyUtils.some(key, key => this.metadata.get(key).outdated); } @@ -66,6 +100,7 @@ TKey } markDataLoaded(key: ResourceKey): void { + this.commitIncludes(key); ResourceKeyUtils.forEach(key, key => { const metadata = this.metadata.get(key); metadata.loading = false; @@ -99,8 +134,27 @@ TKey }); } - isLoaded(key: ResourceKey): boolean { - return ResourceKeyUtils.every(key, key => this.has(key)); + isLoaded(key: ResourceKey, includes?: Array): boolean { + return ResourceKeyUtils.every(key, key => { + if (!this.has(key)) { + return false; + } + + const metadata = this.metadata.get(key); + + if (!includes) { + includes = metadata.includes; + } + + if (includes.length > metadata.loadedIncludes.length) { + return false; + } + + if (includes.some(include => !metadata.loadedIncludes.includes(include))) { + return false; + } + return true; + }); } get(key: TKey): TValue | undefined; @@ -133,20 +187,46 @@ TKey this.onItemDelete.execute(key); } - async refresh(key: TKey): Promise; - async refresh(key: ResourceKeyList): Promise; - async refresh(key: ResourceKey): Promise; - async refresh(key: ResourceKey): Promise { + async refresh = []>( + key: TKey, + includes?: T + ): Promise>; + async refresh = []>( + key: ResourceKeyList, + includes?: T + ): Promise>>; + async refresh = []>( + key: ResourceKey, + includes?: T + ): Promise> | CachedMapValueIncludes>; + async refresh = []>( + key: ResourceKey, + includes?: T + ): Promise> | CachedMapValueIncludes> { + this.setIncludes(key, includes || []); await this.loadData(key, true); - return this.get(key) as TValue[]| TValue; + return this.get(key) as Array> | CachedMapValueIncludes; } - async load(key: TKey): Promise; - async load(key: ResourceKeyList): Promise; - async load(key: ResourceKey): Promise; - async load(key: ResourceKey): Promise { + async load = []>( + key: TKey, + includes?: T + ): Promise>; + async load = []>( + key: ResourceKeyList, + includes?: T + ): Promise>>; + async load = []>( + key: ResourceKey, + includes?: T + ): Promise> | CachedMapValueIncludes>; + async load = []>( + key: ResourceKey, + includes?: T + ): Promise> | CachedMapValueIncludes> { + this.setIncludes(key, includes || []); await this.loadData(key); - return this.get(key) as TValue[]| TValue; + return this.get(key) as Array> | CachedMapValueIncludes; } has(key: TKey): boolean { @@ -156,4 +236,55 @@ TKey includes(param: ResourceKey, key: ResourceKey): boolean { return ResourceKeyUtils.includes(param, key); } + + getIncludes(key?: ResourceKey): Record { + if (!key) { + return ['base', ...this.defaultIncludes].reduce((map, key) => { + map[this.getIncludeName(key as string)] = true; + + return map; + }, {}); + } + + const metadata = this.metadata.get(ResourceKeyUtils.first(key)); + + return ['base', ...metadata.includes].reduce((map, key) => { + map[this.getIncludeName(key as string)] = true; + + return map; + }, {}); + } + + private setIncludes(key: ResourceKey, includes: Array) { + if (includes.length === 0) { + return; + } + + ResourceKeyUtils.forEach(key, key => { + const metadata = this.metadata.get(key); + + const newIncludes = [...metadata.loadedIncludes]; + + for (const include of includes) { + if (!metadata.loadedIncludes.includes(include)) { + newIncludes.push(include); + } + } + + if (metadata.loadedIncludes.length < newIncludes.length) { + metadata.includes = newIncludes; + } + }); + } + + private commitIncludes(key: ResourceKey) { + ResourceKeyUtils.forEach(key, key => { + const metadata = this.metadata.get(key); + metadata.loadedIncludes = metadata.includes; + }); + } + + private getIncludeName(key: string) { + return 'include' + key.charAt(0).toUpperCase() + key.slice(1); + } } diff --git a/webapp/packages/core-sdk/src/Resource/CachedResource.ts b/webapp/packages/core-sdk/src/Resource/CachedResource.ts index 7343278b18..3a153d7cfa 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedResource.ts @@ -8,7 +8,6 @@ import { observable, makeObservable } from 'mobx'; -import { injectable } from '@cloudbeaver/core-di'; import { Executor, IExecutor, TaskScheduler } from '@cloudbeaver/core-executor'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -17,7 +16,6 @@ export interface ICachedResourceMetadata { loading: boolean; } -@injectable() export abstract class CachedResource< TData, TParam, diff --git a/webapp/packages/core-sdk/src/Resource/LocalResource.ts b/webapp/packages/core-sdk/src/Resource/LocalResource.ts index 4a4d9fb841..ffed146d19 100644 --- a/webapp/packages/core-sdk/src/Resource/LocalResource.ts +++ b/webapp/packages/core-sdk/src/Resource/LocalResource.ts @@ -8,10 +8,8 @@ import { observable, makeObservable } from 'mobx'; -import { injectable } from '@cloudbeaver/core-di'; import { Executor, IExecutor, TaskScheduler } from '@cloudbeaver/core-executor'; -@injectable() export abstract class LocalResource< TData, TParam, @@ -30,7 +28,7 @@ export abstract class LocalResource< protected scheduler: TaskScheduler; constructor(defaultValue: TData) { - makeObservable(this, { + makeObservable, 'outdated' | 'dataLoading' | 'loading'>(this, { data: observable, outdated: observable, dataLoading: observable, diff --git a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts index 37fb13e815..7a99a9d098 100644 --- a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts +++ b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts @@ -35,6 +35,7 @@ interface MapFnc { } export interface ResourceKeyUtils { + first: (key: ResourceKey) => TKey; forEach: (key: ResourceKey, action: (key: TKey, index: number) => any) => void; forEachAsync: (key: ResourceKey, action: (key: TKey, index: number) => Promise) => Promise; some: (key: ResourceKey, predicate: (key: TKey, index: number) => boolean) => boolean; @@ -45,6 +46,16 @@ export interface ResourceKeyUtils { // eslint-disable-next-line @typescript-eslint/no-redeclare export const ResourceKeyUtils: ResourceKeyUtils = { + first( + key: ResourceKey + ): TKey { + if (isResourceKeyList(key)) { + return key.list[0]; + } else { + return key; + } + }, + forEach( key: ResourceKey, action: (key: TKey, index: number) => any | Promise diff --git a/webapp/packages/core-sdk/src/queries/connections/driverList.gql b/webapp/packages/core-sdk/src/queries/connections/driverList.gql index 6586da9549..9e89857bd6 100644 --- a/webapp/packages/core-sdk/src/queries/connections/driverList.gql +++ b/webapp/packages/core-sdk/src/queries/connections/driverList.gql @@ -1,32 +1,5 @@ -query driverList { - driverList { - id - name - icon - description - defaultPort - defaultDatabase - defaultServer - defaultUser - sampleURL - embedded - anonymousAccess - promotedScore - - defaultAuthModel - # applicableAuthModel - - providerProperties { - id - displayName - description - category - dataType - defaultValue - validValues - features - order - } - applicableNetworkHandlers +query driverList($driverId: ID, $includeProviderProperties: Boolean!, $includeDriverProperties: Boolean!, $includeDriverParameters: Boolean!) { + drivers: driverList(id: $driverId) { + ...DatabaseDriver } } diff --git a/webapp/packages/core-sdk/src/queries/connections/driverProperties.gql b/webapp/packages/core-sdk/src/queries/connections/driverProperties.gql deleted file mode 100644 index a64958190f..0000000000 --- a/webapp/packages/core-sdk/src/queries/connections/driverProperties.gql +++ /dev/null @@ -1,14 +0,0 @@ -query driverProperties($driverId: ID!) { - driver: driverList(id: $driverId) { - driverProperties { - id - displayName - description - category - dataType - defaultValue - validValues - } - driverParameters - } -} diff --git a/webapp/packages/core-sdk/src/queries/connections/getDriverById.gql b/webapp/packages/core-sdk/src/queries/connections/getDriverById.gql deleted file mode 100644 index c4464ef0da..0000000000 --- a/webapp/packages/core-sdk/src/queries/connections/getDriverById.gql +++ /dev/null @@ -1,7 +0,0 @@ -query getDriverById($driverId: ID!) { - driverList(id: $driverId) { - id - name - icon - } -} diff --git a/webapp/packages/core-sdk/src/queries/fragments/AdminConnection.gql b/webapp/packages/core-sdk/src/queries/fragments/AdminConnection.gql index 9774a0947e..ac5905c2d9 100644 --- a/webapp/packages/core-sdk/src/queries/fragments/AdminConnection.gql +++ b/webapp/packages/core-sdk/src/queries/fragments/AdminConnection.gql @@ -6,6 +6,7 @@ fragment AdminConnection on ConnectionInfo { template connected + provided useUrl readOnly saveCredentials @@ -38,6 +39,10 @@ fragment AdminConnection on ConnectionInfo { properties } + navigatorSettings { + ...AllNavigatorSettings + } + features supportedDataFormats } \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/queries/fragments/DatabaseDriver.gql b/webapp/packages/core-sdk/src/queries/fragments/DatabaseDriver.gql new file mode 100644 index 0000000000..bcdca91860 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/DatabaseDriver.gql @@ -0,0 +1,41 @@ +fragment DatabaseDriver on DriverInfo { + id + name + icon + description + defaultPort + defaultDatabase + defaultServer + defaultUser + sampleURL + embedded + anonymousAccess + promotedScore + + defaultAuthModel + # applicableAuthModel + applicableNetworkHandlers + + providerProperties @include(if: $includeProviderProperties) { + id + displayName + description + category + dataType + defaultValue + validValues + features + order + } + + driverProperties @include(if: $includeDriverProperties) { + id + displayName + description + category + dataType + defaultValue + validValues + } + driverParameters @include(if: $includeDriverParameters) +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index c196d47b6a..f276c60ec1 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -335,6 +335,7 @@ export interface MutationDeleteConnectionArgs { export interface MutationInitConnectionArgs { id: Scalars['ID']; credentials?: Maybe; + networkCredentials?: Maybe; saveCredentials?: Maybe; } @@ -1110,25 +1111,14 @@ export type DeleteConnectionMutationVariables = Exact<{ export type DeleteConnectionMutation = Pick; -export type DriverListQueryVariables = Exact<{ [key: string]: never }>; - -export interface DriverListQuery { - driverList: Array<( - Pick - & { providerProperties: Array> } - )>; -} - -export type DriverPropertiesQueryVariables = Exact<{ - driverId: Scalars['ID']; +export type DriverListQueryVariables = Exact<{ + driverId?: Maybe; + includeProviderProperties: Scalars['Boolean']; + includeDriverProperties: Scalars['Boolean']; + includeDriverParameters: Scalars['Boolean']; }>; -export interface DriverPropertiesQuery { - driver: Array<( - Pick - & { driverProperties: Array> } - )>; -} +export interface DriverListQuery { drivers: DatabaseDriverFragment[] } export type GetAuthModelsQueryVariables = Exact<{ [key: string]: never }>; @@ -1151,12 +1141,6 @@ export type GetConnectionOriginDetailsQueryVariables = Exact<{ export interface GetConnectionOriginDetailsQuery { connection: { origin: { details?: Maybe>> } } } -export type GetDriverByIdQueryVariables = Exact<{ - driverId: Scalars['ID']; -}>; - -export interface GetDriverByIdQuery { driverList: Array> } - export type GetTemplateConnectionsQueryVariables = Exact<{ [key: string]: never }>; export interface GetTemplateConnectionsQuery { connections: UserConnectionFragment[] } @@ -1242,8 +1226,8 @@ export type NavGetStructContainersQueryVariables = Exact<{ export interface NavGetStructContainersQuery { navGetStructContainers: { catalogList: Array>; schemaList: Array> } } export type AdminConnectionFragment = ( - Pick - & { origin: ObjectOriginInfoFragment; authProperties: UserConnectionAuthPropertiesFragment[]; networkHandlersConfig: Array> } + Pick + & { origin: ObjectOriginInfoFragment; authProperties: UserConnectionAuthPropertiesFragment[]; networkHandlersConfig: Array>; navigatorSettings: AllNavigatorSettingsFragment } ); export type AdminUserInfoFragment = ( @@ -1253,6 +1237,11 @@ export type AdminUserInfoFragment = ( export type AllNavigatorSettingsFragment = Pick; +export type DatabaseDriverFragment = ( + MakeOptional, 'driverParameters'> + & { providerProperties: Array>; driverProperties: Array> } +); + export type NavNodeInfoFragment = ( Pick & { object?: Maybe>; nodeDetails?: Maybe } @@ -1484,7 +1473,7 @@ export type ServerConfigQueryVariables = Exact<{ [key: string]: never }>; export interface ServerConfigQuery { serverConfig: ( - Pick + Pick & { supportedLanguages: Array>; defaultNavigatorSettings: AllNavigatorSettingsFragment } ); } @@ -1555,6 +1544,17 @@ export const UserConnectionAuthPropertiesFragmentDoc = ` order } `; +export const AllNavigatorSettingsFragmentDoc = ` + fragment AllNavigatorSettings on NavigatorSettings { + showSystemObjects + showUtilityObjects + showOnlyEntities + mergeEntities + hideFolders + hideSchemas + hideVirtualModel +} + `; export const AdminConnectionFragmentDoc = ` fragment AdminConnection on ConnectionInfo { id @@ -1563,6 +1563,7 @@ export const AdminConnectionFragmentDoc = ` driverId template connected + provided useUrl readOnly saveCredentials @@ -1589,11 +1590,15 @@ export const AdminConnectionFragmentDoc = ` savePassword properties } + navigatorSettings { + ...AllNavigatorSettings + } features supportedDataFormats } ${ObjectOriginInfoFragmentDoc} -${UserConnectionAuthPropertiesFragmentDoc}`; +${UserConnectionAuthPropertiesFragmentDoc} +${AllNavigatorSettingsFragmentDoc}`; export const AdminUserInfoFragmentDoc = ` fragment AdminUserInfo on AdminUserInfo { userId @@ -1603,6 +1608,45 @@ export const AdminUserInfoFragmentDoc = ` } } ${ObjectOriginInfoFragmentDoc}`; +export const DatabaseDriverFragmentDoc = ` + fragment DatabaseDriver on DriverInfo { + id + name + icon + description + defaultPort + defaultDatabase + defaultServer + defaultUser + sampleURL + embedded + anonymousAccess + promotedScore + defaultAuthModel + applicableNetworkHandlers + providerProperties @include(if: $includeProviderProperties) { + id + displayName + description + category + dataType + defaultValue + validValues + features + order + } + driverProperties @include(if: $includeDriverProperties) { + id + displayName + description + category + dataType + defaultValue + validValues + } + driverParameters @include(if: $includeDriverParameters) +} + `; export const NavNodePropertiesFragmentDoc = ` fragment NavNodeProperties on ObjectPropertyInfo { id @@ -1642,17 +1686,6 @@ export const SessionStateFragmentDoc = ` locale } `; -export const AllNavigatorSettingsFragmentDoc = ` - fragment AllNavigatorSettings on NavigatorSettings { - showSystemObjects - showUtilityObjects - showOnlyEntities - mergeEntities - hideFolders - hideSchemas - hideVirtualModel -} - `; export const UserConnectionFragmentDoc = ` fragment UserConnection on ConnectionInfo { id @@ -1938,52 +1971,12 @@ export const DeleteConnectionDocument = ` } `; export const DriverListDocument = ` - query driverList { - driverList { - id - name - icon - description - defaultPort - defaultDatabase - defaultServer - defaultUser - sampleURL - embedded - anonymousAccess - promotedScore - defaultAuthModel - providerProperties { - id - displayName - description - category - dataType - defaultValue - validValues - features - order - } - applicableNetworkHandlers + query driverList($driverId: ID, $includeProviderProperties: Boolean!, $includeDriverProperties: Boolean!, $includeDriverParameters: Boolean!) { + drivers: driverList(id: $driverId) { + ...DatabaseDriver } } - `; -export const DriverPropertiesDocument = ` - query driverProperties($driverId: ID!) { - driver: driverList(id: $driverId) { - driverProperties { - id - displayName - description - category - dataType - defaultValue - validValues - } - driverParameters - } -} - `; + ${DatabaseDriverFragmentDoc}`; export const GetAuthModelsDocument = ` query getAuthModels { models: authModels { @@ -2039,15 +2032,6 @@ export const GetConnectionOriginDetailsDocument = ` } } `; -export const GetDriverByIdDocument = ` - query getDriverById($driverId: ID!) { - driverList(id: $driverId) { - id - name - icon - } -} - `; export const GetTemplateConnectionsDocument = ` query getTemplateConnections { connections: templateConnections { @@ -2641,12 +2625,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = deleteConnection(variables: DeleteConnectionMutationVariables): Promise { return withWrapper(() => client.request(DeleteConnectionDocument, variables)); }, - driverList(variables?: DriverListQueryVariables): Promise { + driverList(variables: DriverListQueryVariables): Promise { return withWrapper(() => client.request(DriverListDocument, variables)); }, - driverProperties(variables: DriverPropertiesQueryVariables): Promise { - return withWrapper(() => client.request(DriverPropertiesDocument, variables)); - }, getAuthModels(variables?: GetAuthModelsQueryVariables): Promise { return withWrapper(() => client.request(GetAuthModelsDocument, variables)); }, @@ -2656,9 +2637,6 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = getConnectionOriginDetails(variables: GetConnectionOriginDetailsQueryVariables): Promise { return withWrapper(() => client.request(GetConnectionOriginDetailsDocument, variables)); }, - getDriverById(variables: GetDriverByIdQueryVariables): Promise { - return withWrapper(() => client.request(GetDriverByIdDocument, variables)); - }, getTemplateConnections(variables?: GetTemplateConnectionsQueryVariables): Promise { return withWrapper(() => client.request(GetTemplateConnectionsDocument, variables)); }, diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts index 605a344441..e60fbab4de 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts +++ b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts @@ -8,7 +8,7 @@ import { observable, action, makeObservable } from 'mobx'; -import { DBDriver, DatabaseAuthModelsResource, ConnectionInfoResource, getUniqueConnectionName } from '@cloudbeaver/core-connections'; +import { DBDriver, DatabaseAuthModelsResource, ConnectionInfoResource, getUniqueConnectionName, DBDriverResource } from '@cloudbeaver/core-connections'; import { injectable, IInitializableController, IDestructibleController } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -53,6 +53,7 @@ implements IInitializableController, IDestructibleController { private maxHostLength = 20; constructor( + private dbDriverResource: DBDriverResource, private customConnectionService: CustomConnectionService, private notificationService: NotificationService, private commonDialogService: CommonDialogService, @@ -222,6 +223,7 @@ implements IInitializableController, IDestructibleController { } try { + await this.dbDriverResource.load(this.driver.id, ['providerProperties']); this.authModel = await this.dbAuthModelsResource.load(this.driver.defaultAuthModel); } catch (exception) { this.notificationService.logException(exception, 'Can\'t load driver auth model'); diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts index 36ae5885ab..ffcea06fd2 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts +++ b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts @@ -9,7 +9,7 @@ import { observable, makeObservable } from 'mobx'; import type { IProperty } from '@cloudbeaver/core-blocks'; -import { DBDriver, DriverPropertiesService } from '@cloudbeaver/core-connections'; +import { DBDriver, DBDriverResource } from '@cloudbeaver/core-connections'; import { injectable, IInitializableController } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; @@ -37,7 +37,7 @@ export class DriverPropertiesController implements IInitializableController { private state!: Record; constructor( - private driverPropertiesService: DriverPropertiesService, + private dbDriverResource: DBDriverResource, private notificationService: NotificationService ) { makeObservable(this, { @@ -70,8 +70,8 @@ export class DriverPropertiesController implements IInitializableController { } this.isLoading = true; try { - const driverProperties = await this.driverPropertiesService.loadDriverProperties(this.driver.id); - this.driverProperties = observable(driverProperties.map(property => ({ + const driver = await this.dbDriverResource.load(this.driver.id, ['driverProperties']); + this.driverProperties = observable(driver.driverProperties.map(property => ({ id: property.id!, key: property.id!, displayName: property.displayName!,