fix: CB-1312 metadata refresh

This commit is contained in:
Wroud
2021-10-13 19:34:38 +03:00
parent cce2853eb1
commit c898f823b7
19 changed files with 344 additions and 92 deletions
@@ -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<string, DBObject> {
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<string>): Promise<Map<string, DBObject>> {
@@ -67,4 +70,18 @@ export class DBObjectResource extends CachedMapResource<string, DBObject> {
return objectInfo;
}
private outdateChildren(key: ResourceKey<string>): 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);
// }
}
}
@@ -322,8 +322,7 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
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<string, string[]> {
}
}
private async loadNodeChildren(parentPath: string) {
private async loadNodeChildren(parentPath: string): Promise<NavNodeChildrenQuery> {
const metadata = this.metadata.get(parentPath);
const { navNodeChildren, navNodeInfo } = await this.graphQLService.sdk.navNodeChildren({
parentPath,
@@ -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<string, Connection
readonly onConnectionClose: IExecutor<Connection>;
private sessionUpdate: boolean;
private nodeIdMap: Map<string, string>;
constructor(
private graphQLService: GraphQLService,
sessionDataResource: SessionDataResource,
@@ -59,6 +60,7 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
this.onConnectionCreate = new Executor();
this.onConnectionClose = new Executor();
this.sessionUpdate = false;
this.nodeIdMap = new Map();
// in case when session was refreshed all data depended on connection info
// should be refreshed by session update executor
@@ -74,7 +76,9 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
this.markOutdated();
});
makeObservable(this, {
makeObservable<this, 'nodeIdMap' | 'updateConnection'>(this, {
nodeIdMap: observable,
updateConnection: action,
createFromTemplate: action,
createConnection: action,
createFromNode: action,
@@ -82,6 +86,18 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
});
}
getConnectionForNode(nodeId: string): Connection | undefined {
const connectionPart = nodeId.slice(0, 53);
const connectionId = this.nodeIdMap.get(connectionPart);
if (connectionId) {
return this.get(connectionId);
}
return undefined;
}
async createFromTemplate(templateId: string, connectionName: string): Promise<Connection> {
const { connection } = await this.graphQLService.sdk.createConnectionFromTemplate({
templateId,
@@ -238,6 +254,19 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
return this.data;
}
protected dataSet(key: string, value: Connection): void {
this.data.set(key, value);
if (value.nodePath) {
this.nodeIdMap.set(value.nodePath, key);
}
}
protected dataDelete(key: string): void {
this.data.delete(key);
this.nodeIdMap.delete(key);
}
private updateConnection(...connections: Connection[]): ResourceKeyList<string> {
const key = resourceKeyList(connections.map(connection => connection.id));
@@ -19,6 +19,7 @@ interface ITaskContainer<T, TValue> {
export type BlockedExecution<T> = (active: T, current: T) => boolean;
export interface IScheduleOptions {
cancel?: () => Promise<any> | any;
before?: () => any;
after?: () => Promise<any> | any;
success?: () => Promise<any> | any;
error?: (exception: Error) => Promise<any> | any;
@@ -56,6 +57,19 @@ export class TaskScheduler<TIdentifier> {
return this.queue.some(active => this.isBlocked!(active.id, id));
}
async waitRelease(id: TIdentifier): Promise<void> {
const promise = this.queue
.slice()
.reverse()
.find(active => this.isBlocked!(active.id, id));
if (promise) {
try {
await promise.task;
} catch {}
}
}
schedule<T>(
id: TIdentifier,
promise: () => Promise<T>,
@@ -68,6 +82,7 @@ export class TaskScheduler<TIdentifier> {
const task = new Task<T>(promise, options?.cancel);
const container: ITaskContainer<TIdentifier, T> = { id, task };
this.queue.push(container);
options?.before?.();
this.execute(container);
@@ -95,10 +95,12 @@ export abstract class CachedMapResource<
return resourceKeyList([]);
});
makeObservable(this, {
makeObservable<this, 'dataSet' | 'dataDelete'>(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<TKey>): void
markOutdated(key?: ResourceKey<TKey>): 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<TKey>);
return;
}
this.markOutdatedSync(key as ResourceKey<TKey>);
}
cleanError(): void
cleanError(key: ResourceKey<TKey>): void
cleanError(key?: ResourceKey<TKey>): 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<TKey>, second: ResourceKey<TKey>): boolean {
if (this.isAlias(param) || this.isAlias(second)) {
return true;
}
return this.includes(param, second);
}
includes(param: ResourceKey<TKey>, key: ResourceKey<TKey>): 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<TKey>, second: ResourceKey<TKey>): 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<TKey>): void
protected markOutdatedSync(key?: ResourceKey<TKey>): 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);
}
}
@@ -63,6 +63,7 @@ export abstract class CachedResource<
protected scheduler: TaskScheduler<TParam>;
protected paramAliases: Array<IParamAlias<TParam>>;
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<T>,
exitCheck?: () => boolean
): Promise<T | undefined> {
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;
}
@@ -46,8 +46,9 @@ export interface ResourceKeyUtils {
every: <TKey>(key: ResourceKey<TKey>, predicate: (key: TKey, index: number) => boolean) => boolean;
map: MapFnc;
includes: <TKey>(first: ResourceKey<TKey>, second: ResourceKey<TKey>) => boolean;
exclude: <TKey>(first: ResourceKeyList<TKey>, second: ResourceKey<TKey>) => ResourceKey<TKey>;
join: <TKey>(...keys: Array<ResourceKey<TKey>>) => ResourceKey<TKey>;
exclude: <TKey>(first: ResourceKeyList<TKey>, second: ResourceKey<TKey>) => ResourceKeyList<TKey>;
join: <TKey>(...keys: Array<ResourceKey<TKey>>) => ResourceKeyList<TKey>;
add: <TKey>(key: ResourceKey<TKey>, ...elements: TKey[]) => ResourceKeyList<TKey>;
}
// eslint-disable-next-line @typescript-eslint/no-redeclare
@@ -145,7 +146,7 @@ export const ResourceKeyUtils: ResourceKeyUtils = {
return param === key;
},
exclude<TKey>(param: ResourceKeyList<TKey>, key: ResourceKey<TKey>): ResourceKey<TKey> {
exclude<TKey>(param: ResourceKeyList<TKey>, key: ResourceKey<TKey>): ResourceKeyList<TKey> {
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<TKey>(...keys: Array<ResourceKey<TKey>>): ResourceKey<TKey> {
join<TKey>(...keys: Array<ResourceKey<TKey>>): ResourceKeyList<TKey> {
const list: TKey[] = [];
for (const param of keys) {
@@ -166,6 +167,20 @@ export const ResourceKeyUtils: ResourceKeyUtils = {
return resourceKeyList(list);
},
add<TKey>(key: ResourceKey<TKey>, ...elements: TKey[]): ResourceKeyList<TKey> {
const list: TKey[] = [];
if (isResourceKeyList(key)) {
list.push(...key.list);
} else {
list.push(key);
}
list.push(...elements);
return resourceKeyList(list);
},
};
export function isResourceKeyList<T>(data: any): data is ResourceKeyList<T> {
@@ -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;
@@ -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<IDatabaseDataModel<IDataContainerOptions, IDatabaseResultSet>> {
const connectionInfo = await this.connectionInfoResource.load(connectionId);
): IDatabaseDataModel<IDataContainerOptions, IDatabaseResultSet> {
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;
}
@@ -124,6 +124,10 @@ implements IDatabaseDataModel<TOptions, TResult> {
return this.source.cancel();
}
resetData(): void {
this.source.resetData();
}
async dispose(): Promise<void> {
await this.source.dispose();
}
@@ -303,6 +303,13 @@ implements IDatabaseDataSource<TOptions, TResult> {
this.error = null;
}
resetData(): void {
if (this.activeSave || this.activeRequest) {
return;
}
this.setResults([]);
}
abstract request(prevResults: TResult[]): TResult[] | Promise<TResult[]>;
abstract save(prevResults: TResult[]): Promise<TResult[]> | TResult[];
@@ -38,5 +38,6 @@ export interface IDatabaseDataModel<TOptions = any, TResult extends IDatabaseDat
requestDataPortion: (offset: number, count: number) => Promise<void>;
requestData: () => Promise<void>;
cancel: () => Promise<void> | void;
resetData: () => void;
dispose: () => Promise<void>;
}
@@ -83,5 +83,6 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
saveData: () => Promise<void> | void;
cancel: () => Promise<void> | void;
clearError: () => void;
resetData: () => void;
dispose: () => Promise<void>;
}
@@ -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<Props>(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(
@@ -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;
},
@@ -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<IProps>(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<IProps>(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) || [];
@@ -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<ObjectPropertyTableProps>(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;
@@ -71,7 +71,12 @@ export const ObjectViewerPanel: TabHandlerPanelComponent<IObjectViewerTabState>
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<IObjectViewerTabState>
});
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;
@@ -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);
}