From 10b04bdb0fd05081716b3630dfdd1bf4b8714d06 Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Mon, 2 Oct 2023 19:15:05 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20-=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B0=D0=B9?= =?UTF-8?q?=D0=B4=D0=B5=D1=80=D1=8B=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D0=BE=D1=82=20=D0=B2=D0=B8=D0=B4=D0=B6=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D1=81=20=D0=BF=D1=80=D0=B5=D0=B4=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/dashboards/dashboard.controller.ts | 35 ++-- .../src/dashboards/dashboards-data.service.ts | 67 ++++++++ .../src/dashboards/dashboards.service.ts | 49 +++--- .../dashboards/interactive-widget-factory.ts | 28 ++++ .../src/dashboards/text-widget-factory.ts | 35 ++++ .../widget-data-loader-interface.ts | 9 ++ ...s-by-fields.widget-data-loader.service.ts} | 20 ++- ...s-like-jira.widget-data-loader.service.ts} | 24 ++- ...es-by-users.widget-data-loader.service.ts} | 20 ++- ...ue-subtrees.widget-data-loader.service.ts} | 12 +- .../src/dashboards/widget-interface.ts | 19 +++ .../dashboards/widgets-collection.service.ts | 68 ++++++++ .../event-emitter/src/event-emitter.module.ts | 33 ++-- libs/event-emitter/src/models/dashboard.ts | 25 ++- .../project-dashboard.service.ts | 152 ------------------ .../src/project-dashboard/widget-interface.ts | 4 - src/app.module.ts | 13 +- src/dashboards/dashboard-init.service.ts | 26 +++ .../simple-issues-list.controller.ts | 6 +- .../simple-kanban-board.controller.ts | 36 ++--- ...ues-by-tags.widget-data-loader.service.ts} | 20 ++- 21 files changed, 431 insertions(+), 270 deletions(-) create mode 100644 libs/event-emitter/src/dashboards/dashboards-data.service.ts create mode 100644 libs/event-emitter/src/dashboards/interactive-widget-factory.ts create mode 100644 libs/event-emitter/src/dashboards/text-widget-factory.ts create mode 100644 libs/event-emitter/src/dashboards/widget-data-loader-interface.ts rename libs/event-emitter/src/{project-dashboard/widgets/list-issues-by-fields.widget.service.ts => dashboards/widget-data-loader/list-issues-by-fields.widget-data-loader.service.ts} (89%) rename libs/event-emitter/src/{project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts => dashboards/widget-data-loader/list-issues-by-users-like-jira.widget-data-loader.service.ts} (88%) rename libs/event-emitter/src/{project-dashboard/widgets/list-issues-by-users.widget.service.ts => dashboards/widget-data-loader/list-issues-by-users.widget-data-loader.service.ts} (89%) rename libs/event-emitter/src/{project-dashboard/widgets/root-issue-subtrees.widget.service.ts => dashboards/widget-data-loader/root-issue-subtrees.widget-data-loader.service.ts} (88%) create mode 100644 libs/event-emitter/src/dashboards/widget-interface.ts create mode 100644 libs/event-emitter/src/dashboards/widgets-collection.service.ts delete mode 100644 libs/event-emitter/src/project-dashboard/project-dashboard.service.ts delete mode 100644 libs/event-emitter/src/project-dashboard/widget-interface.ts create mode 100644 src/dashboards/dashboard-init.service.ts rename src/dashboards/{widgets/issues-by-tags.widget.service.ts => widget-data-loader/issues-by-tags.widget-data-loader.service.ts} (88%) diff --git a/libs/event-emitter/src/dashboards/dashboard.controller.ts b/libs/event-emitter/src/dashboards/dashboard.controller.ts index b631554..a8889e8 100644 --- a/libs/event-emitter/src/dashboards/dashboard.controller.ts +++ b/libs/event-emitter/src/dashboards/dashboard.controller.ts @@ -1,36 +1,45 @@ import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common'; import { DashboardsService } from './dashboards.service'; import { BadRequestErrorHandler, getOrAppErrorOrThrow } from '../utils/result'; +import { DashboardsDataService } from './dashboards-data.service'; @Controller('dashboard') export class DashboardController { - constructor(private dashboardsService: DashboardsService) {} + constructor( + private dashboardsService: DashboardsService, + private dashboardsDataService: DashboardsDataService, + ) {} - @Post(':name') - async create(@Param('name') name: string): Promise { - if (name === 'anonymous') { - name = null; - } + @Post() + async create(): Promise { const res = await getOrAppErrorOrThrow( - () => this.dashboardsService.create(name), + () => this.dashboardsService.create(), BadRequestErrorHandler, ); return res.id; } - @Get(':name') - async load(@Param('name') name: string): Promise { + @Get(':id') + async load(@Param('id') id: string): Promise { const res = await getOrAppErrorOrThrow( - () => this.dashboardsService.load(name), + () => this.dashboardsService.load(id), BadRequestErrorHandler, ); return res; } - @Put(':name') - async save(@Param('name') name: string, @Body() data: any): Promise { + @Get(':id/load-data') + async loadData(@Param('id') id: string): Promise { + return await getOrAppErrorOrThrow( + () => this.dashboardsDataService.loadData(id), + BadRequestErrorHandler, + ); + } + + @Put(':id') + async save(@Param('id') id: string, @Body() data: any): Promise { const res = await getOrAppErrorOrThrow( - () => this.dashboardsService.save(name, data), + () => this.dashboardsService.save(id, data), BadRequestErrorHandler, ); return res; diff --git a/libs/event-emitter/src/dashboards/dashboards-data.service.ts b/libs/event-emitter/src/dashboards/dashboards-data.service.ts new file mode 100644 index 0000000..4fab49f --- /dev/null +++ b/libs/event-emitter/src/dashboards/dashboards-data.service.ts @@ -0,0 +1,67 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { DashboardsService } from './dashboards.service'; +import * as DashboardModel from '../models/dashboard'; +import { + AppError, + Result, + createAppError, + fail, + success, +} from '../utils/result'; +import { WidgetsCollectionService } from './widgets-collection.service'; + +export type WidgetWithData = { + widget: DashboardModel.Widget; + data: any; +}; + +@Injectable() +export class DashboardsDataService { + private logger = new Logger(DashboardsDataService.name); + + constructor( + private dashboardsService: DashboardsService, + private widgetsCollectionService: WidgetsCollectionService, + ) {} + + async loadData(id: string): Promise { + const cfg = await this.dashboardsService.load(id); + const results: WidgetWithData[] = []; + let isSuccess = false; + for (let i = 0; i < cfg.widgets.length; i++) { + const widget = cfg.widgets[i]; + const loadRes = await this.loadWidgetData( + widget.type, + widget.widgetParams, + widget.dataLoaderParams, + cfg, + ); + if (loadRes.result) { + isSuccess = true; + results.push({ + widget: widget, + data: loadRes.result, + }); + } + } + if (!isSuccess) throw createAppError('CANNOT_LOAD_DATA'); + return results; + } + + async loadWidgetData( + type: string, + widgetParams: DashboardModel.WidgetParams, + dataLoaderParams: DashboardModel.DataLoaderParams, + dashboardParams: DashboardModel.Data, + ): Promise> { + const widgetResult = this.widgetsCollectionService.getWidgetByType(type); + if (widgetResult.error) return fail(createAppError(widgetResult.error)); + const widget = widgetResult.result; + const renderResult = await widget.render( + widgetParams, + dataLoaderParams, + dashboardParams, + ); + return renderResult; + } +} diff --git a/libs/event-emitter/src/dashboards/dashboards.service.ts b/libs/event-emitter/src/dashboards/dashboards.service.ts index 797be20..997587a 100644 --- a/libs/event-emitter/src/dashboards/dashboards.service.ts +++ b/libs/event-emitter/src/dashboards/dashboards.service.ts @@ -1,9 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { Dashboards as DashboardsDb } from '../couchdb-datasources/dashboards'; -import { - Dashboard as DashboardModel, - Data as DashboardData, -} from '../models/dashboard'; +import * as DashboardModel from '../models/dashboard'; import nano from 'nano'; import { randomUUID } from 'crypto'; import { createAppError } from '../utils/result'; @@ -14,58 +11,56 @@ export class DashboardsService { constructor(private db: DashboardsDb) {} - async create(name?: string): Promise { - if (!name) { - name = randomUUID(); - } - this.logger.debug(`Create new dashboard with name - ${name}`); - if (await this.isExists(name)) { + async create(): Promise { + const id = randomUUID(); + this.logger.debug(`Create new dashboard with id - ${id}`); + if (await this.isExists(id)) { const err = createAppError('ALREADY_EXISTS'); this.logger.error(`Error - ${JSON.stringify(err)}`); throw err; } const ds = await this.db.getDatasource(); - const doc: nano.MaybeDocument & DashboardModel = { - _id: name, - id: name, + const doc: nano.MaybeDocument & DashboardModel.Dashboard = { + _id: id, + id: id, data: null, }; await ds.insert(doc); - return await ds.get(name); + return await ds.get(id); } async loadRawData( - name: string, - ): Promise { - this.logger.debug(`Load raw data, dashboard name - ${name}`); + id: string, + ): Promise { + this.logger.debug(`Load raw data, dashboard id - ${id}`); const ds = await this.db.getDatasource(); - if (!(await this.isExists(name))) throw createAppError('NOT_EXISTS'); - const res = await ds.get(name); + if (!(await this.isExists(id))) throw createAppError('NOT_EXISTS'); + const res = await ds.get(id); return res; } - async load(name: string): Promise { - this.logger.debug(`Load dashboard name - ${name}`); - const rawData = await this.loadRawData(name); + async load(id: string): Promise { + this.logger.debug(`Load dashboard id - ${id}`); + const rawData = await this.loadRawData(id); return rawData.data; } - async isExists(name: string): Promise { + async isExists(id: string): Promise { const ds = await this.db.getDatasource(); try { - await ds.get(name); + await ds.get(id); return true; } catch (ex) { return false; } } - async save(name: string, data: any): Promise { + async save(id: string, data: DashboardModel.Data): Promise { this.logger.debug( - `Save dashboard name - ${name}, data - ${JSON.stringify(data)}`, + `Save dashboard id - ${id}, data - ${JSON.stringify(data)}`, ); const ds = await this.db.getDatasource(); - const prevValue = await this.loadRawData(name); + const prevValue = await this.loadRawData(id); const newValue = { _id: prevValue._id, diff --git a/libs/event-emitter/src/dashboards/interactive-widget-factory.ts b/libs/event-emitter/src/dashboards/interactive-widget-factory.ts new file mode 100644 index 0000000..b426f93 --- /dev/null +++ b/libs/event-emitter/src/dashboards/interactive-widget-factory.ts @@ -0,0 +1,28 @@ +import { Result, AppError } from '../utils/result'; +import { WidgetDataLoaderInterface } from './widget-data-loader-interface'; +import { WidgetInterface } from './widget-interface'; + +export class InteractiveWidget + implements WidgetInterface +{ + constructor( + public dataLoader: WidgetDataLoaderInterface, + public type: string, + ) {} + + async render( + widgetParams: any, + dataLoaderParams: any, + dashboardParams: any, + ): Promise> { + const data = await this.dataLoader.load(dataLoaderParams, dashboardParams); + return data; + } +} + +export function createInteractiveWidget( + dataLoader: WidgetDataLoaderInterface, + type: string, +): WidgetInterface { + return new InteractiveWidget(dataLoader, type); +} diff --git a/libs/event-emitter/src/dashboards/text-widget-factory.ts b/libs/event-emitter/src/dashboards/text-widget-factory.ts new file mode 100644 index 0000000..7e841c0 --- /dev/null +++ b/libs/event-emitter/src/dashboards/text-widget-factory.ts @@ -0,0 +1,35 @@ +import { Result, AppError, success } from '../utils/result'; +import { WidgetDataLoaderInterface } from './widget-data-loader-interface'; +import { WidgetInterface } from './widget-interface'; +import Handlebars from 'handlebars'; + +export class TextWidget implements WidgetInterface { + constructor( + public dataLoader: WidgetDataLoaderInterface, + public type: string, + public template: string, + ) {} + + async render( + widgetParams: any, + dataLoaderParams: any, + dashboardParams: any, + ): Promise> { + const params = { + widgetParams, + dataLoaderParams, + dashboardParams, + }; + const template = Handlebars.compile(this.template); + const res = template(params); + return success(res); + } +} + +export function createTextWidget( + dataLoader: WidgetDataLoaderInterface, + type: string, + template: string, +): WidgetInterface { + return new TextWidget(dataLoader, type, template); +} diff --git a/libs/event-emitter/src/dashboards/widget-data-loader-interface.ts b/libs/event-emitter/src/dashboards/widget-data-loader-interface.ts new file mode 100644 index 0000000..3608fc7 --- /dev/null +++ b/libs/event-emitter/src/dashboards/widget-data-loader-interface.ts @@ -0,0 +1,9 @@ +import { AppError, Result } from '../utils/result'; + +export interface WidgetDataLoaderInterface { + isMyConfig(dataLoaderParams: DLP): boolean; + load( + dataLoaderParams: DLP, + dashboardParams: DBP, + ): Promise>; +} diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-fields.widget.service.ts b/libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-fields.widget-data-loader.service.ts similarity index 89% rename from libs/event-emitter/src/project-dashboard/widgets/list-issues-by-fields.widget.service.ts rename to libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-fields.widget-data-loader.service.ts index 3716036..7414c77 100644 --- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-fields.widget.service.ts +++ b/libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-fields.widget-data-loader.service.ts @@ -12,8 +12,14 @@ import { GetValueFromObjectByKey } from '@app/event-emitter/utils/get-value-from import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store'; import { Injectable, Logger } from '@nestjs/common'; import nano from 'nano'; -import { WidgetInterface } from '../widget-interface'; import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer'; +import { WidgetDataLoaderInterface } from '../widget-data-loader-interface'; +import { + AppError, + Result, + createAppError, + success, +} from '@app/event-emitter/utils/result'; export namespace ListIssuesByFieldsWidgetNs { export type Params = { @@ -34,10 +40,10 @@ export namespace ListIssuesByFieldsWidgetNs { type Params = ListIssuesByFieldsWidgetNs.Params; @Injectable() -export class ListIssuesByFieldsWidgetService - implements WidgetInterface +export class ListIssuesByFieldsWidgetDataLoaderService + implements WidgetDataLoaderInterface { - private logger = new Logger(ListIssuesByFieldsWidgetService.name); + private logger = new Logger(ListIssuesByFieldsWidgetDataLoaderService.name); private issuesLoader: IssuesServiceNs.IssuesLoader; constructor( @@ -52,7 +58,7 @@ export class ListIssuesByFieldsWidgetService return true; } - async render(widgetParams: Params): Promise { + async load(widgetParams: Params): Promise> { let store: FlatIssuesStore; if (widgetParams.fromRootIssueId) { store = await this.getListFromRoot(widgetParams.fromRootIssueId); @@ -61,7 +67,7 @@ export class ListIssuesByFieldsWidgetService } else { const errMsg = `Wrong widgetParams value`; this.logger.error(errMsg); - throw new Error(errMsg); + return fail(createAppError(errMsg)); } await store.enhanceIssues([ this.timePassedHighlightEnhancer, @@ -87,7 +93,7 @@ export class ListIssuesByFieldsWidgetService return a.metainfo.title.localeCompare(b.metainfo.title); }); } - return res; + return success(res); } private async getListFromRoot(issueId: number): Promise { diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts b/libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-users-like-jira.widget-data-loader.service.ts similarity index 88% rename from libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts rename to libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-users-like-jira.widget-data-loader.service.ts index d31eb6e..bd14553 100644 --- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts +++ b/libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-users-like-jira.widget-data-loader.service.ts @@ -11,8 +11,14 @@ import { GetValueFromObjectByKey } from '@app/event-emitter/utils/get-value-from import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store'; import { Injectable, Logger } from '@nestjs/common'; import nano from 'nano'; -import { WidgetInterface } from '../widget-interface'; import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer'; +import { WidgetDataLoaderInterface } from '../widget-data-loader-interface'; +import { + AppError, + Result, + createAppError, + success, +} from '@app/event-emitter/utils/result'; export namespace ListIssuesByUsersLikeJiraWidgetNs { export namespace Models { @@ -29,15 +35,17 @@ export namespace ListIssuesByUsersLikeJiraWidgetNs { type Params = ListIssuesByUsersLikeJiraWidgetNs.Models.Params; @Injectable() -export class ListIssuesByUsersLikeJiraWidgetService - implements WidgetInterface +export class ListIssuesByUsersLikeJiraWidgetDataLoaderService + implements WidgetDataLoaderInterface { - private logger = new Logger(ListIssuesByUsersLikeJiraWidgetService.name); + private logger = new Logger( + ListIssuesByUsersLikeJiraWidgetDataLoaderService.name, + ); private issuesLoader: IssuesServiceNs.IssuesLoader; constructor( - private issuesService: IssuesService, private timePassedHighlightEnhancer: TimePassedHighlightEnhancer, + private issuesService: IssuesService, private issueUrlEnhancer: IssueUrlEnhancer, ) { this.issuesLoader = this.issuesService.createDynamicIssuesLoader(); @@ -47,7 +55,7 @@ export class ListIssuesByUsersLikeJiraWidgetService return true; } - async render(widgetParams: Params): Promise { + async load(widgetParams: Params): Promise> { let store: FlatIssuesStore; if (widgetParams.fromRootIssueId) { store = await this.getListFromRoot(widgetParams.fromRootIssueId); @@ -56,7 +64,7 @@ export class ListIssuesByUsersLikeJiraWidgetService } else { const errMsg = `Wrong widgetParams value`; this.logger.error(errMsg); - throw new Error(errMsg); + return fail(createAppError(errMsg)); } await store.enhanceIssues([ this.timePassedHighlightEnhancer, @@ -92,7 +100,7 @@ export class ListIssuesByUsersLikeJiraWidgetService return a.metainfo.title.localeCompare(b.metainfo.title); }); } - return res; + return success(res); } private async getListFromRoot(issueId: number): Promise { diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts b/libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-users.widget-data-loader.service.ts similarity index 89% rename from libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts rename to libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-users.widget-data-loader.service.ts index 4ba3cbd..6180254 100644 --- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts +++ b/libs/event-emitter/src/dashboards/widget-data-loader/list-issues-by-users.widget-data-loader.service.ts @@ -12,8 +12,14 @@ import { GetValueFromObjectByKey } from '@app/event-emitter/utils/get-value-from import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store'; import { Injectable, Logger } from '@nestjs/common'; import nano from 'nano'; -import { WidgetInterface } from '../widget-interface'; import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer'; +import { WidgetDataLoaderInterface } from '../widget-data-loader-interface'; +import { + AppError, + Result, + createAppError, + success, +} from '@app/event-emitter/utils/result'; export namespace ListIssuesByUsersWidgetNs { export namespace Models { @@ -41,10 +47,10 @@ type ExtendedIssue = RedmineTypes.Issue & Record; type FindResult = ListIssuesByUsersWidgetNs.Models.FindResult; @Injectable() -export class ListIssuesByUsersWidgetService - implements WidgetInterface +export class ListIssuesByUsersWidgetDataLoaderService + implements WidgetDataLoaderInterface { - private logger = new Logger(ListIssuesByUsersWidgetService.name); + private logger = new Logger(ListIssuesByUsersWidgetDataLoaderService.name); private issuesLoader: IssuesServiceNs.IssuesLoader; constructor( @@ -59,7 +65,7 @@ export class ListIssuesByUsersWidgetService return true; } - async render(widgetParams: Params): Promise { + async load(widgetParams: Params): Promise> { let store: FlatIssuesStore; if (widgetParams.fromRootIssueId) { store = await this.getListFromRoot(widgetParams.fromRootIssueId); @@ -68,7 +74,7 @@ export class ListIssuesByUsersWidgetService } else { const errMsg = `Wrong widgetParams value`; this.logger.error(errMsg); - throw new Error(errMsg); + return fail(createAppError(errMsg)); } await store.enhanceIssues([ this.timePassedHighlightEnhancer, @@ -95,7 +101,7 @@ export class ListIssuesByUsersWidgetService return a.metainfo.title.localeCompare(b.metainfo.title); }); } - return res; + return success(res); } private async getListFromRoot(issueId: number): Promise { diff --git a/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts b/libs/event-emitter/src/dashboards/widget-data-loader/root-issue-subtrees.widget-data-loader.service.ts similarity index 88% rename from libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts rename to libs/event-emitter/src/dashboards/widget-data-loader/root-issue-subtrees.widget-data-loader.service.ts index 94b7974..6288ee5 100644 --- a/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts +++ b/libs/event-emitter/src/dashboards/widget-data-loader/root-issue-subtrees.widget-data-loader.service.ts @@ -11,8 +11,9 @@ import { TreeIssuesStoreNs, } from '@app/event-emitter/utils/tree-issues-store'; import { Injectable } from '@nestjs/common'; -import { WidgetInterface } from '../widget-interface'; import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer'; +import { WidgetDataLoaderInterface } from '../widget-data-loader-interface'; +import { AppError, Result, success } from '@app/event-emitter/utils/result'; export namespace RootIssueSubTreesWidgetNs { export namespace Models { @@ -39,8 +40,8 @@ export namespace RootIssueSubTreesWidgetNs { type Params = RootIssueSubTreesWidgetNs.Models.Params; @Injectable() -export class RootIssueSubTreesWidgetService - implements WidgetInterface +export class RootIssueSubTreesWidgetDataLoaderService + implements WidgetDataLoaderInterface { private issuesLoader: IssuesServiceNs.IssuesLoader; @@ -56,7 +57,7 @@ export class RootIssueSubTreesWidgetService return true; } - async render(widgetParams: Params): Promise { + async load(widgetParams: Params): Promise> { const treeStore = new TreeIssuesStore(); const rootIssue = await this.issuesService.getIssue( widgetParams.rootIssueId, @@ -89,11 +90,12 @@ export class RootIssueSubTreesWidgetService } } } - return stories.map((s) => { + const res = stories.map((s) => { return { data: s.store.groupByStatus(widgetParams.statuses), metainfo: s.metainfo, }; }); + return success(res); } } diff --git a/libs/event-emitter/src/dashboards/widget-interface.ts b/libs/event-emitter/src/dashboards/widget-interface.ts new file mode 100644 index 0000000..4fdd2c1 --- /dev/null +++ b/libs/event-emitter/src/dashboards/widget-interface.ts @@ -0,0 +1,19 @@ +import { AppError, Result } from '../utils/result'; +import { WidgetDataLoaderInterface } from './widget-data-loader-interface'; + +/** + * - WP - widget params + * - DLP - dataloader params + * - DBP - dashboard params + * - DLR - dataloader result + * - R - result + */ +export interface WidgetInterface { + dataLoader: WidgetDataLoaderInterface; + type: string; + render( + widgetParams: WP, + dataLoaderParams: DLP, + dashboardParams: DBP, + ): Promise>; +} diff --git a/libs/event-emitter/src/dashboards/widgets-collection.service.ts b/libs/event-emitter/src/dashboards/widgets-collection.service.ts new file mode 100644 index 0000000..a09685a --- /dev/null +++ b/libs/event-emitter/src/dashboards/widgets-collection.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { WidgetInterface } from './widget-interface'; +import { ListIssuesByFieldsWidgetDataLoaderService } from './widget-data-loader/list-issues-by-fields.widget-data-loader.service'; +import { ListIssuesByUsersLikeJiraWidgetDataLoaderService } from './widget-data-loader/list-issues-by-users-like-jira.widget-data-loader.service'; +import { RootIssueSubTreesWidgetDataLoaderService } from './widget-data-loader/root-issue-subtrees.widget-data-loader.service'; +import { createInteractiveWidget } from './interactive-widget-factory'; +import { Result, success } from '@app/event-emitter/utils/result'; + +@Injectable() +export class WidgetsCollectionService { + collection: WidgetInterface[] = []; + + constructor( + private listIssuesByFieldsWidgetDataLoaderService: ListIssuesByFieldsWidgetDataLoaderService, + private listIssuesByUsersLikeJiraWidgetDataLoaderService: ListIssuesByUsersLikeJiraWidgetDataLoaderService, + private rootIssueSubTreesWidgetDataLoaderService: RootIssueSubTreesWidgetDataLoaderService, + ) { + const collection = [ + createInteractiveWidget( + this.listIssuesByFieldsWidgetDataLoaderService, + 'kanban_by_fields', + ), + createInteractiveWidget( + this.listIssuesByUsersLikeJiraWidgetDataLoaderService, + 'kanban_by_users', + ), + createInteractiveWidget( + this.rootIssueSubTreesWidgetDataLoaderService, + 'kanban_by_tree', + ), + createInteractiveWidget( + this.listIssuesByFieldsWidgetDataLoaderService, + 'issues_list_by_fields', + ), + createInteractiveWidget( + this.listIssuesByUsersLikeJiraWidgetDataLoaderService, + 'issues_list_by_users', + ), + createInteractiveWidget( + this.rootIssueSubTreesWidgetDataLoaderService, + 'issues_list_by_tree', + ), + ]; + + collection.forEach((w) => this.appendWidget(w)); + } + + appendWidget( + widget: WidgetInterface, + ): Result { + const type = widget.type; + const isExists = this.collection.find((w) => w.type === type); + if (isExists) return fail('WIDGET_WITH_SAME_TYPE_ALREADY_EXISTS'); + this.collection.push(widget); + return success(true); + } + + getWidgetTypes(): string[] { + return this.collection.map((w) => w.type); + } + + getWidgetByType( + type: string, + ): Result, string> { + const widget = this.collection.find((w) => w.type === type); + return widget ? success(widget) : fail('WIDGET_WITH_SAME_TYPE_NOT_FOUND'); + } +} diff --git a/libs/event-emitter/src/event-emitter.module.ts b/libs/event-emitter/src/event-emitter.module.ts index f1c9607..0a6d2f5 100644 --- a/libs/event-emitter/src/event-emitter.module.ts +++ b/libs/event-emitter/src/event-emitter.module.ts @@ -18,15 +18,10 @@ import { IssuesService } from './issues/issues.service'; import { IssuesController } from './issues/issues.controller'; import { TimestampEnhancer } from './issue-enhancers/timestamps-enhancer'; import { EnhancerService } from './issue-enhancers/enhancer.service'; -import { ProjectDashboardService } from './project-dashboard/project-dashboard.service'; -import { RootIssueSubTreesWidgetService } from './project-dashboard/widgets/root-issue-subtrees.widget.service'; import { DynamicLoader } from './configs/dynamic-loader'; import { RedminePublicUrlConverter } from './converters/redmine-public-url.converter'; import { IssueUrlEnhancer } from './issue-enhancers/issue-url-enhancer'; -import { ListIssuesByUsersWidgetService } from './project-dashboard/widgets/list-issues-by-users.widget.service'; -import { ListIssuesByUsersLikeJiraWidgetService } from './project-dashboard/widgets/list-issues-by-users-like-jira.widget.service'; import { TimePassedHighlightEnhancer } from './issue-enhancers/time-passed-highlight-enhancer'; -import { ListIssuesByFieldsWidgetService } from './project-dashboard/widgets/list-issues-by-fields.widget.service'; import { IssuesUpdaterService } from './issues-updater/issues-updater.service'; import { CalendarEnhancer } from './issue-enhancers/calendar-enhancer'; import { CalendarService } from './calendar/calendar.service'; @@ -34,6 +29,12 @@ import { CalendarController } from './calendar/calendar.controller'; import { Dashboards as DashboardsDs } from './couchdb-datasources/dashboards'; import { DashboardController } from './dashboards/dashboard.controller'; import { DashboardsService } from './dashboards/dashboards.service'; +import { DashboardsDataService } from './dashboards/dashboards-data.service'; +import { RootIssueSubTreesWidgetDataLoaderService } from './dashboards/widget-data-loader/root-issue-subtrees.widget-data-loader.service'; +import { ListIssuesByUsersWidgetDataLoaderService } from './dashboards/widget-data-loader/list-issues-by-users.widget-data-loader.service'; +import { ListIssuesByUsersLikeJiraWidgetDataLoaderService } from './dashboards/widget-data-loader/list-issues-by-users-like-jira.widget-data-loader.service'; +import { ListIssuesByFieldsWidgetDataLoaderService } from './dashboards/widget-data-loader/list-issues-by-fields.widget-data-loader.service'; +import { WidgetsCollectionService } from './dashboards/widgets-collection.service'; @Module({}) export class EventEmitterModule implements OnModuleInit { @@ -57,15 +58,14 @@ export class EventEmitterModule implements OnModuleInit { IssuesService, TimestampEnhancer, EnhancerService, - ProjectDashboardService, - RootIssueSubTreesWidgetService, + RootIssueSubTreesWidgetDataLoaderService, DynamicLoader, RedminePublicUrlConverter, IssueUrlEnhancer, - ListIssuesByUsersWidgetService, - ListIssuesByUsersLikeJiraWidgetService, + ListIssuesByUsersWidgetDataLoaderService, + ListIssuesByUsersLikeJiraWidgetDataLoaderService, TimePassedHighlightEnhancer, - ListIssuesByFieldsWidgetService, + ListIssuesByFieldsWidgetDataLoaderService, { provide: 'ISSUES_UPDATER_SERVICE', useFactory: (configService: ConfigService) => { @@ -101,6 +101,8 @@ export class EventEmitterModule implements OnModuleInit { inject: ['CALENDAR_ENHANCER', IssuesService], }, DashboardsService, + DashboardsDataService, + WidgetsCollectionService, ], exports: [ EventEmitterService, @@ -116,15 +118,14 @@ export class EventEmitterModule implements OnModuleInit { IssuesService, TimestampEnhancer, EnhancerService, - ProjectDashboardService, - RootIssueSubTreesWidgetService, + RootIssueSubTreesWidgetDataLoaderService, DynamicLoader, RedminePublicUrlConverter, IssueUrlEnhancer, - ListIssuesByUsersWidgetService, - ListIssuesByUsersLikeJiraWidgetService, + ListIssuesByUsersWidgetDataLoaderService, + ListIssuesByUsersLikeJiraWidgetDataLoaderService, TimePassedHighlightEnhancer, - ListIssuesByFieldsWidgetService, + ListIssuesByFieldsWidgetDataLoaderService, { provide: 'ISSUES_UPDATER_SERVICE', useExisting: 'ISSUES_UPDATER_SERVICE', @@ -138,6 +139,8 @@ export class EventEmitterModule implements OnModuleInit { useExisting: 'CALENDAR_SERVICE', }, DashboardsService, + DashboardsDataService, + WidgetsCollectionService, ], controllers: [ MainController, diff --git a/libs/event-emitter/src/models/dashboard.ts b/libs/event-emitter/src/models/dashboard.ts index 8c19d37..8f28995 100644 --- a/libs/event-emitter/src/models/dashboard.ts +++ b/libs/event-emitter/src/models/dashboard.ts @@ -1,6 +1,29 @@ -export type Data = Record | null; +export type Data = { + widgets: Widget[]; + title: string; +} | null; export type Dashboard = { id: string; data: Data; }; + +/** + * Параметры для отрисовки данных + */ +export type WidgetParams = { + collapsed?: boolean; +} & Record; + +/** + * Параметры для загрузки данных + */ +export type DataLoaderParams = Record | null; + +export type Widget = { + type: string; + id: string; + title: string; + widgetParams?: WidgetParams; + dataLoaderParams?: DataLoaderParams; +}; diff --git a/libs/event-emitter/src/project-dashboard/project-dashboard.service.ts b/libs/event-emitter/src/project-dashboard/project-dashboard.service.ts deleted file mode 100644 index 78446ff..0000000 --- a/libs/event-emitter/src/project-dashboard/project-dashboard.service.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -import { Injectable } from '@nestjs/common'; -import { RedmineTypes } from '../models/redmine-types'; - -export namespace ProjectDashboard { - export namespace Models { - export type Params = { - projectName: string; - workers: Worker[]; - filter: FilterDefination[]; - statuses: Status[]; - }; - - export type Worker = { - id?: number; - firstname?: string; - lastname?: string; - name?: string; - }; - - export enum FilterTypes { - TREE = 'TREE', - LIST = 'LIST', - DYNAMIC_LIST = 'DYNAMIC_LIST', - VERSION = 'VERSION', - } - - export namespace FilterParams { - export type Tree = { - rootIssueId: number; - subtrees: SubTree[]; - showOthers: boolean; - }; - export type SubTree = { - title: string; - issueId: number; - }; - export type List = { - issueIds: number[]; - }; - export type Version = { - version: string; - }; - export type DynamicList = { - selector: Record; - }; - export type AnyFilterParams = Tree | List | DynamicList | Version; - } - - export type FilterParams = Record; - - export namespace FilterResults { - export type Tree = List; - export type List = { status: string; issues: RedmineTypes.Issue[] }[]; - export type DynamicList = List; - export type Version = List; - export type AnyFilterResults = List | Tree | DynamicList | Version; - } - - export type FilterResult = Record[]; - - export type AnalyticFunction = { - functionName: string; - }; - - export type FilterDefination = { - type: FilterTypes; - title: string; - params: FilterParams.AnyFilterParams; - }; - - export type FilterWithResults = { - params: FilterDefination; - results: FilterResults.AnyFilterResults; - }; - - export type Status = { - name: string; - closed: boolean; - }; - } - - export function CheckWorker(worker: Models.Worker): boolean { - return Boolean( - (typeof worker.id === 'number' && worker.id >= 0) || - (worker.firstname && worker.lastname) || - worker.name, - ); - } - - export class SingleProject { - // TODO: code for SingleProject - constructor(private params: Models.Params) { - return; - } - } - - export namespace Widgets { - // Чё будет делать виджет? - // * рендер - из параметров будет создавать данные с какими-либо расчётами - - export interface Widget { - render( - filterParams: Models.FilterParams, - dashboardParams: Models.Params, - ): Models.FilterResult; - } - - export class List implements Widget { - render( - filterParams: Models.FilterParams, - dashboardParams: Models.Params, - ): Models.FilterResult { - throw new Error('Method not implemented.'); - } - } - - export class DynamicList implements Widget { - render( - filterParams: Models.FilterParams, - dashboardParams: Models.Params, - ): Models.FilterResult { - throw new Error('Method not implemented.'); - } - } - - export class Tree implements Widget { - render( - filterParams: Models.FilterParams, - dashboardParams: Models.Params, - ): Models.FilterResult { - throw new Error('Method not implemented.'); - } - } - - export class Version implements Widget { - render( - filterParams: Models.FilterParams, - dashboardParams: Models.Params, - ): Models.FilterResult { - throw new Error('Method not implemented.'); - } - } - } -} - -@Injectable() -export class ProjectDashboardService { - constructor() { - return; - } -} diff --git a/libs/event-emitter/src/project-dashboard/widget-interface.ts b/libs/event-emitter/src/project-dashboard/widget-interface.ts deleted file mode 100644 index 2f00e79..0000000 --- a/libs/event-emitter/src/project-dashboard/widget-interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface WidgetInterface { - isMyConfig(widgetParams: W): boolean; - render(widgetParams: W, dashboardParams: D): Promise; -} diff --git a/src/app.module.ts b/src/app.module.ts index 221d5e8..077e588 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -43,7 +43,7 @@ import { SetDailyEccmUserCommentBotHandlerService } from './telegram-bot/handler import { DailyEccmWithExtraDataService } from './reports/daily-eccm-with-extra-data.service'; import { SimpleKanbanBoardController } from './dashboards/simple-kanban-board.controller'; import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer'; -import { IssuesByTagsWidgetService } from './dashboards/widgets/issues-by-tags.widget.service'; +import { IssuesByTagsWidgetDataLoaderService } from './dashboards/widget-data-loader/issues-by-tags.widget-data-loader.service'; import { CategoryMergeToTagsEnhancer } from './issue-enhancers/category-merge-to-tags-enhancer'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; @@ -52,6 +52,7 @@ import { TagsManagerController } from './tags-manager/tags-manager.controller'; import { CreateTagManagerServiceProvider } from './tags-manager/tags-manager.service'; import { CalendarEnhancer } from '@app/event-emitter/issue-enhancers/calendar-enhancer'; import { Dashboards as DashboardsDs } from '@app/event-emitter/couchdb-datasources/dashboards'; +import { DashboardInitService } from './dashboards/dashboard-init.service'; @Module({ imports: [ @@ -99,7 +100,7 @@ import { Dashboards as DashboardsDs } from '@app/event-emitter/couchdb-datasourc DailyEccmUserCommentsService, SetDailyEccmUserCommentBotHandlerService, DailyEccmWithExtraDataService, - IssuesByTagsWidgetService, + IssuesByTagsWidgetDataLoaderService, { provide: 'CATEGORY_MERGE_TO_TAGS_ENHANCER', useFactory: (configService: ConfigService) => { @@ -111,6 +112,7 @@ import { Dashboards as DashboardsDs } from '@app/event-emitter/couchdb-datasourc inject: [ConfigService], }, CreateTagManagerServiceProvider('TAG_MANAGER_SERVICE'), + DashboardInitService, ], }) export class AppModule implements OnModuleInit { @@ -137,6 +139,8 @@ export class AppModule implements OnModuleInit { @Inject('CALENDAR_ENHANCER') private calendarEnhancer: CalendarEnhancer, + + private dashboardInitService: DashboardInitService, ) {} onModuleInit() { @@ -215,6 +219,7 @@ export class AppModule implements OnModuleInit { }); this.initDailyEccmUserCommentsPipeline(); + this.initDashbordProviders(); } private initDailyEccmUserCommentsPipeline(): void { @@ -228,4 +233,8 @@ export class AppModule implements OnModuleInit { }, ); } + + private initDashbordProviders(): void { + this.dashboardInitService.init(); + } } diff --git a/src/dashboards/dashboard-init.service.ts b/src/dashboards/dashboard-init.service.ts new file mode 100644 index 0000000..cb3e7f1 --- /dev/null +++ b/src/dashboards/dashboard-init.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { WidgetsCollectionService } from '@app/event-emitter/dashboards/widgets-collection.service'; +import { IssuesByTagsWidgetDataLoaderService } from './widget-data-loader/issues-by-tags.widget-data-loader.service'; +import { createInteractiveWidget } from '@app/event-emitter/dashboards/interactive-widget-factory'; + +@Injectable() +export class DashboardInitService { + constructor( + private issuesByTagsWidgetDataLoaderService: IssuesByTagsWidgetDataLoaderService, + private widgetsCollectionService: WidgetsCollectionService, + ) {} + + init(): void { + const collection = [ + createInteractiveWidget( + this.issuesByTagsWidgetDataLoaderService, + 'kanban_by_tags', + ), + createInteractiveWidget( + this.issuesByTagsWidgetDataLoaderService, + 'issues_list_by_tags', + ), + ]; + collection.forEach((w) => this.widgetsCollectionService.appendWidget(w)); + } +} diff --git a/src/dashboards/simple-issues-list.controller.ts b/src/dashboards/simple-issues-list.controller.ts index 5a91041..705b326 100644 --- a/src/dashboards/simple-issues-list.controller.ts +++ b/src/dashboards/simple-issues-list.controller.ts @@ -1,7 +1,7 @@ import { DynamicLoader } from '@app/event-emitter/configs/dynamic-loader'; import { Controller, Get, Param, Render } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { IssuesByTagsWidgetService } from './widgets/issues-by-tags.widget.service'; +import { IssuesByTagsWidgetDataLoaderService } from './widget-data-loader/issues-by-tags.widget-data-loader.service'; import { parse } from 'jsonc-parser'; @Controller('simple-issues-list') @@ -9,7 +9,7 @@ export class SimpleIssuesListController { private path: string; constructor( - private issuesByTagsWidgetService: IssuesByTagsWidgetService, + private issuesByTagsWidgetDataLoaderService: IssuesByTagsWidgetDataLoaderService, private dynamicLoader: DynamicLoader, private configService: ConfigService, ) { @@ -23,7 +23,7 @@ export class SimpleIssuesListController { ext: 'jsonc', parser: parse, }); - return await this.issuesByTagsWidgetService.render(cfg); + return await this.issuesByTagsWidgetDataLoaderService.load(cfg); } @Get('/by-tags/:name') diff --git a/src/dashboards/simple-kanban-board.controller.ts b/src/dashboards/simple-kanban-board.controller.ts index 0bed6b5..a504923 100644 --- a/src/dashboards/simple-kanban-board.controller.ts +++ b/src/dashboards/simple-kanban-board.controller.ts @@ -1,18 +1,14 @@ import { DynamicLoader } from '@app/event-emitter/configs/dynamic-loader'; import { RedmineEventsGateway } from '@app/event-emitter/events/redmine-events.gateway'; import { IssuesService } from '@app/event-emitter/issues/issues.service'; -import { ListIssuesByFieldsWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-fields.widget.service'; -import { ListIssuesByUsersLikeJiraWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service'; -import { ListIssuesByUsersWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-users.widget.service'; -import { - RootIssueSubTreesWidgetNs, - RootIssueSubTreesWidgetService, -} from '@app/event-emitter/project-dashboard/widgets/root-issue-subtrees.widget.service'; -import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store'; import { Controller, Get, Logger, Param, Render } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { parse } from 'jsonc-parser'; -import { IssuesByTagsWidgetService } from './widgets/issues-by-tags.widget.service'; +import { IssuesByTagsWidgetDataLoaderService } from './widget-data-loader/issues-by-tags.widget-data-loader.service'; +import { RootIssueSubTreesWidgetDataLoaderService } from '@app/event-emitter/dashboards/widget-data-loader/root-issue-subtrees.widget-data-loader.service'; +import { ListIssuesByUsersWidgetDataLoaderService } from '@app/event-emitter/dashboards/widget-data-loader/list-issues-by-users.widget-data-loader.service'; +import { ListIssuesByUsersLikeJiraWidgetDataLoaderService } from '@app/event-emitter/dashboards/widget-data-loader/list-issues-by-users-like-jira.widget-data-loader.service'; +import { ListIssuesByFieldsWidgetDataLoaderService } from '@app/event-emitter/dashboards/widget-data-loader/list-issues-by-fields.widget-data-loader.service'; @Controller('simple-kanban-board') export class SimpleKanbanBoardController { @@ -20,14 +16,14 @@ export class SimpleKanbanBoardController { private path: string; constructor( - private rootIssueSubTreesWidgetService: RootIssueSubTreesWidgetService, + private rootIssueSubTreesWidgetDataLoaderService: RootIssueSubTreesWidgetDataLoaderService, private dynamicLoader: DynamicLoader, private configService: ConfigService, - private listIssuesByUsersWidgetService: ListIssuesByUsersWidgetService, - private listIssuesByUsersLikeJiraWidgetService: ListIssuesByUsersLikeJiraWidgetService, - private issuesByTagsWidgetService: IssuesByTagsWidgetService, + private listIssuesByUsersWidgetDataLoaderService: ListIssuesByUsersWidgetDataLoaderService, + private listIssuesByUsersLikeJiraWidgetDataLoaderService: ListIssuesByUsersLikeJiraWidgetDataLoaderService, + private issuesByTagsWidgetDataLoaderService: IssuesByTagsWidgetDataLoaderService, private redmineEventsGateway: RedmineEventsGateway, - private listIssuesByFieldsWidgetService: ListIssuesByFieldsWidgetService, + private listIssuesByFieldsWidgetDataLoaderService: ListIssuesByFieldsWidgetDataLoaderService, private issuesService: IssuesService, ) { this.path = this.configService.get('simpleKanbanBoard.path'); @@ -40,7 +36,7 @@ export class SimpleKanbanBoardController { ext: 'jsonc', parser: parse, }); - return await this.rootIssueSubTreesWidgetService.render(cfg); + return await this.rootIssueSubTreesWidgetDataLoaderService.load(cfg); } @Get('/tree/:name') @@ -73,7 +69,7 @@ export class SimpleKanbanBoardController { ext: 'jsonc', parser: parse, }); - return await this.listIssuesByUsersWidgetService.render(cfg); + return await this.listIssuesByUsersWidgetDataLoaderService.load(cfg); } @Get('/by-users/:name') @@ -89,7 +85,9 @@ export class SimpleKanbanBoardController { ext: 'jsonc', parser: parse, }); - return await this.listIssuesByUsersLikeJiraWidgetService.render(cfg); + return await this.listIssuesByUsersLikeJiraWidgetDataLoaderService.load( + cfg, + ); } @Get('/by-users-like-jira/:name') @@ -105,7 +103,7 @@ export class SimpleKanbanBoardController { ext: 'jsonc', parser: parse, }); - return await this.issuesByTagsWidgetService.render(cfg); + return await this.issuesByTagsWidgetDataLoaderService.load(cfg); } @Get('/by-tags/:name') @@ -121,7 +119,7 @@ export class SimpleKanbanBoardController { ext: 'jsonc', parser: parse, }); - return await this.listIssuesByFieldsWidgetService.render(cfg); + return await this.listIssuesByFieldsWidgetDataLoaderService.load(cfg); } @Get('/by-fields/:name') diff --git a/src/dashboards/widgets/issues-by-tags.widget.service.ts b/src/dashboards/widget-data-loader/issues-by-tags.widget-data-loader.service.ts similarity index 88% rename from src/dashboards/widgets/issues-by-tags.widget.service.ts rename to src/dashboards/widget-data-loader/issues-by-tags.widget-data-loader.service.ts index 13e2e9c..bca896f 100644 --- a/src/dashboards/widgets/issues-by-tags.widget.service.ts +++ b/src/dashboards/widget-data-loader/issues-by-tags.widget-data-loader.service.ts @@ -6,13 +6,19 @@ import { IssuesService, IssuesServiceNs, } from '@app/event-emitter/issues/issues.service'; -import { WidgetInterface } from '@app/event-emitter/project-dashboard/widget-interface'; import { FlatIssuesStore } from '@app/event-emitter/utils/flat-issues-store'; import { GetValueFromObjectByKey } from '@app/event-emitter/utils/get-value-from-object-by-key'; import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store'; import { Injectable, Logger } from '@nestjs/common'; import nano from 'nano'; import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer'; +import { WidgetDataLoaderInterface } from '@app/event-emitter/dashboards/widget-data-loader-interface'; +import { + AppError, + Result, + createAppError, + success, +} from '@app/event-emitter/utils/result'; export namespace IssuesByTagsWidgetNs { export type Params = { @@ -27,10 +33,10 @@ export namespace IssuesByTagsWidgetNs { type Params = IssuesByTagsWidgetNs.Params; @Injectable() -export class IssuesByTagsWidgetService - implements WidgetInterface +export class IssuesByTagsWidgetDataLoaderService + implements WidgetDataLoaderInterface { - private logger = new Logger(IssuesByTagsWidgetService.name); + private logger = new Logger(IssuesByTagsWidgetDataLoaderService.name); private issuesLoader: IssuesServiceNs.IssuesLoader; constructor( @@ -45,7 +51,7 @@ export class IssuesByTagsWidgetService return true; } - async render(widgetParams: Params): Promise { + async load(widgetParams: Params): Promise> { let store: FlatIssuesStore; if (widgetParams.fromRootIssueId) { store = await this.getListFromRoot(widgetParams.fromRootIssueId); @@ -54,7 +60,7 @@ export class IssuesByTagsWidgetService } else { const errMsg = `Wrong widgetParams value`; this.logger.error(errMsg); - throw new Error(errMsg); + return fail(createAppError(errMsg)); } await store.enhanceIssues([ this.timePassedHighlightEnhancer, @@ -100,7 +106,7 @@ export class IssuesByTagsWidgetService return a.metainfo.title.localeCompare(b.metainfo.title); }); } - return res; + return success(res); } private async getListFromRoot(issueId: number): Promise {