mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-2344 refactor: dependency injection for execution context
This commit is contained in:
@@ -13,12 +13,7 @@ import { IconOrImage, Link } from '@cloudbeaver/core-blocks';
|
||||
import { ENotificationType } from '@cloudbeaver/core-events';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
|
||||
interface Props {
|
||||
message: string | undefined | null;
|
||||
status: ENotificationType | undefined;
|
||||
onShowDetails?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
import { useErrorDetails } from './useErrorDetails';
|
||||
|
||||
const styles = css`
|
||||
status-message {
|
||||
@@ -43,17 +38,36 @@ const styles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const StatusMessage = observer<Props>(function StatusMessage({ status, message, onShowDetails, className }) {
|
||||
interface Props {
|
||||
message?: string | null;
|
||||
status?: ENotificationType;
|
||||
exception?: Error | null;
|
||||
onShowDetails?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const StatusMessage = observer<Props>(function StatusMessage({
|
||||
status,
|
||||
message,
|
||||
exception = null,
|
||||
onShowDetails,
|
||||
className,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
message = message ? translate(message) : message;
|
||||
const errorDetails = useErrorDetails(exception);
|
||||
message = (message ? translate(message) : message) ?? errorDetails.details?.name;
|
||||
let icon = '/icons/info_icon.svg';
|
||||
|
||||
if (status === ENotificationType.Error) {
|
||||
if (status === ENotificationType.Error || exception !== null) {
|
||||
icon = '/icons/error_icon.svg';
|
||||
} else if (status === ENotificationType.Success) {
|
||||
icon = '/icons/success_icon.svg';
|
||||
}
|
||||
|
||||
if (errorDetails.details?.hasDetails && !onShowDetails) {
|
||||
onShowDetails = errorDetails.open;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<status-message className={className}>
|
||||
{message && (
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { observable, computed, action } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { useObjectRef } from '../useObjectRef';
|
||||
@@ -34,6 +34,23 @@ export const Table = observer<React.PropsWithChildren<Props>>(function Table({
|
||||
const [selected] = useState<Map<any, boolean>>(() => selectedItems || observable(new Map()));
|
||||
const [expanded] = useState<Map<any, boolean>>(() => expandedItems || observable(new Map()));
|
||||
|
||||
useEffect(action(() => {
|
||||
if (!keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
const removeSelected = Array.from(selected.keys()).filter(key => !keys.includes(key));
|
||||
const removeExpanded = Array.from(expanded.keys()).filter(key => !keys.includes(key));
|
||||
|
||||
for (const id of removeSelected) {
|
||||
selected.delete(id);
|
||||
}
|
||||
|
||||
for (const id of removeExpanded) {
|
||||
expanded.delete(id);
|
||||
}
|
||||
}), [keys]);
|
||||
|
||||
const state: ITableState = useObservableRef(() => ({
|
||||
get selectableItems() {
|
||||
if (!this.keys) {
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { ISyncContextLoader } from '@cloudbeaver/core-executor';
|
||||
import type { IExecutorHandlersCollection, ISyncContextLoader } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { App } from './App';
|
||||
import { App } from './App';
|
||||
import { dependencyInjectorContext } from './dependencyInjectorContext';
|
||||
import type { IServiceConstructor, IServiceInjector } from './IApp';
|
||||
import { injectable } from './injectable';
|
||||
|
||||
@@ -22,7 +23,11 @@ export class DIService {
|
||||
private readonly app: App
|
||||
) { }
|
||||
|
||||
dependencyInjectorContext: ISyncContextLoader<<T>(ctor: IServiceConstructor<T>) => T> = (
|
||||
addDIContext(context: IExecutorHandlersCollection<any>): void {
|
||||
context.addContextCreator(dependencyInjectorContext, this.dependencyInjectorContext);
|
||||
}
|
||||
|
||||
private readonly dependencyInjectorContext: ISyncContextLoader<<T>(ctor: IServiceConstructor<T>) => T> = (
|
||||
() => this.serviceInjector.getServiceByClass.bind(this)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 type { ISyncContextLoader } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { IServiceConstructor } from './IApp';
|
||||
|
||||
export const dependencyInjectorContext: ISyncContextLoader<<T>(ctor: IServiceConstructor<T>) => T> = function dependencyInjectorContext() {
|
||||
throw new Error('Implementation not provided \n Use addContextCreator to add addDIContext implementation to context');
|
||||
};
|
||||
@@ -3,6 +3,7 @@ export * from './App';
|
||||
export * from './AppContext';
|
||||
export * from './Bootstrap';
|
||||
export * from './Dependency';
|
||||
export * from './dependencyInjectorContext';
|
||||
export * from './DIService';
|
||||
export * from './injectable';
|
||||
export * from './PluginManifest';
|
||||
|
||||
@@ -10,23 +10,39 @@ import type { IAsyncContextLoader, IContextLoader, IExecutionContext, ISyncConte
|
||||
|
||||
export class ExecutionContext<TData> implements IExecutionContext<TData> {
|
||||
readonly contexts: Map<IContextLoader<any, TData>, any>;
|
||||
readonly contextCreators: Map<IContextLoader<any, TData>, IContextLoader<any, TData>>;
|
||||
|
||||
constructor(private data: TData, context?: IExecutionContext<any>) {
|
||||
this.contexts = context?.contexts || new Map<IContextLoader<any, TData>, any>();
|
||||
constructor(private readonly data: TData, context?: IExecutionContext<any>) {
|
||||
this.contexts = context?.contexts || new Map();
|
||||
this.contextCreators = context?.contextCreators ?? new Map();
|
||||
}
|
||||
|
||||
hasContext(loader: IContextLoader<any>): boolean {
|
||||
return this.contexts.has(loader);
|
||||
addContextCreators(
|
||||
creators: [IContextLoader<any, TData>, IContextLoader<any, TData>][]
|
||||
): void {
|
||||
for (const [key, value] of creators) {
|
||||
this.contextCreators.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
getContext<T>(token: ISyncContextLoader<T, TData>): T
|
||||
getContext<T>(token: IAsyncContextLoader<T, TData>): Promise<T>
|
||||
hasContext(loader: IContextLoader): boolean {
|
||||
return this.contextCreators.has(loader) || this.contexts.has(loader);
|
||||
}
|
||||
|
||||
getContext<T>(token: ISyncContextLoader<T, TData>): T;
|
||||
getContext<T>(token: IAsyncContextLoader<T, TData>): Promise<T>;
|
||||
getContext<T>(token: IContextLoader<T, TData>): Promise<T> | T {
|
||||
if (this.contexts.has(token)) {
|
||||
return this.contexts.get(token);
|
||||
}
|
||||
|
||||
const value = token(this, this.data);
|
||||
let value: T | Promise<T>;
|
||||
|
||||
if (this.contextCreators.has(token)) {
|
||||
value = this.contextCreators.get(token)!(this, this.data);
|
||||
} else {
|
||||
value = token(this, this.data);
|
||||
}
|
||||
|
||||
if (value instanceof Promise) {
|
||||
return this.getAsyncContext(token, value);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { flat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ExecutionContext } from './ExecutionContext';
|
||||
import { executionExceptionContext } from './executionExceptionContext';
|
||||
import { ExecutorHandlersCollection } from './ExecutorHandlersCollection';
|
||||
import { ExecutorInterrupter, IExecutorInterrupter } from './ExecutorInterrupter';
|
||||
import type { IExecutionContext, IExecutionContextProvider } from './IExecutionContext';
|
||||
@@ -68,9 +69,16 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
|
||||
context: IExecutionContext<T>,
|
||||
scoped: Array<IExecutorHandlersCollection<T>>
|
||||
): Promise<IExecutionContextProvider<T>> {
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
scoped = [...collection.collections, ...scoped];
|
||||
|
||||
context.addContextCreators(collection.contextCreators as any);
|
||||
|
||||
for (const scope of scoped) {
|
||||
context.addContextCreators(scope.contextCreators as any);
|
||||
}
|
||||
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
|
||||
await this.executeChain(collection, data, context, 'before');
|
||||
|
||||
for (const scope of scoped) {
|
||||
@@ -83,7 +91,11 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
|
||||
for (const scope of scoped) {
|
||||
await this.executeHandlers(data, context, scope.handlers, interrupter);
|
||||
}
|
||||
} finally {
|
||||
} catch (exception: any) {
|
||||
const exceptionContext = context.getContext(executionExceptionContext);
|
||||
exceptionContext.setException(exception);
|
||||
throw exception;
|
||||
} finally {
|
||||
await this.executeHandlers(data, context, collection.postHandlers);
|
||||
|
||||
for (const scope of scoped) {
|
||||
@@ -158,6 +170,7 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
|
||||
|
||||
this.scheduler.schedule(data, async () => {
|
||||
const context = new ExecutionContext(data);
|
||||
context.addContextCreators(this.contextCreators as any);
|
||||
|
||||
try {
|
||||
await handler(data, context);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import type { IContextLoader, ISyncContextLoader, IAsyncContextLoader } from './IExecutionContext';
|
||||
import type { IExecutorHandler } from './IExecutorHandler';
|
||||
import type { ExecutorDataFilter, ExecutorDataMap, IChainLink, IExecutorHandlersCollection } from './IExecutorHandlersCollection';
|
||||
|
||||
@@ -16,12 +17,14 @@ implements IExecutorHandlersCollection<T, TResult> {
|
||||
handlers: Array<IExecutorHandler<T, TResult>> = [];
|
||||
postHandlers: Array<IExecutorHandler<T, TResult>> = [];
|
||||
chain: Array<IChainLink<T, TResult>> = [];
|
||||
readonly contextCreators: Map<IContextLoader<any, T>, IContextLoader<any, T>>;
|
||||
readonly collections: Array<IExecutorHandlersCollection<T, TResult>>;
|
||||
protected initialDataGetter: (() => T) | null;
|
||||
private readonly links: Map<IExecutorHandlersCollection<any, TResult>, IExecutorHandlersCollection<T, TResult>>;
|
||||
|
||||
constructor() {
|
||||
this.links = new Map();
|
||||
this.contextCreators = new Map();
|
||||
this.collections = [];
|
||||
this.initialDataGetter = null;
|
||||
|
||||
@@ -34,6 +37,22 @@ implements IExecutorHandlersCollection<T, TResult> {
|
||||
});
|
||||
}
|
||||
|
||||
addContextCreator<TContext>(
|
||||
context: ISyncContextLoader<TContext, T>,
|
||||
creator: ISyncContextLoader<TContext, T>
|
||||
): this;
|
||||
addContextCreator<TContext>(
|
||||
context: IAsyncContextLoader<TContext, T>,
|
||||
creator: IAsyncContextLoader<TContext, T>
|
||||
): this;
|
||||
addContextCreator<TContext>(
|
||||
context: IContextLoader<TContext, T>,
|
||||
creator: IContextLoader<TContext, T>
|
||||
): this {
|
||||
this.contextCreators.set(context, creator);
|
||||
return this;
|
||||
}
|
||||
|
||||
setInitialDataGetter(getter: (() => T) | null): this {
|
||||
this.initialDataGetter = getter;
|
||||
return this;
|
||||
|
||||
@@ -17,7 +17,13 @@ export interface IExecutionContextProvider<TData> {
|
||||
}
|
||||
|
||||
export interface IExecutionContext<TData> extends IExecutionContextProvider<TData> {
|
||||
readonly parent?: IExecutionContext<any>;
|
||||
readonly contexts: Map<IContextLoader<any, TData>, any>;
|
||||
readonly contextCreators: Map<IContextLoader<any, TData>, IContextLoader<any, TData>>;
|
||||
|
||||
addContextCreators(
|
||||
creators: [IContextLoader<any, TData>, IContextLoader<any, TData>][]
|
||||
): void;
|
||||
}
|
||||
|
||||
export type IAsyncContextLoader<T = any, TData = any> = (
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IExecutionContextProvider } from './IExecutionContext';
|
||||
import type { IAsyncContextLoader, IContextLoader, IExecutionContextProvider, ISyncContextLoader } from './IExecutionContext';
|
||||
import type { IExecutorHandler } from './IExecutorHandler';
|
||||
|
||||
export type ExecutorDataFilter<T> = (data: T, contexts: IExecutionContextProvider<T>) => boolean;
|
||||
@@ -25,9 +25,19 @@ export interface IExecutorHandlersCollection<T = unknown, TResult = any | Promis
|
||||
readonly postHandlers: Array<IExecutorHandler<T, TResult>>;
|
||||
readonly chain: Array<IChainLink<T, TResult>>;
|
||||
readonly collections: Array<IExecutorHandlersCollection<T, TResult>>;
|
||||
readonly contextCreators: Map<IContextLoader<any, T>, IContextLoader<any, T>>;
|
||||
|
||||
setInitialDataGetter(getter: () => T): this;
|
||||
|
||||
addContextCreator<TContext>(
|
||||
context: ISyncContextLoader<TContext, T>,
|
||||
creator: ISyncContextLoader<TContext, T>
|
||||
): this;
|
||||
addContextCreator<TContext>(
|
||||
context: IAsyncContextLoader<TContext, T>,
|
||||
creator: IAsyncContextLoader<TContext, T>
|
||||
): this;
|
||||
|
||||
before: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
|
||||
next: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
|
||||
addCollection: (collection: IExecutorHandlersCollection<T, TResult>) => this;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { flat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ExecutionContext } from './ExecutionContext';
|
||||
import { executionExceptionContext } from './executionExceptionContext';
|
||||
import { ExecutorHandlersCollection } from './ExecutorHandlersCollection';
|
||||
import { ExecutorInterrupter, IExecutorInterrupter } from './ExecutorInterrupter';
|
||||
import type { IExecutionContext, IExecutionContextProvider } from './IExecutionContext';
|
||||
@@ -55,9 +56,16 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
|
||||
context: IExecutionContext<T>,
|
||||
scoped: Array<IExecutorHandlersCollection<T>>
|
||||
): IExecutionContextProvider<T> {
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
scoped = [...collection.collections, ...scoped];
|
||||
|
||||
context.addContextCreators(collection.contextCreators as any);
|
||||
|
||||
for (const scope of scoped) {
|
||||
context.addContextCreators(scope.contextCreators as any);
|
||||
}
|
||||
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
|
||||
this.executeChain(collection, data, context, 'before');
|
||||
|
||||
for (const scope of scoped) {
|
||||
@@ -70,6 +78,10 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
|
||||
for (const scope of scoped) {
|
||||
this.executeHandlers(data, context, scope.handlers, interrupter);
|
||||
}
|
||||
} catch (exception: any) {
|
||||
const exceptionContext = context.getContext(executionExceptionContext);
|
||||
exceptionContext.setException(exception);
|
||||
throw exception;
|
||||
} finally {
|
||||
this.executeHandlers(data, context, collection.postHandlers);
|
||||
|
||||
@@ -144,6 +156,8 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
|
||||
const data = this.initialDataGetter();
|
||||
const context = new ExecutionContext(data);
|
||||
|
||||
context.addContextCreators(this.contextCreators as any);
|
||||
|
||||
try {
|
||||
handler(data, context);
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export function executionExceptionContext() {
|
||||
return {
|
||||
exception: null as null | Error,
|
||||
setException(exception: Error) {
|
||||
this.exception = exception;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export * from './TaskScheduler/ITask';
|
||||
export * from './TaskScheduler/Task';
|
||||
export * from './TaskScheduler/TaskScheduler';
|
||||
export * from './ExecutionContext';
|
||||
export * from './executionExceptionContext';
|
||||
export * from './Executor';
|
||||
export * from './ExecutorHandlersCollection';
|
||||
export * from './ExecutorInterrupter';
|
||||
|
||||
@@ -10,6 +10,7 @@ import { runInAction } from 'mobx';
|
||||
|
||||
import { UserInfoResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SharedProjectsResource } from '@cloudbeaver/core-resource-manager';
|
||||
import { GraphQLService, ProjectInfo as SchemaProjectInfo, CachedMapResource, CachedMapAllKey, ResourceKey, ResourceKeyUtils, resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type ProjectInfo = SchemaProjectInfo;
|
||||
@@ -18,11 +19,15 @@ export type ProjectInfo = SchemaProjectInfo;
|
||||
export class ProjectInfoResource extends CachedMapResource<string, ProjectInfo> {
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly sharedProjectsResource: SharedProjectsResource,
|
||||
private readonly userInfoResource: UserInfoResource,
|
||||
) {
|
||||
super([]);
|
||||
|
||||
this.sync(this.userInfoResource);
|
||||
this.sharedProjectsResource.onDataOutdated.addHandler(this.markOutdated.bind(this));
|
||||
this.sharedProjectsResource.onItemAdd.addHandler(() => this.markOutdated());
|
||||
this.sharedProjectsResource.onItemDelete.addHandler(() => this.markOutdated());
|
||||
this.userInfoResource.onUserChange.addPostHandler(() => {
|
||||
this.clear();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user