refactor: CB-722 resource's includes API

This commit is contained in:
Wroud
2021-02-14 01:17:32 +03:00
parent 6ca613d6c0
commit ec6e3253c2
32 changed files with 398 additions and 295 deletions
+1
View File
@@ -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',
@@ -20,7 +20,7 @@ export class DBObjectService extends CachedMapResource<string, DBObject> {
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));
}
@@ -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<NavNode> {
withDetails: boolean;
}
@@ -34,7 +34,7 @@ interface INodeMetadata extends ICachedResourceMetadata {
export class NavNodeInfoResource extends CachedMapResource<string, NavNode> {
protected metadata: MetadataMap<string, INodeMetadata>;
constructor(private graphQLService: GraphQLService) {
super(new Map());
super();
makeObservable(this, {
setDetails: action,
@@ -44,6 +44,8 @@ export class NavNodeInfoResource extends CachedMapResource<string, NavNode> {
outdated: true,
loading: false,
withDetails: false,
includes: [],
loadedIncludes: [],
}));
}
@@ -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<string[]> {
withDetails: boolean;
}
@@ -41,7 +43,7 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
private graphQLService: GraphQLService,
private navNodeInfoResource: NavNodeInfoResource
) {
super(new Map());
super();
makeObservable(this, {
setDetails: action,
@@ -51,6 +53,8 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
outdated: true,
loading: false,
withDetails: false,
includes: [],
loadedIncludes: [],
}));
this.onDataOutdated.addHandler(navNodeInfoResource.markOutdated.bind(navNodeInfoResource));
}
@@ -19,7 +19,7 @@ import { MetadataMap } from '@cloudbeaver/core-utils';
export class RolesResource extends CachedMapResource<string, AdminRoleInfo> {
private loadedKeyMetadata: MetadataMap<string, boolean>;
constructor(private graphQLService: GraphQLService) {
super(new Map());
super();
this.loadedKeyMetadata = new MetadataMap(() => false);
}
@@ -42,7 +42,7 @@ export class UsersResource extends CachedMapResource<string, AdminUser> {
private authProviderService: AuthProviderService,
private authInfoService: AuthInfoService
) {
super(new Map());
super();
this.loadedKeyMetadata = new MetadataMap(() => false);
}
@@ -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>(() => (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))(
<properties as="div" className={className}>
@@ -119,7 +116,7 @@ export const PropertiesTable = observer(function PropertiesTable({
</properties-header-right>
</properties-header>
<properties-list as="div">
{alphabetOrderProperties.get().map(property => (
{alphabetOrderProperties.map(property => (
<PropertyItem
key={property.id}
property={property}
@@ -26,38 +26,50 @@ interface IActions<TResource> {
) => Promise<any> | any;
}
interface KeyWithIncludes<TKey, TIncludes> {
key: TKey | null;
includes: TIncludes;
}
interface IMapResourceResult<
TKeyArg extends ResourceKey<CachedMapResourceKey<TResource>>,
TResource extends CachedMapResource<any, any>
TResource extends CachedMapResource<any, any>,
TIncludes extends Array<keyof CachedMapResourceValue<TResource>>
> {
data: CachedMapResourceGetter<TKeyArg, CachedMapResourceKey<TResource>, CachedMapResourceValue<TResource>>;
data: CachedMapResourceGetter<TKeyArg, CachedMapResourceKey<TResource>, CachedMapResourceValue<TResource>, TIncludes>;
resource: TResource;
isLoading: () => boolean;
isLoaded: () => boolean;
}
export function useMapResource<
TResource extends CachedMapResource<any, any>,
TKeyArg extends ResourceKey<CachedMapResourceKey<TResource>>,
TIncludes extends Array<keyof CachedMapResourceValue<TResource>> = []
>(
ctor: IServiceConstructor<TResource>,
key: TKeyArg | null,
keyObj: TKeyArg | null | KeyWithIncludes<TKeyArg, TIncludes>,
actions?: IActions<TResource>
): IMapResourceResult<TKeyArg, TResource> {
): IMapResourceResult<TKeyArg, TResource, TIncludes> {
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<TResource>,
}, {
resource,
key,
includes,
actions,
});
const [result] = useState<IMapResourceResult<TKeyArg, TResource>>(() => ({
const [result] = useState<IMapResourceResult<TKeyArg, TResource, TIncludes>>(() => ({
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;
}
@@ -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,
@@ -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<string, string>;
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<IProperty>(property => ({
const driver = await this.dbDriverResource.load(this.driverId, ['driverProperties']);
this.driverProperties = observable(driver.driverProperties.map<IProperty>(property => ({
id: property.id!,
key: property.id!,
keyPlaceholder: property.id,
@@ -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
);
@@ -43,7 +43,7 @@ export class ConnectionsResource extends CachedMapResource<string, AdminConnecti
constructor(
private graphQLService: GraphQLService
) {
super(new Map());
super();
makeObservable(this, {
add: action,
@@ -37,7 +37,7 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
appAuthService: AppAuthService,
sessionResource: SessionResource
) {
super(new Map());
super();
makeObservable(this, {
refreshSession: action,
@@ -10,35 +10,22 @@ import { injectable } from '@cloudbeaver/core-di';
import {
GraphQLService,
CachedMapResource,
DriverInfo
ResourceKey,
ResourceKeyUtils,
DatabaseDriverFragment
} from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
export type DBDriver = Pick<
DriverInfo,
| 'id'
| 'name'
| 'icon'
| 'description'
| 'defaultPort'
| 'defaultDatabase'
| 'defaultServer'
| 'defaultUser'
| 'sampleURL'
| 'embedded'
| 'anonymousAccess'
| 'promotedScore'
| 'defaultAuthModel'
| 'applicableNetworkHandlers'
| 'providerProperties'
>;
export type DBDriver = DatabaseDriverFragment;
const allKey = 'all';
@injectable()
export class DBDriverResource extends CachedMapResource<string, DBDriver> {
private loadedKeyMetadata: MetadataMap<string, boolean>;
constructor(private graphQLService: GraphQLService) {
super(new Map());
super();
this.loadedKeyMetadata = new MetadataMap(() => false);
}
@@ -51,7 +38,7 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver> {
}
async loadAll(): Promise<Map<string, DBDriver>> {
await this.load('all');
await this.load(allKey);
return this.data;
}
@@ -63,18 +50,30 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver> {
return (driverB.promotedScore || 0) - (driverA.promotedScore || 0);
}
protected async loader(key: string): Promise<Map<string, DBDriver>> {
const { driverList } = await this.graphQLService.sdk.driverList();
protected async loader(key: ResourceKey<string>): Promise<Map<string, DBDriver>> {
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;
}
@@ -17,7 +17,7 @@ import {
@injectable()
export class DatabaseAuthModelsResource extends CachedMapResource<string, DatabaseAuthModel> {
constructor(private graphQLService: GraphQLService) {
super(new Map());
super();
}
protected async loader(key: string): Promise<Map<string, DatabaseAuthModel>> {
@@ -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<ObjectPropertyInfo[]> {
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[];
}
}
@@ -25,7 +25,7 @@ export class NetworkHandlerResource extends CachedMapResource<string, NetworkHan
private readonly graphQLService: GraphQLService,
private readonly notificationService: NotificationService
) {
super(new Map());
super();
this.loadedKeyMetadata = new MetadataMap(() => false);
}
@@ -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';
@@ -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;
@@ -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,
@@ -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<keyof TValue>> = TValue
& (TKeys extends Array<infer T> ?
{
[P in Extract<T, keyof TValue>]-?: Required<TValue>[P] extends undefined ? TValue[P] : NonNullable<TValue[P]>;
}
// eslint-disable-next-line @typescript-eslint/ban-types
: { });
export type CachedMapResourceGetter<
TRealKey extends ResourceKey<TKey>,
TKey,
TValue
TValue,
TIncludes extends Array<keyof TValue>
> = TRealKey extends ResourceKeyList<TKey>
? Array<TValue | undefined>
: TValue | undefined;
? Array<CachedMapValueIncludes<TValue, TIncludes> | undefined>
: CachedMapValueIncludes<TValue, TIncludes> | undefined;
@injectable()
export abstract class CachedMapResource<TKey, TValue> extends CachedResource<
Map<TKey, TValue>,
ResourceKey<TKey>,
TKey
> {
export interface ICachedMapResourceMetadata<TValue> extends ICachedResourceMetadata {
includes: Array<keyof TValue>;
loadedIncludes: Array<keyof TValue>;
}
export abstract class CachedMapResource<
TKey,
TValue
> extends CachedResource<
Map<TKey, TValue>,
ResourceKey<TKey>,
TKey
> {
readonly onItemAdd: IExecutor<ResourceKey<TKey>>;
readonly onItemDelete: IExecutor<ResourceKey<TKey>>;
protected metadata: MetadataMap<TKey, ICachedMapResourceMetadata<TValue>>;
private defaultIncludes: Array<keyof TValue>;
get values(): TValue[] {
return Array.from(this.data.values());
@@ -39,10 +57,18 @@ TKey
return Array.from(this.data.keys());
}
constructor(defaultValue?: Map<TKey, TValue>) {
constructor(defaultIncludes?: Array<keyof TValue>, defaultValue?: Map<TKey, TValue>) {
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<TKey>, includes: Array<keyof TValue>): boolean {
return ResourceKeyUtils.every(key, key => {
const metadata = this.metadata.get(key);
return includes.every(include => metadata.includes.includes(include));
});
}
isOutdated(key: ResourceKey<TKey>): boolean {
return ResourceKeyUtils.some(key, key => this.metadata.get(key).outdated);
}
@@ -66,6 +100,7 @@ TKey
}
markDataLoaded(key: ResourceKey<TKey>): 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<TKey>): boolean {
return ResourceKeyUtils.every(key, key => this.has(key));
isLoaded(key: ResourceKey<TKey>, includes?: Array<keyof TValue>): 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<TValue>;
async refresh(key: ResourceKeyList<TKey>): Promise<TValue[]>;
async refresh(key: ResourceKey<TKey>): Promise<TValue[]| TValue>;
async refresh(key: ResourceKey<TKey>): Promise<TValue[]| TValue> {
async refresh<T extends Array<keyof TValue> = []>(
key: TKey,
includes?: T
): Promise<CachedMapValueIncludes<TValue, T>>;
async refresh<T extends Array<keyof TValue> = []>(
key: ResourceKeyList<TKey>,
includes?: T
): Promise<Array<CachedMapValueIncludes<TValue, T>>>;
async refresh<T extends Array<keyof TValue> = []>(
key: ResourceKey<TKey>,
includes?: T
): Promise<Array<CachedMapValueIncludes<TValue, T>> | CachedMapValueIncludes<TValue, T>>;
async refresh<T extends Array<keyof TValue> = []>(
key: ResourceKey<TKey>,
includes?: T
): Promise<Array<CachedMapValueIncludes<TValue, T>> | CachedMapValueIncludes<TValue, T>> {
this.setIncludes(key, includes || []);
await this.loadData(key, true);
return this.get(key) as TValue[]| TValue;
return this.get(key) as Array<CachedMapValueIncludes<TValue, T>> | CachedMapValueIncludes<TValue, T>;
}
async load(key: TKey): Promise<TValue>;
async load(key: ResourceKeyList<TKey>): Promise<TValue[]>;
async load(key: ResourceKey<TKey>): Promise<TValue[]| TValue>;
async load(key: ResourceKey<TKey>): Promise<TValue[]| TValue> {
async load<T extends Array<keyof TValue> = []>(
key: TKey,
includes?: T
): Promise<CachedMapValueIncludes<TValue, T>>;
async load<T extends Array<keyof TValue> = []>(
key: ResourceKeyList<TKey>,
includes?: T
): Promise<Array<CachedMapValueIncludes<TValue, T>>>;
async load<T extends Array<keyof TValue> = []>(
key: ResourceKey<TKey>,
includes?: T
): Promise<Array<CachedMapValueIncludes<TValue, T>> | CachedMapValueIncludes<TValue, T>>;
async load<T extends Array<keyof TValue> = []>(
key: ResourceKey<TKey>,
includes?: T
): Promise<Array<CachedMapValueIncludes<TValue, T>> | CachedMapValueIncludes<TValue, T>> {
this.setIncludes(key, includes || []);
await this.loadData(key);
return this.get(key) as TValue[]| TValue;
return this.get(key) as Array<CachedMapValueIncludes<TValue, T>> | CachedMapValueIncludes<TValue, T>;
}
has(key: TKey): boolean {
@@ -156,4 +236,55 @@ TKey
includes(param: ResourceKey<TKey>, key: ResourceKey<TKey>): boolean {
return ResourceKeyUtils.includes(param, key);
}
getIncludes(key?: ResourceKey<TKey>): Record<string, any> {
if (!key) {
return ['base', ...this.defaultIncludes].reduce<any>((map, key) => {
map[this.getIncludeName(key as string)] = true;
return map;
}, {});
}
const metadata = this.metadata.get(ResourceKeyUtils.first(key));
return ['base', ...metadata.includes].reduce<any>((map, key) => {
map[this.getIncludeName(key as string)] = true;
return map;
}, {});
}
private setIncludes(key: ResourceKey<TKey>, includes: Array<keyof TValue>) {
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<TKey>) {
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);
}
}
@@ -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,
@@ -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<TParam>;
constructor(defaultValue: TData) {
makeObservable<LocalResource, 'outdated' | 'dataLoading' | 'loading'>(this, {
makeObservable<LocalResource<TData, TParam>, 'outdated' | 'dataLoading' | 'loading'>(this, {
data: observable,
outdated: observable,
dataLoading: observable,
@@ -35,6 +35,7 @@ interface MapFnc {
}
export interface ResourceKeyUtils {
first: <TKey>(key: ResourceKey<TKey>) => TKey;
forEach: <TKey>(key: ResourceKey<TKey>, action: (key: TKey, index: number) => any) => void;
forEachAsync: <TKey>(key: ResourceKey<TKey>, action: (key: TKey, index: number) => Promise<any>) => Promise<void>;
some: <TKey>(key: ResourceKey<TKey>, 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<TKey>(
key: ResourceKey<TKey>
): TKey {
if (isResourceKeyList(key)) {
return key.list[0];
} else {
return key;
}
},
forEach<TKey>(
key: ResourceKey<TKey>,
action: (key: TKey, index: number) => any | Promise<any>
@@ -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
}
}
@@ -1,14 +0,0 @@
query driverProperties($driverId: ID!) {
driver: driverList(id: $driverId) {
driverProperties {
id
displayName
description
category
dataType
defaultValue
validValues
}
driverParameters
}
}
@@ -1,7 +0,0 @@
query getDriverById($driverId: ID!) {
driverList(id: $driverId) {
id
name
icon
}
}
@@ -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
}
@@ -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)
}
+76 -98
View File
@@ -335,6 +335,7 @@ export interface MutationDeleteConnectionArgs {
export interface MutationInitConnectionArgs {
id: Scalars['ID'];
credentials?: Maybe<Scalars['Object']>;
networkCredentials?: Maybe<NetworkHandlerConfigInput[]>;
saveCredentials?: Maybe<Scalars['Boolean']>;
}
@@ -1110,25 +1111,14 @@ export type DeleteConnectionMutationVariables = Exact<{
export type DeleteConnectionMutation = Pick<Mutation, 'deleteConnection'>;
export type DriverListQueryVariables = Exact<{ [key: string]: never }>;
export interface DriverListQuery {
driverList: Array<(
Pick<DriverInfo, 'id' | 'name' | 'icon' | 'description' | 'defaultPort' | 'defaultDatabase' | 'defaultServer' | 'defaultUser' | 'sampleURL' | 'embedded' | 'anonymousAccess' | 'promotedScore' | 'defaultAuthModel' | 'applicableNetworkHandlers'>
& { providerProperties: Array<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues' | 'features' | 'order'>> }
)>;
}
export type DriverPropertiesQueryVariables = Exact<{
driverId: Scalars['ID'];
export type DriverListQueryVariables = Exact<{
driverId?: Maybe<Scalars['ID']>;
includeProviderProperties: Scalars['Boolean'];
includeDriverProperties: Scalars['Boolean'];
includeDriverParameters: Scalars['Boolean'];
}>;
export interface DriverPropertiesQuery {
driver: Array<(
Pick<DriverInfo, 'driverParameters'>
& { driverProperties: Array<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues'>> }
)>;
}
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<Array<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues' | 'value' | 'features' | 'order'>>> } } }
export type GetDriverByIdQueryVariables = Exact<{
driverId: Scalars['ID'];
}>;
export interface GetDriverByIdQuery { driverList: Array<Pick<DriverInfo, 'id' | 'name' | 'icon'>> }
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<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>>; schemaList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>> } }
export type AdminConnectionFragment = (
Pick<ConnectionInfo, 'id' | 'name' | 'description' | 'driverId' | 'template' | 'connected' | 'useUrl' | 'readOnly' | 'saveCredentials' | 'host' | 'port' | 'databaseName' | 'url' | 'properties' | 'providerProperties' | 'features' | 'authNeeded' | 'authModel' | 'supportedDataFormats'>
& { origin: ObjectOriginInfoFragment; authProperties: UserConnectionAuthPropertiesFragment[]; networkHandlersConfig: Array<Pick<NetworkHandlerConfig, 'id' | 'enabled' | 'userName' | 'password' | 'savePassword' | 'properties'>> }
Pick<ConnectionInfo, 'id' | 'name' | 'description' | 'driverId' | 'template' | 'connected' | 'provided' | 'useUrl' | 'readOnly' | 'saveCredentials' | 'host' | 'port' | 'databaseName' | 'url' | 'properties' | 'providerProperties' | 'features' | 'authNeeded' | 'authModel' | 'supportedDataFormats'>
& { origin: ObjectOriginInfoFragment; authProperties: UserConnectionAuthPropertiesFragment[]; networkHandlersConfig: Array<Pick<NetworkHandlerConfig, 'id' | 'enabled' | 'userName' | 'password' | 'savePassword' | 'properties'>>; navigatorSettings: AllNavigatorSettingsFragment }
);
export type AdminUserInfoFragment = (
@@ -1253,6 +1237,11 @@ export type AdminUserInfoFragment = (
export type AllNavigatorSettingsFragment = Pick<NavigatorSettings, 'showSystemObjects' | 'showUtilityObjects' | 'showOnlyEntities' | 'mergeEntities' | 'hideFolders' | 'hideSchemas' | 'hideVirtualModel'>;
export type DatabaseDriverFragment = (
MakeOptional<Pick<DriverInfo, 'id' | 'name' | 'icon' | 'description' | 'defaultPort' | 'defaultDatabase' | 'defaultServer' | 'defaultUser' | 'sampleURL' | 'embedded' | 'anonymousAccess' | 'promotedScore' | 'defaultAuthModel' | 'applicableNetworkHandlers' | 'driverParameters'>, 'driverParameters'>
& { providerProperties: Array<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues' | 'features' | 'order'>>; driverProperties: Array<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues'>> }
);
export type NavNodeInfoFragment = (
Pick<NavigatorNodeInfo, 'id' | 'name' | 'hasChildren' | 'nodeType' | 'icon' | 'folder' | 'inline' | 'navigable' | 'features'>
& { object?: Maybe<Pick<DatabaseObjectInfo, 'features'>>; nodeDetails?: Maybe<NavNodePropertiesFragment[]> }
@@ -1484,7 +1473,7 @@ export type ServerConfigQueryVariables = Exact<{ [key: string]: never }>;
export interface ServerConfigQuery {
serverConfig: (
Pick<ServerConfig, 'name' | 'version' | 'productConfiguration' | 'supportsCustomConnections' | 'supportsConnectionBrowser' | 'supportsWorkspaces' | 'sessionExpireTime' | 'anonymousAccessEnabled' | 'authenticationEnabled' | 'configurationMode' | 'developmentMode'>
Pick<ServerConfig, 'name' | 'version' | 'productConfiguration' | 'supportsCustomConnections' | 'supportsConnectionBrowser' | 'supportsWorkspaces' | 'sessionExpireTime' | 'anonymousAccessEnabled' | 'authenticationEnabled' | 'adminCredentialsSaveEnabled' | 'publicCredentialsSaveEnabled' | 'configurationMode' | 'developmentMode'>
& { supportedLanguages: Array<Pick<ServerLanguage, 'isoCode' | 'displayName' | 'nativeName'>>; 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<DeleteConnectionMutation> {
return withWrapper(() => client.request<DeleteConnectionMutation>(DeleteConnectionDocument, variables));
},
driverList(variables?: DriverListQueryVariables): Promise<DriverListQuery> {
driverList(variables: DriverListQueryVariables): Promise<DriverListQuery> {
return withWrapper(() => client.request<DriverListQuery>(DriverListDocument, variables));
},
driverProperties(variables: DriverPropertiesQueryVariables): Promise<DriverPropertiesQuery> {
return withWrapper(() => client.request<DriverPropertiesQuery>(DriverPropertiesDocument, variables));
},
getAuthModels(variables?: GetAuthModelsQueryVariables): Promise<GetAuthModelsQuery> {
return withWrapper(() => client.request<GetAuthModelsQuery>(GetAuthModelsDocument, variables));
},
@@ -2656,9 +2637,6 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
getConnectionOriginDetails(variables: GetConnectionOriginDetailsQueryVariables): Promise<GetConnectionOriginDetailsQuery> {
return withWrapper(() => client.request<GetConnectionOriginDetailsQuery>(GetConnectionOriginDetailsDocument, variables));
},
getDriverById(variables: GetDriverByIdQueryVariables): Promise<GetDriverByIdQuery> {
return withWrapper(() => client.request<GetDriverByIdQuery>(GetDriverByIdDocument, variables));
},
getTemplateConnections(variables?: GetTemplateConnectionsQueryVariables): Promise<GetTemplateConnectionsQuery> {
return withWrapper(() => client.request<GetTemplateConnectionsQuery>(GetTemplateConnectionsDocument, variables));
},
@@ -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');
@@ -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<string, string>;
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<IProperty>(property => ({
const driver = await this.dbDriverResource.load(this.driver.id, ['driverProperties']);
this.driverProperties = observable(driver.driverProperties.map<IProperty>(property => ({
id: property.id!,
key: property.id!,
displayName: property.displayName!,