From 4509a729cbf20a0e459d67bfb8b9578c8d0cd514 Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Thu, 9 Feb 2023 19:04:32 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B4=D0=BE=D1=81=D0=BA=D0=B0=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=20=D1=81=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D0=B0=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BD=D0=B8=D0=BA=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event-emitter/src/event-emitter.module.ts | 3 + .../list-issues-by-users.widget.service.ts | 127 ++++++++++++++++++ .../simple-kanban-board.controller.ts | 18 +++ views/simple-kanban-board.hbs | 2 +- 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts diff --git a/libs/event-emitter/src/event-emitter.module.ts b/libs/event-emitter/src/event-emitter.module.ts index abbd5f0..c2113c3 100644 --- a/libs/event-emitter/src/event-emitter.module.ts +++ b/libs/event-emitter/src/event-emitter.module.ts @@ -23,6 +23,7 @@ import { RootIssueSubTreesWidgetService } from './project-dashboard/widgets/root 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'; @Module({}) export class EventEmitterModule implements OnModuleInit { @@ -50,6 +51,7 @@ export class EventEmitterModule implements OnModuleInit { DynamicLoader, RedminePublicUrlConverter, IssueUrlEnhancer, + ListIssuesByUsersWidgetService, ], exports: [ EventEmitterService, @@ -69,6 +71,7 @@ export class EventEmitterModule implements OnModuleInit { DynamicLoader, RedminePublicUrlConverter, IssueUrlEnhancer, + ListIssuesByUsersWidgetService, ], controllers: [MainController, UsersController, IssuesController], }; diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts new file mode 100644 index 0000000..dd380e1 --- /dev/null +++ b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts @@ -0,0 +1,127 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +import { IssuesService } from '@app/event-emitter/issues/issues.service'; +import { RedmineTypes } from '@app/event-emitter/models/redmine-types'; +import { FlatIssuesStore } from '@app/event-emitter/utils/flat-issues-store'; +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'; + +export namespace ListIssuesByUsersWidgetNs { + export namespace Models { + export type Params = { + fromRootIssueId?: number; + fromQuery?: nano.MangoQuery; + userKey: string; + statuses: string[]; + }; + + export type FindResult = { + result?: any; + error?: FindErrors; + }; + + export enum FindErrors { + NOT_FOUND = 'NOT_FOUND', + } + } +} + +type Params = ListIssuesByUsersWidgetNs.Models.Params; +type ExtendedIssue = RedmineTypes.Issue & Record; +type FindResult = ListIssuesByUsersWidgetNs.Models.FindResult; + +@Injectable() +export class ListIssuesByUsersWidgetService + implements WidgetInterface +{ + private logger = new Logger(ListIssuesByUsersWidgetService.name); + + constructor(private issuesService: IssuesService) { + return; + } + + isMyConfig(): boolean { + return true; + } + + async render(widgetParams: Params): Promise { + // if (widgetParams) + let store: FlatIssuesStore; + if (widgetParams.fromRootIssueId) { + store = await this.getListFromRoot(widgetParams.fromRootIssueId); + } else if (widgetParams.fromQuery) { + store = await this.getListByQuery(widgetParams.fromQuery); + } else { + const errMsg = `Wrong widgetParams value`; + this.logger.error(`Wrong widgetParams value`); + throw new Error(errMsg); + } + const grouped = store.groupByStatusWithExtra((issue) => { + const res = this.getUserValueByKey(issue, widgetParams.userKey); + return res.result || 'Unknown'; + }, widgetParams.statuses); + const res = [] as any[]; + for (const user in grouped) { + if (Object.prototype.hasOwnProperty.call(grouped, user)) { + const data = grouped[user]; + res.push({ + data: data, + metainfo: this.createMetaInfo(user), + }); + } + } + return res; + } + + private async getListFromRoot(issueId: number): Promise { + const treeStore = new TreeIssuesStore(); + const rootIssue = await this.issuesService.getIssue(issueId); + treeStore.setRootIssue(rootIssue); + await treeStore.fillData(this.issuesLoader.bind(this)); + return treeStore.getFlatStore(); + } + + private async getListByQuery( + query: nano.MangoQuery, + ): Promise { + const rawData = await this.issuesService.find(query); + const store = new FlatIssuesStore(); + for (let i = 0; i < rawData.length; i++) { + const issue = rawData[i]; + store.push(issue); + } + return store; + } + + private async issuesLoader( + ids: number[], + ): Promise> { + const issues = await this.issuesService.getIssues(ids); + const res = {} as Record; + for (let i = 0; i < issues.length; i++) { + const issue = issues[i]; + res[issue.id] = issue; + } + return res; + } + + private getUserValueByKey(issue: ExtendedIssue, key: string): FindResult { + const keys = key.split('.'); + let res: any = issue; + for (let i = 0; i < keys.length; i++) { + const k = keys[i]; + if (!res.hasOwnProperty(k)) { + return { error: ListIssuesByUsersWidgetNs.Models.FindErrors.NOT_FOUND }; + } + res = res[k]; + } + return { result: res }; + } + + private createMetaInfo(user: string): Record { + return { + title: user, + }; + } +} diff --git a/src/dashboards/simple-kanban-board.controller.ts b/src/dashboards/simple-kanban-board.controller.ts index 51309a9..f8fb993 100644 --- a/src/dashboards/simple-kanban-board.controller.ts +++ b/src/dashboards/simple-kanban-board.controller.ts @@ -1,4 +1,5 @@ import { DynamicLoader } from '@app/event-emitter/configs/dynamic-loader'; +import { ListIssuesByUsersWidgetService } from '@app/event-emitter/project-dashboard/widgets/list-issues-by-users.widget.service'; import { RootIssueSubTreesWidgetService } from '@app/event-emitter/project-dashboard/widgets/root-issue-subtrees.widget.service'; import { Controller, Get, Param, Render } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @@ -12,6 +13,7 @@ export class SimpleKanbanBoardController { private rootIssueSubTreesWidgetService: RootIssueSubTreesWidgetService, private dynamicLoader: DynamicLoader, private configService: ConfigService, + private listIssuesByUsersWidgetService: ListIssuesByUsersWidgetService, ) { this.path = this.configService.get('simpleKanbanBoard.path'); } @@ -31,4 +33,20 @@ export class SimpleKanbanBoardController { async get(@Param('name') name: string): Promise { return await this.getRawData(name); } + + @Get('/by-users/:name/raw') + async getByUsersRawData(@Param('name') name: string): Promise { + const cfg = this.dynamicLoader.load(name, { + path: this.path, + ext: 'jsonc', + parser: parse, + }); + return await this.listIssuesByUsersWidgetService.render(cfg); + } + + @Get('/by-users/:name') + @Render('simple-kanban-board') + async getByUsers(@Param('name') name: string): Promise { + return await this.getByUsersRawData(name); + } } diff --git a/views/simple-kanban-board.hbs b/views/simple-kanban-board.hbs index d391d30..8688ffb 100644 --- a/views/simple-kanban-board.hbs +++ b/views/simple-kanban-board.hbs @@ -52,7 +52,7 @@
{{this.status}}
{{#each this.issues}}
-
{{this.tracker.name}} #{{this.id}} - {{this.subject}}
+
Исп.: {{this.current_user.name}}
Прогресс: {{this.done_ration}}
Трудозатраты: {{this.total_spent_hours}} / {{this.total_estimated_hours}}