diff --git a/webapp/packages/core-app/src/shared/NodesManager/DBObjectResource.ts b/webapp/packages/core-app/src/shared/NodesManager/DBObjectResource.ts index facfd60aca..062b42e28e 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/DBObjectResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/DBObjectResource.ts @@ -8,23 +8,26 @@ import { injectable } from '@cloudbeaver/core-di'; import { - GraphQLService, CachedMapResource, ResourceKey, isResourceKeyList, resourceKeyList + GraphQLService, CachedMapResource, ResourceKey, isResourceKeyList, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; import type { DBObject } from './EntityTypes'; import { NavNodeInfoResource } from './NavNodeInfoResource'; +import { NavTreeResource } from './NavTreeResource'; @injectable() export class DBObjectResource extends CachedMapResource { constructor( private graphQLService: GraphQLService, - private navNodeInfoResource: NavNodeInfoResource + private navNodeInfoResource: NavNodeInfoResource, + private navTreeResource: NavTreeResource ) { super(); // this.preloadResource(this.navNodeInfoResource); this.navNodeInfoResource.outdateResource(this); this.navNodeInfoResource.deleteInResource(this); + this.navNodeInfoResource.onDataOutdated.addHandler(this.outdateChildren.bind(this)); } async loadChildren(parentId: string, key: ResourceKey): Promise> { @@ -67,4 +70,18 @@ export class DBObjectResource extends CachedMapResource { return objectInfo; } + + private outdateChildren(key: ResourceKey): void { + const childrenToOutdate: string[] = []; + + ResourceKeyUtils.forEach(key, key => { + childrenToOutdate.push(...this.navTreeResource.get(key) || []); + }); + + const outdateKey = resourceKeyList(childrenToOutdate); + + // if (!this.isOutdated(outdateKey)) { + this.markOutdated(outdateKey); + // } + } } diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts index 4ad9c1887a..01a98ffaf9 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -322,8 +322,7 @@ export class NavTreeResource extends CachedMapResource { if (outdatedTrees.length > 0 || outdatedFolders.length > 0) { const key = resourceKeyList([...outdatedTrees, ...outdatedFolders]); - // this.markOutdated(key); - this.refresh(key); // TODO: better to use markOutdated, but currently leads for missing update if connection was initiated not from nav-tree + this.markOutdated(key); } } @@ -428,7 +427,7 @@ export class NavTreeResource extends CachedMapResource { } } - private async loadNodeChildren(parentPath: string) { + private async loadNodeChildren(parentPath: string): Promise { const metadata = this.metadata.get(parentPath); const { navNodeChildren, navNodeInfo } = await this.graphQLService.sdk.navNodeChildren({ parentPath, diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index fcb6efa30b..e42938bcde 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { action, makeObservable } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor'; @@ -49,6 +49,7 @@ export class ConnectionInfoResource extends CachedMapResource; private sessionUpdate: boolean; + private nodeIdMap: Map; constructor( private graphQLService: GraphQLService, sessionDataResource: SessionDataResource, @@ -59,6 +60,7 @@ export class ConnectionInfoResource extends CachedMapResource(this, { + nodeIdMap: observable, + updateConnection: action, createFromTemplate: action, createConnection: action, createFromNode: action, @@ -82,6 +86,18 @@ export class ConnectionInfoResource extends CachedMapResource { const { connection } = await this.graphQLService.sdk.createConnectionFromTemplate({ templateId, @@ -238,6 +254,19 @@ export class ConnectionInfoResource extends CachedMapResource { const key = resourceKeyList(connections.map(connection => connection.id)); diff --git a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts index ae3b5f0e5d..e83bf4d220 100644 --- a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts +++ b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts @@ -19,6 +19,7 @@ interface ITaskContainer { export type BlockedExecution = (active: T, current: T) => boolean; export interface IScheduleOptions { cancel?: () => Promise | any; + before?: () => any; after?: () => Promise | any; success?: () => Promise | any; error?: (exception: Error) => Promise | any; @@ -56,6 +57,19 @@ export class TaskScheduler { return this.queue.some(active => this.isBlocked!(active.id, id)); } + async waitRelease(id: TIdentifier): Promise { + const promise = this.queue + .slice() + .reverse() + .find(active => this.isBlocked!(active.id, id)); + + if (promise) { + try { + await promise.task; + } catch {} + } + } + schedule( id: TIdentifier, promise: () => Promise, @@ -68,6 +82,7 @@ export class TaskScheduler { const task = new Task(promise, options?.cancel); const container: ITaskContainer = { id, task }; this.queue.push(container); + options?.before?.(); this.execute(container); diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts index 63692211eb..3e9f6569d0 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts @@ -95,10 +95,12 @@ export abstract class CachedMapResource< return resourceKeyList([]); }); - makeObservable(this, { + makeObservable(this, { set: action, delete: action, clear: action, + dataSet: action, + dataDelete: action, values: computed, keys: computed, }); @@ -203,27 +205,30 @@ export abstract class CachedMapResource< markOutdated(): void markOutdated(key: ResourceKey): void markOutdated(key?: ResourceKey): void { + if ( + ( + (key === undefined ? this.scheduler.executing : this.scheduler.isExecuting(key)) + ) && !this.outdateWaitList.some(param => this.includes(key!, param))) { + this.outdateWaitList.push(key as ResourceKey); + return; + } + + this.markOutdatedSync(key as ResourceKey); + } + + cleanError(): void + cleanError(key: ResourceKey): void + cleanError(key?: ResourceKey): void { if (key === undefined) { - key = ResourceKeyUtils.join(resourceKeyList(this.keys), ...this.loadedKeys.map(key => this.transformParam(key))); - this.loadedKeys = []; + key = resourceKeyList(this.keys); } else { - if (this.isAlias(key)) { - const index = this.loadedKeys.findIndex(loadedKey => this.includes(key!, loadedKey)); - - if (index >= 0) { - this.loadedKeys.splice(index, 1); - } - } - key = this.transformParam(key); } - runInAction(() => ResourceKeyUtils.forEach(key!, key => { + ResourceKeyUtils.forEach(key, key => { const metadata = this.metadata.get(key); - metadata.outdated = true; - })); - - this.onDataOutdated.execute(key); + metadata.exception = null; + }); } markUpdated(): void @@ -243,7 +248,6 @@ export abstract class CachedMapResource< ResourceKeyUtils.forEach(key, key => { const metadata = this.metadata.get(key); metadata.outdated = false; - metadata.exception = null; }); } @@ -284,9 +288,9 @@ export abstract class CachedMapResource< key = this.transformParam(key); ResourceKeyUtils.forEach(key, (key, i) => { if (i === -1) { - this.data.set(key, value as TValue); + this.dataSet(key, value as TValue); } else { - this.data.set(key, (value as TValue[])[i]); + this.dataSet(key, (value as TValue[])[i]); } }); this.markUpdated(key); @@ -300,7 +304,7 @@ export abstract class CachedMapResource< key = this.transformParam(key); this.onItemDelete.execute(key); - ResourceKeyUtils.forEach(key, key => this.data.delete(key)); + ResourceKeyUtils.forEach(key, key => this.dataDelete(key)); this.markUpdated(key); } @@ -358,14 +362,6 @@ export abstract class CachedMapResource< return this.data.has(key); } - protected lock(param: ResourceKey, second: ResourceKey): boolean { - if (this.isAlias(param) || this.isAlias(second)) { - return true; - } - - return this.includes(param, second); - } - includes(param: ResourceKey, key: ResourceKey): boolean { if (param === key) { return true; @@ -394,6 +390,22 @@ export abstract class CachedMapResource< }, {}); } + protected dataSet(key: TKey, value: TValue): void { + this.data.set(key, value); + } + + protected dataDelete(key: TKey): void { + this.data.delete(key); + } + + protected lock(param: ResourceKey, second: ResourceKey): boolean { + if (this.isAlias(param) || this.isAlias(second)) { + return true; + } + + return this.includes(param, second); + } + protected resetIncludes(): void { const keys = resourceKeyList(this.keys); ResourceKeyUtils.forEach(keys, key => { @@ -415,4 +427,30 @@ export abstract class CachedMapResource< } }); } + + protected markOutdatedSync(): void + protected markOutdatedSync(key: ResourceKey): void + protected markOutdatedSync(key?: ResourceKey): void { + if (key === undefined) { + key = ResourceKeyUtils.join(resourceKeyList(this.keys), ...this.loadedKeys.map(key => this.transformParam(key))); + this.loadedKeys = []; + } else { + if (this.isAlias(key)) { + const index = this.loadedKeys.findIndex(loadedKey => this.includes(key!, loadedKey)); + + if (index >= 0) { + this.loadedKeys.splice(index, 1); + } + } + + key = this.transformParam(key); + } + + runInAction(() => ResourceKeyUtils.forEach(key!, key => { + const metadata = this.metadata.get(key); + metadata.outdated = true; + })); + + this.onDataOutdated.execute(key); + } } diff --git a/webapp/packages/core-sdk/src/Resource/CachedResource.ts b/webapp/packages/core-sdk/src/Resource/CachedResource.ts index 5ad4111ffd..3590d2445c 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedResource.ts @@ -63,6 +63,7 @@ export abstract class CachedResource< protected scheduler: TaskScheduler; protected paramAliases: Array>; protected logActivity: boolean; + protected outdateWaitList: TParam[]; constructor(defaultValue: TData) { super(); @@ -75,6 +76,7 @@ export abstract class CachedResource< this.loadedKeys = []; this.paramAliases = []; + this.outdateWaitList = []; this.metadata = new MetadataMap(() => (observable({ outdated: true, loading: false, @@ -271,19 +273,19 @@ export abstract class CachedResource< this.onDataError.execute({ param, exception }); } - markOutdated(param: TParam): void { - if (this.isAlias(param)) { - const index = this.loadedKeys.findIndex(key => this.includes(param, key)); - - if (index >= 0) { - this.loadedKeys.splice(index, 1); - } - } - + cleanError(param: TParam): void { param = this.transformParam(param); const metadata = this.metadata.get(param as unknown as TKey); - metadata.outdated = true; - this.onDataOutdated.execute(param); + metadata.exception = null; + } + + markOutdated(param: TParam): void { + if (this.scheduler.isExecuting(param) && !this.outdateWaitList.some(key => this.includes(param, key))) { + this.outdateWaitList.push(param); + return; + } + + this.markOutdatedSync(param); } markUpdated(param: TParam): void { @@ -294,7 +296,6 @@ export abstract class CachedResource< param = this.transformParam(param); const metadata = this.metadata.get(param as unknown as TKey); metadata.outdated = false; - metadata.exception = null; } addAlias(param: TParam, getAlias: (param: TParam) => TParam): void { @@ -335,6 +336,21 @@ export abstract class CachedResource< this.data = data; } + protected markOutdatedSync(param: TParam): void { + if (this.isAlias(param)) { + const index = this.loadedKeys.findIndex(key => this.includes(param, key)); + + if (index >= 0) { + this.loadedKeys.splice(index, 1); + } + } + + param = this.transformParam(param); + const metadata = this.metadata.get(param as unknown as TKey); + metadata.outdated = true; + this.onDataOutdated.execute(param); + } + protected lock(param: TParam, second: TParam): boolean { if (this.isAlias(param) || this.isAlias(second)) { return true; @@ -373,17 +389,18 @@ export abstract class CachedResource< update: (param: TParam, context: TContext) => Promise, exitCheck?: () => boolean ): Promise { - if (exitCheck?.()) { - return; - } - const contexts = await this.beforeLoad.execute(param); if (ExecutorInterrupter.isInterrupted(contexts)) { return; } - this.markDataLoading(param, context); + await this.scheduler.waitRelease(param); + + if (exitCheck?.()) { + return; + } + let loaded = false; return this.scheduler.schedule( param, @@ -393,14 +410,22 @@ export abstract class CachedResource< return; } - const result = await this.taskWrapper(param, context, update); - loaded = true; - return result; + this.markDataLoading(param, context); + try { + const result = await this.taskWrapper(param, context, update); + loaded = true; + return result; + } finally { + this.markDataLoaded(param, context); + } }, { - after: () => this.markDataLoaded(param, context), + before: () => { + this.markOutdatedSync(param); + }, success: () => { if (loaded) { + this.cleanError(param); this.onDataUpdate.execute(param); } }, @@ -415,28 +440,41 @@ export abstract class CachedResource< return; } - if (this.isLoaded(param, context) && !this.isOutdated(param) && !refresh) { - return; + if (!refresh) { + await this.scheduler.waitRelease(param); + + if (this.isLoaded(param, context) && !this.isOutdated(param)) { + return; + } } - this.markDataLoading(param, context); let loaded = false; await this.scheduler.schedule( param, async () => { // repeated because previous task maybe has been load requested data - if (this.isLoaded(param, context) && !this.isOutdated(param) && !refresh) { + if (this.isLoaded(param, context) && !this.isOutdated(param)) { return; } - const result = await this.taskWrapper(param, context, this.loadingTask); - loaded = true; - return result; + this.markDataLoading(param, context); + try { + const result = await this.taskWrapper(param, context, this.loadingTask); + loaded = true; + return result; + } finally { + this.markDataLoaded(param, context); + } }, { - after: () => this.markDataLoaded(param, context), + before: () => { + if (refresh) { + this.markOutdatedSync(param); + } + }, success: async () => { if (loaded) { + this.cleanError(param); this.onDataUpdate.execute(param); } }, @@ -456,9 +494,18 @@ export abstract class CachedResource< if (this.logActivity) { console.log(this.getActionPrefixedName('loading')); } - this.markOutdated(param); + const value = await promise(param, context); this.markUpdated(param); + + for (let i = 0; i < this.outdateWaitList.length; i++) { + const key = this.outdateWaitList[i]; + if (this.includes(param, key)) { + this.markOutdatedSync(param); + this.outdateWaitList.splice(i, 1); + break; + } + } return value; } diff --git a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts index 7cc0728efb..6453d5df5a 100644 --- a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts +++ b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts @@ -46,8 +46,9 @@ export interface ResourceKeyUtils { every: (key: ResourceKey, predicate: (key: TKey, index: number) => boolean) => boolean; map: MapFnc; includes: (first: ResourceKey, second: ResourceKey) => boolean; - exclude: (first: ResourceKeyList, second: ResourceKey) => ResourceKey; - join: (...keys: Array>) => ResourceKey; + exclude: (first: ResourceKeyList, second: ResourceKey) => ResourceKeyList; + join: (...keys: Array>) => ResourceKeyList; + add: (key: ResourceKey, ...elements: TKey[]) => ResourceKeyList; } // eslint-disable-next-line @typescript-eslint/no-redeclare @@ -145,7 +146,7 @@ export const ResourceKeyUtils: ResourceKeyUtils = { return param === key; }, - exclude(param: ResourceKeyList, key: ResourceKey): ResourceKey { + exclude(param: ResourceKeyList, key: ResourceKey): ResourceKeyList { if (isResourceKeyList(key)) { return resourceKeyList(param.list.filter(param => !key.list.includes(param)), param.mark); } @@ -153,7 +154,7 @@ export const ResourceKeyUtils: ResourceKeyUtils = { return resourceKeyList(param.list.filter(param => param !== key), param.mark); }, - join(...keys: Array>): ResourceKey { + join(...keys: Array>): ResourceKeyList { const list: TKey[] = []; for (const param of keys) { @@ -166,6 +167,20 @@ export const ResourceKeyUtils: ResourceKeyUtils = { return resourceKeyList(list); }, + + add(key: ResourceKey, ...elements: TKey[]): ResourceKeyList { + const list: TKey[] = []; + + if (isResourceKeyList(key)) { + list.push(...key.list); + } else { + list.push(key); + } + + list.push(...elements); + + return resourceKeyList(list); + }, }; export function isResourceKeyList(data: any): data is ResourceKeyList { diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts index d3f2c7f84d..d384198e8d 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts @@ -7,6 +7,7 @@ */ import { NavNodeManagerService, INodeNavigationData, ITab } from '@cloudbeaver/core-app'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; @@ -29,6 +30,7 @@ export class DataViewerTabService { private dbObjectPageService: DBObjectPageService, private notificationService: NotificationService, private dataPresentationService: DataPresentationService, + private connectionInfoResource: ConnectionInfoResource, ) { this.page = this.dbObjectPageService.register({ key: 'data_viewer_data', @@ -88,14 +90,23 @@ export class DataViewerTabService { let model = this.dataViewerTableService.get(tab.handlerState.tableId || ''); - if (tab.handlerState.tableId && model && !model.source.executionContext?.context) { - await this.dataViewerTableService.removeTableModel(tab.handlerState.tableId); - model = undefined; + if ( + model + && !model.source.executionContext?.context + && model.source.results.length > 0 + ) { + model.resetData(); } if (!model) { - model = await this.dataViewerTableService.create( - tab.handlerState.connectionId, + const connectionInfo = this.connectionInfoResource.get(tab.handlerState.connectionId); + + if (!connectionInfo) { + throw new Error('Connection doesn\'t exists'); + } + + model = this.dataViewerTableService.create( + connectionInfo, tab.handlerState.objectId ); tab.handlerState.tableId = model.id; diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts index 26565863fa..c7bb2b3740 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { ConnectionInfoResource, ConnectionExecutionContextService } from '@cloudbeaver/core-connections'; +import { ConnectionExecutionContextService, Connection } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { GraphQLService } from '@cloudbeaver/core-sdk'; @@ -23,7 +23,6 @@ import { TableViewerStorageService } from './TableViewer/TableViewerStorageServi export class DataViewerTableService { constructor( private tableViewerStorageService: TableViewerStorageService, - private connectionInfoResource: ConnectionInfoResource, private graphQLService: GraphQLService, private notificationService: NotificationService, private connectionExecutionContextService: ConnectionExecutionContextService, @@ -41,16 +40,15 @@ export class DataViewerTableService { const model = this.tableViewerStorageService.get(tableId); if (model) { - await model.dispose(); this.tableViewerStorageService.remove(tableId); + await model.dispose(); } } - async create( - connectionId: string, + create( + connection: Connection, containerNodePath = '' - ): Promise> { - const connectionInfo = await this.connectionInfoResource.load(connectionId); + ): IDatabaseDataModel { const source = new ContainerDataSource( this.graphQLService, this.notificationService, @@ -59,17 +57,17 @@ export class DataViewerTableService { source .setOptions({ - connectionId, + connectionId: connection.id, containerNodePath, constraints: [], whereFilter: '', }) - .setSupportedDataFormats(connectionInfo.supportedDataFormats); + .setSupportedDataFormats(connection.supportedDataFormats); const dataModel = this.tableViewerStorageService.add(new DatabaseDataModel(source)) .setCountGain(getDefaultRowsCount()) .setSlice(0) - .setAccess(connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default); + .setAccess(connection.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default); return dataModel; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts index 0b7336f951..8f7ed0a327 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts @@ -124,6 +124,10 @@ implements IDatabaseDataModel { return this.source.cancel(); } + resetData(): void { + this.source.resetData(); + } + async dispose(): Promise { await this.source.dispose(); } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts index f16a239ec5..2ba46c6b64 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts @@ -303,6 +303,13 @@ implements IDatabaseDataSource { this.error = null; } + resetData(): void { + if (this.activeSave || this.activeRequest) { + return; + } + this.setResults([]); + } + abstract request(prevResults: TResult[]): TResult[] | Promise; abstract save(prevResults: TResult[]): Promise | TResult[]; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts index 7982ec58a4..c8268714ac 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts @@ -38,5 +38,6 @@ export interface IDatabaseDataModel Promise; requestData: () => Promise; cancel: () => Promise | void; + resetData: () => void; dispose: () => Promise; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts index ca17c39fdb..e5a6bee7da 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts @@ -83,5 +83,6 @@ export interface IDatabaseDataSource Promise | void; cancel: () => Promise | void; clearError: () => void; + resetData: () => void; dispose: () => Promise; } diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/NavNodeMetadata/ObjectProperties.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/NavNodeMetadata/ObjectProperties.tsx index 458ca1315e..4cc55e0966 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/NavNodeMetadata/ObjectProperties.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/NavNodeMetadata/ObjectProperties.tsx @@ -12,6 +12,7 @@ import styled from 'reshadow'; import { DBObjectResource, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-app'; import { ColoredContainer, Loader, TextPlaceholder, useObjectPropertyCategories, GroupTitle, ObjectPropertyInfoFormNew, Group, useMapResource } from '@cloudbeaver/core-blocks'; import { BASE_CONTAINERS_STYLES } from '@cloudbeaver/core-blocks'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; @@ -33,8 +34,15 @@ export const ObjectProperties = observer(function ObjectProperties({ const translate = useTranslate(); const navNodeInfoResource = useService(NavNodeInfoResource); const navTreeResource = useService(NavTreeResource); + const connectionInfoResource = useService(ConnectionInfoResource); const dbObject = useMapResource(ObjectProperties, DBObjectResource, objectId, { - onLoad: async () => !(await preloadNodeParents(navTreeResource, navNodeInfoResource, parents, objectId)), + onLoad: async () => !(await preloadNodeParents( + connectionInfoResource, + navTreeResource, + navNodeInfoResource, + parents, + objectId + )), }); const styles = useStyles(BASE_CONTAINERS_STYLES); const { categories, isUncategorizedExists } = useObjectPropertyCategories( diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx index ff1ef0fc7c..a660bbcd57 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx @@ -11,6 +11,7 @@ import styled, { css } from 'reshadow'; import { DBObject, DBObjectResource, NavNodeInfoResource, NavNodeTransformViewComponent, NavTreeResource } from '@cloudbeaver/core-app'; import { Loader, TextPlaceholder, useMapResource } from '@cloudbeaver/core-blocks'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import { resourceKeyList } from '@cloudbeaver/core-sdk'; @@ -35,12 +36,30 @@ export const VirtualFolderPanel: NavNodeTransformViewComponent = observer(functi const translate = useTranslate(); const nodeType = VirtualFolderUtils.getNodeType(folderId); const navNodeInfoResource = useService(NavNodeInfoResource); + const connectionInfoResource = useService(ConnectionInfoResource); const tree = useMapResource(VirtualFolderPanel, NavTreeResource, nodeId, { - onLoad: async resource => !(await preloadNodeParents(resource, navNodeInfoResource, parents, nodeId)), + onLoad: async resource => !(await preloadNodeParents( + connectionInfoResource, + resource, + navNodeInfoResource, + parents, + nodeId)), }); + const key = resourceKeyList(tree.data || []); const dbObject = useMapResource(VirtualFolderPanel, DBObjectResource, key, { async onLoad(resource: DBObjectResource) { + const preloaded = await preloadNodeParents(connectionInfoResource, + tree.resource, + navNodeInfoResource, + parents, + nodeId + ); + + if (!preloaded) { + return true; + } + await resource.loadChildren(nodeId, key); return true; }, diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx index 30608edf97..d6bb8d00cb 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx @@ -12,6 +12,7 @@ import styled, { css } from 'reshadow'; import { ITab, NavNodeInfoResource, NavNodeManagerService, NavNodeViewService, NavTreeResource } from '@cloudbeaver/core-app'; import { ITabData, Loader, TabList, TabPanel, TabsState, TextPlaceholder, useMapResource, verticalTabStyles } from '@cloudbeaver/core-blocks'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles, composes } from '@cloudbeaver/core-theming'; @@ -74,6 +75,7 @@ export const ObjectFolders = observer(function ObjectFolders({ tab }) { const navNodeManagerService = useService(NavNodeManagerService); const navNodeViewService = useService(NavNodeViewService); const navNodeInfoResource = useService(NavNodeInfoResource); + const connectionInfoResource = useService(ConnectionInfoResource); const style = useStyles(verticalTabStyles, styles); const nodeId = tab.handlerState.objectId; @@ -82,7 +84,13 @@ export const ObjectFolders = observer(function ObjectFolders({ tab }) { let folderId = tab.handlerState.folderId; const children = useMapResource(ObjectFolders, NavTreeResource, nodeId, { - onLoad: async resource => !(await preloadNodeParents(resource, navNodeInfoResource, parents, nodeId)), + onLoad: async resource => !(await preloadNodeParents( + connectionInfoResource, + resource, + navNodeInfoResource, + parents, + nodeId + )), }); const folders = navNodeViewService.getFolders(nodeId) || []; diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx index 11ac722382..0bcb6e17bc 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx @@ -11,6 +11,7 @@ import styled, { css } from 'reshadow'; import { DBObject, DBObjectResource, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-app'; import { Loader, TextPlaceholder, useMapResource } from '@cloudbeaver/core-blocks'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import { resourceKeyList } from '@cloudbeaver/core-sdk'; @@ -39,16 +40,29 @@ export const ObjectPropertyTable = observer(function O parentId, parents, }) { + parents = [...parents, parentId]; const translate = useTranslate(); const navNodeInfoResource = useService(NavNodeInfoResource); - const navTreeResource = useService(NavTreeResource); + const connectionInfoResource = useService(ConnectionInfoResource); const tree = useMapResource(ObjectPropertyTable, NavTreeResource, objectId, { - onLoad: async () => !(await preloadNodeParents(navTreeResource, navNodeInfoResource, parents, objectId)), + onLoad: async resource => !(await preloadNodeParents( + connectionInfoResource, + resource, + navNodeInfoResource, + parents, + objectId + )), }); const key = resourceKeyList(tree.data || []); const dbObject = useMapResource(ObjectPropertyTable, DBObjectResource, key, { async onLoad(resource: DBObjectResource) { - const preloaded = await preloadNodeParents(navTreeResource, navNodeInfoResource, parents); + const preloaded = await preloadNodeParents( + connectionInfoResource, + tree.resource, + navNodeInfoResource, + parents, + objectId + ); if (!preloaded) { return true; diff --git a/webapp/packages/plugin-object-viewer/src/ObjectViewerPanel.tsx b/webapp/packages/plugin-object-viewer/src/ObjectViewerPanel.tsx index 2501eb0e93..3ba4aaa85c 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectViewerPanel.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectViewerPanel.tsx @@ -71,7 +71,12 @@ export const ObjectViewerPanel: TabHandlerPanelComponent const children = useMapResource(ObjectViewerPanel, NavTreeResource, parentId, { onLoad: async resource => { - const preload = await preloadNodeParents(resource, navNodeInfoResource, parents); + const preload = await preloadNodeParents( + connection.resource, + resource, + navNodeInfoResource, + parents + ); state.notFound = !preload; return state.notFound; }, @@ -83,7 +88,13 @@ export const ObjectViewerPanel: TabHandlerPanelComponent }); const node = useMapResource(ObjectViewerPanel, navNodeInfoResource, objectId, { - onLoad: async resource => !(await preloadNodeParents(children.resource, resource, parents, objectId)), + onLoad: async resource => !(await preloadNodeParents( + connection.resource, + children.resource, + resource, + parents, + objectId + )), onData(data) { tab.handlerState.tabIcon = data.icon; tab.handlerState.tabTitle = data.name; diff --git a/webapp/packages/plugin-object-viewer/src/preloadNodeParents.ts b/webapp/packages/plugin-object-viewer/src/preloadNodeParents.ts index 3955ec0e79..f8e9b4632c 100644 --- a/webapp/packages/plugin-object-viewer/src/preloadNodeParents.ts +++ b/webapp/packages/plugin-object-viewer/src/preloadNodeParents.ts @@ -7,8 +7,10 @@ */ import type { NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-app'; +import type { ConnectionInfoResource } from '@cloudbeaver/core-connections'; export async function preloadNodeParents( + connectionInfoResource: ConnectionInfoResource, navTreeResource: NavTreeResource, navNodeInfoResource: NavNodeInfoResource, parents: string[], @@ -19,12 +21,20 @@ export async function preloadNodeParents( } const first = parents[0]; + await connectionInfoResource.waitLoad(); await navTreeResource.load(first); for (const nodeId of parents) { if (!navNodeInfoResource.has(nodeId)) { return false; } + + const connection = connectionInfoResource.getConnectionForNode(nodeId); + + if (connection && !connection.connected) { + return false; + } + await navTreeResource.load(nodeId); }