mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 06:40:05 +08:00
fix: CB-1312 metadata refresh
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
|
||||
+9
-1
@@ -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(
|
||||
|
||||
+20
-1
@@ -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) || [];
|
||||
|
||||
+17
-3
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user