Compare commits

...
1 Commits
Author SHA1 Message Date
792a430f4f CB-5658 CB-5659 fix: pagination resolving (#2922)
* CB-5658 CB-5659 fix: pagination resolving

* CB-5658 fix resources pages

* CB-5667 fix: node rename behavior

* CB-5658 chore: refactor repeated code

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-09-18 21:18:50 +08:00
9 changed files with 153 additions and 74 deletions
@@ -9,12 +9,11 @@ import { runInAction } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import {
CACHED_RESOURCE_DEFAULT_PAGE_LIMIT,
CACHED_RESOURCE_DEFAULT_PAGE_OFFSET,
CachedMapAllKey,
CachedMapResource,
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
getOffsetPageKeyInfo,
isResourceAlias,
type ResourceKey,
resourceKeyList,
@@ -260,19 +259,11 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
usersList.push(user);
} else {
const pageKey =
this.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || this.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey);
const { isPageListKey, offset, limit } = getOffsetPageKeyInfo(this, originalKey);
const filterKey = this.aliases.isAlias(originalKey, UsersResourceFilterKey);
let offset = CACHED_RESOURCE_DEFAULT_PAGE_OFFSET;
let limit = CACHED_RESOURCE_DEFAULT_PAGE_LIMIT;
let userIdMask: string | undefined;
let enabledState: boolean | undefined;
if (pageKey) {
offset = pageKey.options.offset;
limit = pageKey.options.limit;
}
if (filterKey) {
userIdMask = filterKey.options.userId;
enabledState = filterKey.options.enabledState;
@@ -294,7 +285,9 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
usersList.push(...users);
pages.push([
CachedResourceOffsetPageListKey(offset, users.length).setParent(filterKey!),
isPageListKey
? CachedResourceOffsetPageListKey(offset, users.length).setParent(filterKey)
: CachedResourceOffsetPageKey(offset, users.length).setParent(filterKey),
users.map(user => user.userId),
users.length === limit,
]);
@@ -15,6 +15,7 @@ import {
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
CachedResourceOffsetPageTargetKey,
getOffsetPageKeyInfo,
isResourceAlias,
type ResourceKey,
resourceKeyList,
@@ -96,16 +97,8 @@ export class DBObjectResource extends CachedMapResource<string, DBObject> {
}
protected async loader(originalKey: ResourceKey<string>): Promise<Map<string, DBObject>> {
let limit = this.navTreeResource.childrenLimit;
let offset = CACHED_RESOURCE_DEFAULT_PAGE_OFFSET;
const parentKey = this.aliases.isAlias(originalKey, DBObjectParentKey);
const pageKey =
this.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || this.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey);
if (pageKey) {
limit = pageKey.options.limit;
offset = pageKey.options.offset;
}
const { isPageListKey, offset, limit } = getOffsetPageKeyInfo(this, originalKey, undefined, this.navTreeResource.childrenLimit);
if (parentKey) {
const nodeId = parentKey.options.parentId;
@@ -116,11 +109,11 @@ export class DBObjectResource extends CachedMapResource<string, DBObject> {
this.set(resourceKeyList(keys), dbObjects);
this.offsetPagination.setPage(
CachedResourceOffsetPageKey(offset, limit).setParent(CachedResourceOffsetPageTargetKey(originalKey)),
isPageListKey
? CachedResourceOffsetPageListKey(offset, limit).setParent(parentKey || CachedResourceOffsetPageTargetKey(nodeId))
: CachedResourceOffsetPageKey(offset, limit).setParent(parentKey || CachedResourceOffsetPageTargetKey(nodeId)),
keys,
this.navTreeResource.offsetPagination.hasNextPage(
CachedResourceOffsetPageKey(offset, limit).setParent(CachedResourceOffsetPageTargetKey(nodeId)),
),
keys.length === limit,
);
});
@@ -12,12 +12,12 @@ import { injectable } from '@cloudbeaver/core-di';
import { Executor, ExecutorInterrupter, IExecutionContext, IExecutor } from '@cloudbeaver/core-executor';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
import {
CACHED_RESOURCE_DEFAULT_PAGE_OFFSET,
CachedMapAllKey,
CachedMapResource,
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
CachedResourceOffsetPageTargetKey,
getOffsetPageKeyInfo,
type ICachedResourceMetadata,
isResourceAlias,
isResourceKeyList,
@@ -275,9 +275,10 @@ export class NavTreeResource extends CachedMapResource<string, string[], Record<
parts.splice(parts.length - 1, 1, name);
const path = parts.join('/');
this.markTreeOutdated(parentId);
this.markOutdated(parentId);
this.markLoaded(node.id);
this.onDataOutdated.execute(parentId);
return path;
} finally {
this.markLoading(node.id, false);
@@ -461,27 +462,25 @@ export class NavTreeResource extends CachedMapResource<string, string[], Record<
}
protected async loader(originalKey: ResourceKey<string>): Promise<Map<string, string[]>> {
const pageKey =
this.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || this.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey);
const { isPageListKey, pageTargetKey, offset, limit } = getOffsetPageKeyInfo(this, originalKey, undefined, this.childrenLimit);
const allKey = this.aliases.isAlias(originalKey, CachedMapAllKey);
const pageTarget = this.aliases.isAlias(originalKey, CachedResourceOffsetPageTargetKey);
if (allKey) {
throw new Error('Loading all nodes is prohibited');
}
const offset = pageKey?.options.offset ?? CACHED_RESOURCE_DEFAULT_PAGE_OFFSET;
const limit = pageKey?.options.limit ?? this.childrenLimit;
const values: NavNodeChildrenQuery[] = [];
const pages: Parameters<typeof this.offsetPagination.setPage>[] = [];
await ResourceKeyUtils.forEachAsync(originalKey, async key => {
const nodeId = pageTarget?.options?.target ?? key;
const nodeId = pageTargetKey ?? key;
const navNodeChildren = await this.loadNodeChildren(nodeId, offset, limit);
values.push(navNodeChildren);
pages.push([
CachedResourceOffsetPageKey(offset, navNodeChildren.navNodeChildren.length).setParent(CachedResourceOffsetPageTargetKey(nodeId)),
isPageListKey
? CachedResourceOffsetPageListKey(offset, navNodeChildren.navNodeChildren.length).setParent(CachedResourceOffsetPageTargetKey(nodeId))
: CachedResourceOffsetPageKey(offset, navNodeChildren.navNodeChildren.length).setParent(CachedResourceOffsetPageTargetKey(nodeId)),
navNodeChildren.navNodeChildren.map(node => node.id),
navNodeChildren.navNodeChildren.length === limit,
]);
@@ -34,7 +34,7 @@ import { isResourceAlias } from './ResourceAlias';
import { ResourceError } from './ResourceError';
import type { ResourceKey, ResourceKeyFlat } from './ResourceKey';
import { resourceKeyAlias } from './ResourceKeyAlias';
import { resourceKeyList } from './ResourceKeyList';
import { isResourceKeyList, resourceKeyList } from './ResourceKeyList';
import { resourceKeyListAlias } from './ResourceKeyListAlias';
import { ResourceOffsetPagination } from './ResourceOffsetPagination';
@@ -91,41 +91,58 @@ export abstract class CachedResource<
this.aliases.add(CachedResourceParamKey, () => defaultKey);
this.aliases.add(CachedResourceListEmptyKey, () => resourceKeyList([]));
this.aliases.add(CachedResourceOffsetPageTargetKey, key => key.options.target);
this.aliases.add(CachedResourceOffsetPageKey, key => {
const keys = [];
const pageInfo = this.offsetPagination.getPageInfo(key);
this.aliases.add(
CachedResourceOffsetPageListKey,
key => key.parent! as any,
(param, key) => {
if (!isResourceKeyList(key)) {
return key as any;
}
if (pageInfo) {
const from = key.options.offset;
const to = key.options.offset + key.options.limit;
const keys = new Set<any>();
const pageInfo = this.offsetPagination.getPageInfo(param);
for (const page of pageInfo.pages) {
if (page.isHasCommonSegment(from, to)) {
keys.push(...page.get(from, to));
if (pageInfo) {
const from = param.options.offset;
const to = param.options.offset + param.options.limit;
for (const page of pageInfo.pages) {
if (page.isHasCommonSegment(from, to)) {
for (const pageKey of page.get(from, to)) {
keys.add(pageKey);
}
}
}
}
}
return resourceKeyList(key.filter(value => keys.has(value)));
},
);
this.aliases.add(
CachedResourceOffsetPageKey,
key => key.parent! as any,
(param, key) => {
if (!isResourceKeyList(key)) {
return key as any;
}
// todo: return single element?
return resourceKeyList([...new Set(keys)]);
});
this.aliases.add(CachedResourceOffsetPageListKey, key => {
const keys = [];
const pageInfo = this.offsetPagination.getPageInfo(key);
const keys = new Set<any>();
const pageInfo = this.offsetPagination.getPageInfo(param);
if (pageInfo) {
const from = key.options.offset;
const to = key.options.offset + key.options.limit;
if (pageInfo) {
const from = param.options.offset;
const to = param.options.offset + param.options.limit;
for (const page of pageInfo.pages) {
if (page.isHasCommonSegment(from, to)) {
keys.push(...page.get(from, to));
for (const page of pageInfo.pages) {
if (page.isHasCommonSegment(from, to)) {
for (const pageKey of page.get(from, to)) {
keys.add(pageKey);
}
}
}
}
}
return resourceKeyList([...new Set(keys)]);
});
return resourceKeyList(key.filter(value => keys.has(value)));
},
);
// this.logger.spy(this.beforeLoad, 'beforeLoad');
// this.logger.spy(this.onDataOutdated, 'onDataOutdated');
@@ -329,6 +346,7 @@ export abstract class CachedResource<
}
const pageKey = this.aliases.isAlias(param, CachedResourceOffsetPageKey) || this.aliases.isAlias(param, CachedResourceOffsetPageListKey);
if (pageKey) {
const pageInfo = this.offsetPagination.getPageInfo(pageKey);
@@ -42,7 +42,7 @@ export abstract class ResourceAlias<TKey, TOptions extends ResourceAliasOptions>
return undefined;
}
setParent(parent: ResourceAlias<TKey, any>): this {
setParent(parent: ResourceAlias<TKey, any> | undefined): this {
parent = this.parent ? this.parent.setParent(parent) : parent;
const copy = new (this.constructor as any)(this.id, this.options, parent) as this;
return copy;
@@ -8,7 +8,7 @@
import { toJS } from 'mobx';
import { isResourceAlias, type ResourceAlias, ResourceAliasFactory, type ResourceAliasOptions } from './ResourceAlias';
import type { ResourceKey } from './ResourceKey';
import type { ResourceKey, ResourceKeySimple } from './ResourceKey';
import type { ResourceKeyAlias } from './ResourceKeyAlias';
import { isResourceKeyList, ResourceKeyList } from './ResourceKeyList';
import type { ResourceKeyListAlias } from './ResourceKeyListAlias';
@@ -16,14 +16,25 @@ import type { ResourceLogger } from './ResourceLogger';
export type IParamAlias<TKey> = {
id: string;
getAlias: (param: ResourceAlias<TKey, any>) => ResourceKey<TKey>;
getAlias: ResourceAliasResolver<TKey, any>;
transformKey?: ResourceAliasKeyTransformer<TKey, any>;
};
export type ResourceAliasResolver<TKey, TOptions extends ResourceAliasOptions> = (param: ResourceAlias<TKey, TOptions>) => ResourceKey<TKey>;
export type ResourceAliasKeyTransformer<TKey, TOptions extends ResourceAliasOptions> = <T extends ResourceKeySimple<TKey>>(
param: ResourceAlias<TKey, TOptions>,
key: T,
) => T;
export class ResourceAliases<TKey> {
protected paramAliases: Array<IParamAlias<TKey>>;
private captureAliasGetterExecution: boolean;
constructor(private readonly logger: ResourceLogger, private readonly validateKey: (key: TKey) => boolean) {
constructor(
private readonly logger: ResourceLogger,
private readonly validateKey: (key: TKey) => boolean,
) {
this.paramAliases = [];
this.captureAliasGetterExecution = false;
@@ -52,21 +63,23 @@ export class ResourceAliases<TKey> {
add<TOptions extends ResourceAliasOptions>(
param: ResourceAlias<TKey, TOptions> | ResourceAliasFactory<TKey, TOptions>,
getAlias: (param: ResourceAlias<TKey, TOptions>) => ResourceKey<TKey>,
getAlias: ResourceAliasResolver<TKey, TOptions>,
transformKey?: ResourceAliasKeyTransformer<TKey, TOptions>,
): void {
this.paramAliases.push({ id: param.id, getAlias });
this.paramAliases.push({ id: param.id, getAlias, transformKey });
}
replace<TOptions extends ResourceAliasOptions>(
param: ResourceAlias<TKey, TOptions> | ResourceAliasFactory<TKey, TOptions>,
getAlias: (param: ResourceAlias<TKey, TOptions>) => ResourceKey<TKey>,
getAlias: ResourceAliasResolver<TKey, TOptions>,
transformKey?: ResourceAliasKeyTransformer<TKey, TOptions>,
): void {
const indexOf = this.paramAliases.findIndex(aliasInfo => aliasInfo.id === param.id);
if (indexOf === -1) {
this.add(param, getAlias);
this.add(param, getAlias, transformKey);
} else {
this.paramAliases.splice(indexOf, 1, { id: param.id, getAlias });
this.paramAliases.splice(indexOf, 1, { id: param.id, getAlias, transformKey });
}
}
@@ -111,6 +124,7 @@ export class ResourceAliases<TKey> {
transformToKey(param: ResourceKey<TKey>): TKey | ResourceKeyList<TKey> {
let deep = 0;
const transforms: Array<{ key: ResourceKeyAlias<TKey, any> | ResourceKeyListAlias<TKey, any>; alias: IParamAlias<TKey> }> = [];
while (deep < 10) {
if (!this.validateResourceKey(param)) {
let paramString = JSON.stringify(toJS(param));
@@ -124,7 +138,15 @@ export class ResourceAliases<TKey> {
if (isResourceAlias(param)) {
for (const alias of this.paramAliases) {
if (alias.id === param.id) {
param = this.captureGetAlias(alias, param);
transforms.push({ key: param, alias });
const nextParam = this.captureGetAlias(alias, param);
if (isResourceAlias(nextParam)) {
param = nextParam.setParent(param);
} else {
param = nextParam;
}
deep++;
break;
}
@@ -141,6 +163,11 @@ export class ResourceAliases<TKey> {
if (isResourceAlias(param)) {
throw new Error(`Alias ${param.toString()} is not registered in ${this.logger.getName()}`);
}
for (const { key, alias } of transforms) {
param = alias.transformKey ? alias.transformKey(key, param) : param;
}
return param;
}
@@ -11,7 +11,7 @@ import { DefaultValueGetter, isPrimitive, MetadataMap } from '@cloudbeaver/core-
import { CachedResourceOffsetPageKey, CachedResourceOffsetPageListKey } from './CachedResourceOffsetPageKeys';
import type { ICachedResourceMetadata } from './ICachedResourceMetadata';
import { isResourceAlias } from './ResourceAlias';
import { isResourceAlias, ResourceAlias } from './ResourceAlias';
import type { ResourceAliases } from './ResourceAliases';
import type { ResourceKey, ResourceKeyFlat } from './ResourceKey';
import { isResourceKeyList, ResourceKeyList } from './ResourceKeyList';
@@ -111,6 +111,8 @@ export class ResourceMetadata<TKey, TMetadata extends ICachedResourceMetadata> {
if (this.some(param, predicate)) {
result = true;
}
} else if (predicate(this.get(param))) {
result = true;
}
}
@@ -194,11 +196,11 @@ export class ResourceMetadata<TKey, TMetadata extends ICachedResourceMetadata> {
if (isResourceAlias(key)) {
key = this.aliases.transformToAlias(key);
if (this.aliases.isAlias(key, CachedResourceOffsetPageKey) || this.aliases.isAlias(key, CachedResourceOffsetPageListKey)) {
if (isResourceAlias(key, CachedResourceOffsetPageKey) || isResourceAlias(key, CachedResourceOffsetPageListKey)) {
return this.getMetadataKeyRef(key.parent as any);
}
return key.toString() as TKey;
return (key as ResourceAlias<TKey, any>).toString() as TKey;
}
if (isPrimitive(key)) {
@@ -0,0 +1,46 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { CachedResource } from './CachedResource';
import {
CACHED_RESOURCE_DEFAULT_PAGE_LIMIT,
CACHED_RESOURCE_DEFAULT_PAGE_OFFSET,
CachedResourceOffsetPageKey,
CachedResourceOffsetPageListKey,
CachedResourceOffsetPageTargetKey,
} from './CachedResourceOffsetPageKeys';
import { CachedResourceKey } from './IResource';
interface IOffsetPageKeyInfo {
limit: number;
offset: number;
isPageListKey: boolean;
pageTargetKey?: any;
}
export function getOffsetPageKeyInfo(
resource: CachedResource<any, any, any, any>,
originalKey: CachedResourceKey<any>,
offset = CACHED_RESOURCE_DEFAULT_PAGE_OFFSET,
limit = CACHED_RESOURCE_DEFAULT_PAGE_LIMIT,
): IOffsetPageKeyInfo {
const pageListKey = resource.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey);
const pageKey = resource.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || pageListKey;
const pageTargetKey = resource.aliases.isAlias(originalKey, CachedResourceOffsetPageTargetKey);
if (pageKey) {
limit = pageKey.options.limit;
offset = pageKey.options.offset;
}
return {
limit,
offset,
isPageListKey: !!pageListKey,
pageTargetKey: pageTargetKey?.options.target,
};
}
@@ -18,6 +18,7 @@ export {
getNextPageOffset,
type ICachedResourceOffsetPageOptions,
} from './Resource/CachedResourceOffsetPageKeys';
export * from './Resource/getOffsetPageKeyInfo';
export * from './Resource/CachedTreeResource/CachedTreeResource';
export * from './Resource/CachedTreeResource/ICachedTreeMoveData';
export * from './Resource/ICachedResourceMetadata';