Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CB-5839 fix: resource blocking #3199

Open
wants to merge 22 commits into
base: devel
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b57f843
CB-5839 fix: resource blocking
Wroud Jan 20, 2025
5248447
CB-5839 fix: unlock nav resources
Wroud Jan 22, 2025
2d9dae1
Merge branch 'devel' into fix/cb-5839/resource-blocking
mr-anton-t Jan 28, 2025
fbce66e
Merge remote-tracking branch 'origin/devel' into fix/cb-5839/resource…
Wroud Feb 19, 2025
36856ae
fix: connections loading
Wroud Feb 19, 2025
5431455
feat: select new folder or connection
Wroud Feb 19, 2025
b2a64ba
chore: localization
Wroud Feb 19, 2025
2f3682a
Merge remote-tracking branch 'origin/devel' into fix/cb-5839/resource…
Wroud Feb 19, 2025
25fd8e1
chore: revert improvements
Wroud Feb 19, 2025
876a7e3
Merge branch 'devel' into fix/cb-5839/resource-blocking
mr-anton-t Feb 20, 2025
1852a47
Merge branch 'devel' into fix/cb-5839/resource-blocking
mr-anton-t Feb 20, 2025
732480d
Merge branch 'devel' into fix/cb-5839/resource-blocking
mr-anton-t Feb 20, 2025
c829ac2
Merge branch 'devel' into fix/cb-5839/resource-blocking
mr-anton-t Feb 21, 2025
9340cd8
fix: load connections list for connection selector
Wroud Feb 21, 2025
cd5c275
fix: error
Wroud Feb 21, 2025
ca52482
Merge branch 'devel' into fix/cb-5839/resource-blocking
mr-anton-t Feb 21, 2025
f170d42
fix: wrong action handler
Wroud Feb 21, 2025
5b7e942
Merge branch 'devel' into fix/cb-5839/resource-blocking
dariamarutkina Feb 21, 2025
230a8e2
Merge branch 'devel' into fix/cb-5839/resource-blocking
dariamarutkina Feb 21, 2025
7e922cc
CB-5839 fix: resources tree validation
Wroud Feb 24, 2025
1dba309
Merge branch 'devel' into fix/cb-5839/resource-blocking
dariamarutkina Feb 24, 2025
b0038f6
fix: reaction concurrency
Wroud Feb 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions webapp/packages/core-authentication/src/UserInfoResource.ts
Original file line number Diff line number Diff line change
@@ -47,11 +47,11 @@
return this.data?.authRole as ELMRole | undefined;
}

get teams() {

Check warning on line 50 in webapp/packages/core-authentication/src/UserInfoResource.ts

GitHub Actions / Frontend / Lint

Missing return type on function
return this.data?.teams || [];
}

get parametersAvailable() {

Check warning on line 54 in webapp/packages/core-authentication/src/UserInfoResource.ts

GitHub Actions / Frontend / Lint

Missing return type on function
return this.data !== null;
}

@@ -183,9 +183,12 @@
configuration,
});

this.resetIncludes();
this.setData(await this.loader());
this.sessionResource.markOutdated();
const data = await this.loader();
runInAction(() => {
this.resetIncludes();
this.setData(data);
this.sessionResource.markOutdated();
});

return result;
}
@@ -204,7 +207,7 @@
return this.data;
}

