CB-2344 refactor: dependency injection for execution context

This commit is contained in:
Wroud
2022-09-09 14:23:04 +03:00
parent e3a611a89c
commit 21afbe21cd
14 changed files with 176 additions and 24 deletions
@@ -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) {
+8 -3
View File
@@ -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');
};
+1
View File
@@ -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);
+15 -2
View File
@@ -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();
});