diff --git a/src/app.module.ts b/src/app.module.ts index 05dd508..5ae2661 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -37,6 +37,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'; @Module({ imports: [ @@ -79,6 +80,7 @@ import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-e DailyEccmUserCommentsService, SetDailyEccmUserCommentBotHandlerService, DailyEccmWithExtraDataService, + IssuesByTagsWidgetService, ], }) export class AppModule implements OnModuleInit { diff --git a/src/dashboards/simple-kanban-board.controller.ts b/src/dashboards/simple-kanban-board.controller.ts index 09d7fc8..09c1961 100644 --- a/src/dashboards/simple-kanban-board.controller.ts +++ b/src/dashboards/simple-kanban-board.controller.ts @@ -5,6 +5,7 @@ import { RootIssueSubTreesWidgetService } from '@app/event-emitter/project-dashb import { Controller, Get, Param, Render } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { parse } from 'jsonc-parser'; +import { IssuesByTagsWidgetService } from './widgets/issues-by-tags.widget.service'; @Controller('simple-kanban-board') export class SimpleKanbanBoardController { @@ -16,6 +17,7 @@ export class SimpleKanbanBoardController { private configService: ConfigService, private listIssuesByUsersWidgetService: ListIssuesByUsersWidgetService, private listIssuesByUsersLikeJiraWidgetService: ListIssuesByUsersLikeJiraWidgetService, + private issuesByTagsWidgetService: IssuesByTagsWidgetService, ) { this.path = this.configService.get('simpleKanbanBoard.path'); } @@ -67,4 +69,20 @@ export class SimpleKanbanBoardController { async getByUsersLikeJira(@Param('name') name: string): Promise { return await this.getByUsersLikeJiraRawData(name); } + + @Get('/by-tags/:name/raw') + async getByTagsRawData(@Param('name') name: string): Promise { + const cfg = this.dynamicLoader.load(name, { + path: this.path, + ext: 'jsonc', + parser: parse, + }); + return await this.issuesByTagsWidgetService.render(cfg); + } + + @Get('/by-tags/:name') + @Render('simple-kanban-board') + async getByTags(@Param('name') name: string): Promise { + return await this.getByTagsRawData(name); + } } diff --git a/src/dashboards/widgets/issues-by-tags.widget.service.ts b/src/dashboards/widgets/issues-by-tags.widget.service.ts new file mode 100644 index 0000000..c199eb1 --- /dev/null +++ b/src/dashboards/widgets/issues-by-tags.widget.service.ts @@ -0,0 +1,129 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer'; +import { TagStyledEnhancerNs } from '@app/event-emitter/issue-enhancers/tag-styled-enhancer'; +import { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer'; +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'; + +export namespace IssuesByTagsWidgetNs { + export type Params = { + fromRootIssueId?: number; + fromQuery?: nano.MangoQuery; + tagsSort?: boolean; + showEmptyTags?: string; + statuses: string[]; + } & TagStyledEnhancerNs.ConfigWithTagsStyles; +} + +type Params = IssuesByTagsWidgetNs.Params; + +@Injectable() +export class IssuesByTagsWidgetService + implements WidgetInterface +{ + private logger = new Logger(IssuesByTagsWidgetService.name); + private issuesLoader: IssuesServiceNs.IssuesLoader; + + constructor( + private issuesService: IssuesService, + private timePassedHighlightEnhancer: TimePassedHighlightEnhancer, + private issueUrlEnhancer: IssueUrlEnhancer, + ) { + this.issuesLoader = this.issuesService.createDynamicIssuesLoader(); + } + + isMyConfig(): boolean { + return true; + } + + async render(widgetParams: Params): Promise { + 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(errMsg); + throw new Error(errMsg); + } + await store.enhanceIssues([ + this.timePassedHighlightEnhancer, + this.issueUrlEnhancer, + TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams), + ]); + const grouped = store.groupByStatusWithExtraToMultipleStories((issue) => { + if (!issue || !widgetParams.tags || !widgetParams.tags.tagsKeyName) { + return []; + } + const tagsResult = GetValueFromObjectByKey( + issue, + widgetParams.tags.tagsKeyName, + ); + if ( + (tagsResult.error == 'NOT_FOUND' || + (tagsResult.result && tagsResult.result.length <= 0)) && + widgetParams.showEmptyTags + ) { + return [widgetParams.showEmptyTags]; + } + if ( + typeof tagsResult.result !== 'object' || + tagsResult.result.length <= 0 + ) { + return []; + } + return tagsResult.result; + }, widgetParams.statuses); + let res = [] as any[]; + for (const tag in grouped) { + if (Object.prototype.hasOwnProperty.call(grouped, tag)) { + const data = grouped[tag]; + res.push({ + data: data, + metainfo: this.createMetaInfo(tag), + }); + } + } + if (widgetParams.tagsSort) { + res = res.sort((a, b) => { + return a.metainfo.title.localeCompare(b.metainfo.title); + }); + } + 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); + 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 createMetaInfo(tag: string): Record { + return { + title: tag, + }; + } +}