async setConfigurationParameter(key: string, value: any): Promise<UserInfo | null> {

Check warning on line 210 in webapp/packages/core-authentication/src/UserInfoResource.ts

GitHub Actions / Frontend / Lint

Argument 'value' should be typed with a non-any type
await this.load();

if (!this.parametersAvailable) {
11 changes: 10 additions & 1 deletion webapp/packages/core-blocks/src/Loader/useAutoLoad.ts
Original file line number Diff line number Diff line change
@@ -10,14 +10,16 @@
import { type ILoadableState, isContainsException } from '@cloudbeaver/core-utils';

import { getComputed } from '../getComputed.js';
import { useObjectRef } from '../useObjectRef.js';

export function useAutoLoad(

Check warning on line 15 in webapp/packages/core-blocks/src/Loader/useAutoLoad.ts

GitHub Actions / Frontend / Lint

Missing return type on function
component: { name: string },
state: ILoadableState | ReadonlyArray<ILoadableState>,
enabled = true,
lazy = false,
throwExceptions = false,
) {
const unmountedRef = useObjectRef({ unmounted: false });
const [loadFunctionName] = useState(`${component.name}.useAutoLoad(...)` as const);
if (!Array.isArray(state)) {
state = [state] as ReadonlyArray<ILoadableState>;
@@ -32,7 +34,7 @@

const obj = {
[loadFunctionName]: async () => {
if (!enabled) {
if (!enabled || unmountedRef.unmounted) {
return;
}

@@ -70,4 +72,11 @@
useEffect(() => {
obj[loadFunctionName]!();
});

useEffect(
() => () => {
unmountedRef.unmounted = true;
},
[],

Check warning on line 80 in webapp/packages/core-blocks/src/Loader/useAutoLoad.ts

GitHub Actions / Frontend / Lint

React Hook useEffect has a missing dependency: 'unmountedRef'. Either include it or remove the dependency array
);
}
10 changes: 9 additions & 1 deletion webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts
Original file line number Diff line number Diff line change
@@ -132,6 +132,7 @@
): IMapResourceResult<TResource, TIncludes> | IMapResourceListResult<TResource, TIncludes> | IDataResourceResult<TResource, TIncludes> {
// eslint-disable-next-line react-hooks/rules-of-hooks
const resource = ctor instanceof Resource ? ctor : useService(ctor);
const unmountedRef = useObjectRef({ unmounted: false });
const errorContext = useContext(ErrorContext);
let key: ResourceKey<TKeyArg> | null = keyObj as ResourceKey<TKeyArg>;
let includes: TIncludes = [] as unknown as TIncludes;
@@ -234,7 +235,7 @@
}
},
async load(refresh?: boolean): Promise<void> {
if (propertiesRef.key === null) {
if (propertiesRef.key === null || unmountedRef.unmounted) {
return;
}

@@ -462,7 +463,7 @@
disposeErrorUpdate();
refObj.use(null);
};
}, []);

Check warning on line 466 in webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts

GitHub Actions / Frontend / Lint

React Hook useEffect has missing dependencies: 'actions', 'propertiesRef.errorContext', 'refObj', 'resource', and 'result'. Either include them or remove the dependency array

const canLoad = useDeferredValue(getComputed(() => result.canLoad));
const loadKey = useDeferredValue(getComputed(() => propertiesRef.key));
@@ -472,10 +473,17 @@
if (canLoad) {
result.load();
}
}, [canLoad, loadKey]);

Check warning on line 476 in webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts

GitHub Actions / Frontend / Lint

React Hook useEffect has missing dependencies: 'refObj' and 'result'. Either include them or remove the dependency array

useEffect(
() => () => {
unmountedRef.unmounted = true;
},
[],

Check warning on line 482 in webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts

GitHub Actions / Frontend / Lint

React Hook useEffect has a missing dependency: 'unmountedRef'. Either include it or remove the dependency array
);

if (actions?.forceSuspense) {
result.data;

Check warning on line 486 in webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts

GitHub Actions / Frontend / Lint

Expected an assignment or function call and instead saw an expression
}

return result;
12 changes: 11 additions & 1 deletion webapp/packages/core-connections/src/ConnectionInfoResource.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
import { AppAuthService, UserInfoResource } from '@cloudbeaver/core-authentication';
import { injectable } from '@cloudbeaver/core-di';
import { Executor, ExecutorInterrupter, type ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import { NodeManagerUtils } from '@cloudbeaver/core-navigation-tree';
import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
import {
CachedMapAllKey,
@@ -167,7 +168,7 @@

connectionStateEventHandler.onEvent<IWsDataSourceDisconnectEvent>(
ServerEventId.CbDatasourceDisconnected,
async data => {

Check failure on line 171 in webapp/packages/core-connections/src/ConnectionInfoResource.ts

GitHub Actions / Frontend / Lint

Async arrow function has no 'await' expression
const key: IConnectionInfoParams = {
projectId: data.projectId,
connectionId: data.connectionId,
@@ -183,7 +184,7 @@

connectionStateEventHandler.onEvent<IWsDataSourceConnectEvent>(
ServerEventId.CbDatasourceConnected,
async data => {

Check failure on line 187 in webapp/packages/core-connections/src/ConnectionInfoResource.ts

GitHub Actions / Frontend / Lint

Async arrow function has no 'await' expression
const key: IConnectionInfoParams = {
projectId: data.projectId,
connectionId: data.connectionId,
@@ -291,9 +292,18 @@
return this.get(key).every(connection => connection?.connected ?? false);
}

// TODO: we need here node path ie ['', 'project://', 'database://...', '...']
getConnectionIdForNodeId(projectId: string, nodeId: string): IConnectionInfoParams | undefined {
if (!NodeManagerUtils.isDatabaseObject(nodeId)) {
return;
}

return createConnectionParam(projectId, NodeManagerUtils.getConnectionId(nodeId));
}

// TODO: we need here node path ie ['', 'project://', 'database://...', '...']
getConnectionForNode(nodeId: string): Connection | undefined {
if (!nodeId.startsWith('database://')) {
if (!NodeManagerUtils.isDatabaseObject(nodeId)) {
return;
}

22 changes: 10 additions & 12 deletions webapp/packages/core-connections/src/ConnectionsManagerService.ts
Original file line number Diff line number Diff line change
@@ -32,7 +32,9 @@
@injectable()
export class ConnectionsManagerService {
get projectConnections(): Connection[] {
return this.connectionInfo.values.filter(connection => this.projectsService.activeProjects.some(project => project.id === connection.projectId));
return this.connectionInfoResource.values.filter(connection =>
this.projectsService.activeProjects.some(project => project.id === connection.projectId),
);
}
get createConnectionProjects(): ProjectInfo[] {
return this.projectsService.activeProjects.filter(project => project.canEditDataSources).sort(projectInfoSortByName);
@@ -44,7 +46,7 @@
private disconnecting: boolean;

constructor(
readonly connectionInfo: ConnectionInfoResource,
readonly connectionInfoResource: ConnectionInfoResource,
readonly containerContainers: ContainerResource,
private readonly notificationService: NotificationService,
private readonly commonDialogService: CommonDialogService,
@@ -56,9 +58,9 @@
this.onDisconnect = new Executor();
this.onDelete = new Executor();

this.connectionExecutor.addHandler(data => connectionInfo.load(data.key));
this.connectionExecutor.addHandler(data => connectionInfoResource.load(data.key));
this.onDelete.before(this.onDisconnect);
this.connectionInfo.onConnectionClose.next(this.onDisconnect, key => ({
this.connectionInfoResource.onConnectionClose.next(this.onDisconnect, key => ({
connections: [key],
state: 'after' as const,
}));
@@ -80,10 +82,6 @@
return connection.connection;
}

addOpenedConnection(connection: Connection): void {
this.connectionInfo.add(connection);
}

getObjectContainerById(connectionKey: IConnectionInfoParams, objectCatalogId?: string, objectSchemaId?: string): ObjectContainer | undefined {
if (objectCatalogId) {
const objectContainers = this.containerContainers.getCatalogData(connectionKey, objectCatalogId);
@@ -107,7 +105,7 @@
}

async deleteConnection(key: IConnectionInfoParams): Promise<void> {
const connection = await this.connectionInfo.load(key);
const connection = await this.connectionInfoResource.load(key);

if (!connection.canDelete) {
return;
@@ -131,7 +129,7 @@
return;
}

await this.connectionInfo.deleteConnection(key);
await this.connectionInfoResource.deleteConnection(key);

this.onDelete.execute({
connections: [key],
@@ -146,7 +144,7 @@
return !!this.projectConnections.length;
}

connectionContext() {

Check warning on line 147 in webapp/packages/core-connections/src/ConnectionsManagerService.ts

GitHub Actions / Frontend / Lint

Missing return type on function
return {
connection: null as Connection | null,
};
@@ -157,7 +155,7 @@
return;
}

await this.connectionInfo.close(createConnectionParam(connection));
await this.connectionInfoResource.close(createConnectionParam(connection));
}

async closeAllConnections(): Promise<void> {
@@ -192,7 +190,7 @@
}

async closeConnectionAsync(key: IConnectionInfoParams): Promise<void> {
const connection = this.connectionInfo.get(key);
const connection = this.connectionInfoResource.get(key);
if (!connection || !connection.connected) {
return;
}
Original file line number Diff line number Diff line change
@@ -7,6 +7,6 @@
*/
import { createDataContext } from '@cloudbeaver/core-data-context';

import type { Connection } from '../ConnectionInfoResource.js';
import type { IConnectionInfoParams } from '../CONNECTION_INFO_PARAM_SCHEMA.js';

export const DATA_CONTEXT_CONNECTION = createDataContext<Connection>('connection');
export const DATA_CONTEXT_CONNECTION = createDataContext<IConnectionInfoParams>('connection');
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import { NavNodeInfoResource, NavNodeManagerService } from '@cloudbeaver/core-na
import { projectProvider } from '@cloudbeaver/core-projects';

import type { IConnectionInfoParams } from '../CONNECTION_INFO_PARAM_SCHEMA.js';
import { ConnectionInfoResource, createConnectionParam } from '../ConnectionInfoResource.js';
import { ConnectionInfoResource } from '../ConnectionInfoResource.js';
import { connectionProvider } from '../extensions/IConnectionProvider.js';
import { objectCatalogProvider } from '../extensions/IObjectCatalogProvider.js';
import { objectSchemaProvider } from '../extensions/IObjectSchemaProvider.js';
@@ -40,13 +40,17 @@ export class NavNodeExtensionsService {
}

getConnection(navNodeId: string): IConnectionInfoParams | undefined {
const connection = this.connectionInfoResource.getConnectionForNode(navNodeId);
const node = this.navNodeInfoResource.get(navNodeId);
if (!node?.projectId) {
return;
}
const connectionKey = this.connectionInfoResource.getConnectionIdForNodeId(node.projectId, navNodeId);

if (!connection) {
if (!connectionKey) {
return;
}

return createConnectionParam(connection);
return connectionKey;
}

getDBObjectCatalog(navNodeId: string) {
2 changes: 1 addition & 1 deletion webapp/packages/core-connections/src/index.ts
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ export * from './extensions/IObjectSchemaProvider.js';
export * from './extensions/IObjectSchemaSetter.js';
export * from './extensions/IObjectLoaderProvider.js';
export * from './extensions/IExecutionContextProvider.js';
export * from './NavTree/ConnectionNavNodeService.js';
export * from './NavTree/NavNodeExtensionsService.js';
export * from './NavTree/getConnectionFolderIdFromNodeId.js';
export * from './NavTree/getConnectionFolderId.js';
@@ -31,6 +30,7 @@ export * from './NavTree/isConnectionNode.js';

export * from './extensions/IConnectionProvider.js';
export * from './extensions/IConnectionSetter.js';
export * from './ConnectionFolderEventHandler.js';
export * from './ConnectionsManagerService.js';
export * from './ConnectionFolderResource.js';
export * from './ConnectionDialectResource.js';
1 change: 0 additions & 1 deletion webapp/packages/core-connections/src/manifest.ts
Original file line number Diff line number Diff line change
@@ -27,7 +27,6 @@ export const manifest: PluginManifest = {
() => import('./DBDriverResource.js').then(m => m.DBDriverResource),
() => import('./NetworkHandlerResource.js').then(m => m.NetworkHandlerResource),
() => import('./ConnectionDialectResource.js').then(m => m.ConnectionDialectResource),
() => import('./NavTree/ConnectionNavNodeService.js').then(m => m.ConnectionNavNodeService),
() => import('./NavTree/NavNodeExtensionsService.js').then(m => m.NavNodeExtensionsService),
() => import('./ConnectionInfoEventHandler.js').then(m => m.ConnectionInfoEventHandler),
() => import('./ConnectionFolderEventHandler.js').then(m => m.ConnectionFolderEventHandler),
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/en.ts
Original file line number Diff line number Diff line change
@@ -84,6 +84,7 @@ export default [
['ui_create_processing', 'Creating...'],
['ui_folder', 'Folder'],
['ui_folder_new', 'New folder'],
['ui_folder_new_default_name', 'New folder'],
['ui_rename_processing', 'Renaming...'],
['ui_interval', 'Interval'],
['ui_name', 'Name'],
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/fr.ts
Original file line number Diff line number Diff line change
@@ -78,6 +78,7 @@ export default [
['ui_create_processing', 'Création...'],
['ui_folder', 'Dossier'],
['ui_folder_new', 'Nouveau dossier'],
['ui_folder_new_default_name', 'Nouveau dossier'],
['ui_rename_processing', 'Renommage...'],
['ui_interval', 'Intervalle'],
['ui_name', 'Nom'],
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/it.ts
Original file line number Diff line number Diff line change
@@ -81,6 +81,7 @@ export default [
['ui_create_processing', 'Creating...'],
['ui_folder', 'Folder'],
['ui_folder_new', 'New folder'],
['ui_folder_new_default_name', 'New folder'],
['ui_rename_processing', 'Renaming...'],
['ui_interval', 'Interval'],
['ui_name', 'Nome'],
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/ru.ts
Original file line number Diff line number Diff line change
@@ -80,6 +80,7 @@ export default [
['ui_create_processing', 'Создание...'],
['ui_folder', 'Папка'],
['ui_folder_new', 'Новая папка'],
['ui_folder_new_default_name', 'Новая папка'],
['ui_rename_processing', 'Переименование...'],
['ui_name', 'Название'],
['ui_value', 'Значение'],
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/zh.ts
Original file line number Diff line number Diff line change
@@ -80,6 +80,7 @@ export default [
['ui_create_processing', '创建中...'],
['ui_folder', '文件夹'],
['ui_folder_new', '新建文件夹'],
['ui_folder_new_default_name', '新建文件夹'],
['ui_rename', '重命名'],
['ui_rename_processing', '重命名中...'],
['ui_name', '名称'],
Original file line number Diff line number Diff line change
@@ -60,7 +60,6 @@ export class DBObjectResource extends CachedMapResource<string, DBObject> {
});

this.beforeLoad.addHandler(async (originalKey, context) => {
await this.navTreeResource.waitLoad();
const parentKey = this.aliases.isAlias(originalKey, DBObjectParentKey);
const pageKey =
this.aliases.isAlias(originalKey, CachedResourceOffsetPageKey) || this.aliases.isAlias(originalKey, CachedResourceOffsetPageListKey);
@@ -80,6 +79,15 @@ export class DBObjectResource extends CachedMapResource<string, DBObject> {
}

const key = ResourceKeyUtils.toList(this.aliases.transformToKey(originalKey));

for (const nodeId of key) {
const preloaded = await this.navTreeResource.preloadParents(nodeId);

if (!preloaded) {
throw new DetailsError('Not found: ' + nodeId);
}
}

const parents = [
...new Set(
key.map(nodeId => this.navNodeInfoResource.get(nodeId)?.parentId).filter<string>((nodeId): nodeId is string => nodeId !== undefined),
Original file line number Diff line number Diff line change
@@ -149,7 +149,7 @@
return true;
}

async refreshTree(navNodeId: string, silent = false): Promise<void> {

Check failure on line 152 in webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts

GitHub Actions / Frontend / Lint

Async method 'refreshTree' has no 'await' expression
this.performUpdate(navNodeId, [], async () => {
await this.graphQLService.sdk.navRefreshNode({
nodePath: navNodeId,
@@ -162,7 +162,7 @@
});
}

async refreshNode(navNodeId: string, silent = false): Promise<void> {

Check failure on line 165 in webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts

GitHub Actions / Frontend / Lint

Async method 'refreshNode' has no 'await' expression
this.performUpdate(navNodeId, [], async () => {
await this.graphQLService.sdk.navRefreshNode({
nodePath: navNodeId,
@@ -455,17 +455,22 @@
this.navNodeInfoResource.delete(items.exclude(key));
}

async preloadParents(nodeId: string): Promise<boolean> {
if (!this.navNodeInfoResource.has(nodeId) && nodeId !== ROOT_NODE_PATH) {
await this.navNodeInfoResource.loadNodeParents(nodeId);
}
const parents = this.navNodeInfoResource.getParents(nodeId);
return await this.preloadNodeParents(parents, nodeId);
}

protected override async preLoadData(key: ResourceKey<string>, contexts: IExecutionContext<ResourceKey<string>>): Promise<void> {
await ResourceKeyUtils.forEachAsync(key, async nodeId => {
if (isResourceAlias(nodeId)) {
return;
}

if (!this.navNodeInfoResource.has(nodeId) && nodeId !== ROOT_NODE_PATH) {
await this.navNodeInfoResource.loadNodeParents(nodeId);
}
const preloaded = await this.preloadParents(nodeId);
const parents = this.navNodeInfoResource.getParents(nodeId);
const preloaded = await this.preloadNodeParents(parents, nodeId);

if (!preloaded) {
const cause = new DetailsError(`Entity not found:\n"${nodeId}"\nPath:\n${parents.map(parent => `"${parent}"`).join('\n')}`);
Original file line number Diff line number Diff line change
@@ -6,13 +6,22 @@
* you may not use this file except in compliance with the License.
*/

const CONNECTION_NODE_ID_PREFIX = 'database://';

export const NodeManagerUtils = {
getConnectionId(nodeId: string) {
const indexOfConnectionPart = nodeId.indexOf('/', CONNECTION_NODE_ID_PREFIX.length);
const connectionId = nodeId.slice(CONNECTION_NODE_ID_PREFIX.length, indexOfConnectionPart > -1 ? indexOfConnectionPart : nodeId.length);

return connectionId;
},

connectionIdToConnectionNodeId(connectionId: string): string {
return `database://${connectionId}`;
return `${CONNECTION_NODE_ID_PREFIX}${connectionId}`;
},

isDatabaseObject(objectId: string): boolean {
return objectId.startsWith('database://');
isDatabaseObject(nodeId: string): boolean {
return nodeId.startsWith(CONNECTION_NODE_ID_PREFIX);
},

concatSchemaAndCatalog(catalogId?: string, schemaId?: string): string {
Loading