From 2cf5ccf11802e92f2b420a41ca480fa55ebd4912 Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Tue, 19 Jul 2022 18:00:38 +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=D1=8B=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event-emitter/src/event-emitter.module.ts | 40 +++++++++++- .../issue-enhancer-interface.ts | 6 +- .../issue-enhancers/timestamps-enhancer.ts | 10 ++- .../event-emitter/src/models/module-params.ts | 2 + src/app.module.ts | 13 +++- src/issue-enhancers/custom-fields-enhancer.ts | 65 +++++++++++++++++++ 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 src/issue-enhancers/custom-fields-enhancer.ts diff --git a/libs/event-emitter/src/event-emitter.module.ts b/libs/event-emitter/src/event-emitter.module.ts index ced2c9b..a2ed0f0 100644 --- a/libs/event-emitter/src/event-emitter.module.ts +++ b/libs/event-emitter/src/event-emitter.module.ts @@ -1,4 +1,10 @@ -import { DynamicModule, Logger, Module, OnModuleInit } from '@nestjs/common'; +import { + DynamicModule, + Inject, + Logger, + Module, + OnModuleInit, +} from '@nestjs/common'; import { EventEmitterService } from './event-emitter.service'; import { RedmineEventsGateway } from './events/redmine-events.gateway'; import { ServeStaticModule } from '@nestjs/serve-static'; @@ -18,6 +24,9 @@ import { RedmineTypes } from './models/redmine-types'; import { UsersService } from './users/users.service'; import { IssuesService } from './issues/issues.service'; import { IssuesController } from './issues/issues.controller'; +import { IssueEnhancerInterface } from './issue-enhancers/issue-enhancer-interface'; +import { TimestampEnhancer } from './issue-enhancers/timestamps-enhancer'; +import { ModuleRef } from '@nestjs/core'; @Module({}) export class EventEmitterModule implements OnModuleInit { @@ -41,6 +50,11 @@ export class EventEmitterModule implements OnModuleInit { RedmineUserCacheWriterService, UsersService, IssuesService, + TimestampEnhancer, + { + provide: 'ENHANCERS', + useValue: params?.enhancers || null, + }, ], exports: [ EventEmitterService, @@ -53,6 +67,7 @@ export class EventEmitterModule implements OnModuleInit { RedmineUserCacheWriterService, UsersService, IssuesService, + TimestampEnhancer, ], controllers: [MainController, UsersController, IssuesController], }; @@ -63,7 +78,12 @@ export class EventEmitterModule implements OnModuleInit { constructor( private redmineEventsGateway: RedmineEventsGateway, private redmineIssuesCacheWriterService: RedmineIssuesCacheWriterService, - ) {} + private moduleRef: ModuleRef, + @Inject('ENHANCERS') + private enhancers: IssueEnhancerInterface[], + ) { + this.enhancers.forEach((e) => e.init(this.moduleRef)); + } onModuleInit() { const queue = this.redmineEventsGateway.getIssuesChangesQueue(); @@ -72,7 +92,7 @@ export class EventEmitterModule implements OnModuleInit { this.logger.debug(`Changed issues = ${JSON.stringify(issues)}`); for (let i = 0; i < issues.length; i++) { - const issue: RedmineTypes.Issue = issues[i]; + const issue: RedmineTypes.Issue = await this.enhanceIssue(issues[i]); try { this.logger.debug( @@ -94,4 +114,18 @@ export class EventEmitterModule implements OnModuleInit { } }); } + + private async enhanceIssue( + issue: RedmineTypes.Issue & Record, + ): Promise> { + const enhancers = this.enhancers; + for (let i = 0; i < enhancers.length; i++) { + const enhancer = enhancers[i]; + issue = await enhancer.enhance(issue); + this.logger.debug( + `Issue after enhancer: issue = ${JSON.stringify(issue)}`, + ); + } + return issue; + } } diff --git a/libs/event-emitter/src/issue-enhancers/issue-enhancer-interface.ts b/libs/event-emitter/src/issue-enhancers/issue-enhancer-interface.ts index 3a55dfd..6e3f600 100644 --- a/libs/event-emitter/src/issue-enhancers/issue-enhancer-interface.ts +++ b/libs/event-emitter/src/issue-enhancers/issue-enhancer-interface.ts @@ -1,5 +1,9 @@ +import { ModuleRef } from '@nestjs/core'; import { RedmineTypes } from '../models/redmine-types'; export interface IssueEnhancerInterface { - enhance(issue: RedmineTypes.Issue): RedmineTypes.Issue & Record; + init(moduleRef: ModuleRef); + enhance( + issue: RedmineTypes.Issue, + ): Promise>; } diff --git a/libs/event-emitter/src/issue-enhancers/timestamps-enhancer.ts b/libs/event-emitter/src/issue-enhancers/timestamps-enhancer.ts index 602fbe7..b4e14b8 100644 --- a/libs/event-emitter/src/issue-enhancers/timestamps-enhancer.ts +++ b/libs/event-emitter/src/issue-enhancers/timestamps-enhancer.ts @@ -1,8 +1,16 @@ +import { Injectable } from '@nestjs/common'; import { RedmineTypes } from '../models/redmine-types'; import { IssueEnhancerInterface } from './issue-enhancer-interface'; +@Injectable() export class TimestampEnhancer implements IssueEnhancerInterface { - enhance(issue: RedmineTypes.Issue): RedmineTypes.Issue & Record { + init() { + return; + } + + async enhance( + issue: RedmineTypes.Issue, + ): Promise> { return this.createTimestamp(this.enhanceJournal(issue), [ 'closed_on', 'created_on', diff --git a/libs/event-emitter/src/models/module-params.ts b/libs/event-emitter/src/models/module-params.ts index 2eafb13..bf1fd0a 100644 --- a/libs/event-emitter/src/models/module-params.ts +++ b/libs/event-emitter/src/models/module-params.ts @@ -1,5 +1,7 @@ +import { IssueEnhancerInterface } from '../issue-enhancers/issue-enhancer-interface'; import { MainConfigModel } from './main-config-model'; export type ModuleParams = { config?: MainConfigModel; + enhancers?: IssueEnhancerInterface[]; }; diff --git a/src/app.module.ts b/src/app.module.ts index 30b982f..7bc95a5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,19 +1,28 @@ import { EventEmitterModule } from '@app/event-emitter'; +import { TimestampEnhancer } from '@app/event-emitter/issue-enhancers/timestamps-enhancer'; import { MainController } from '@app/event-emitter/main/main.controller'; +import { UsersService } from '@app/event-emitter/users/users.service'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import configuration from './configs/app'; +import { CustomFieldsEnhancer } from './issue-enhancers/custom-fields-enhancer'; @Module({ imports: [ EventEmitterModule.register({ config: configuration().redmineIssueEventEmitterConfig, + enhancers: [new TimestampEnhancer(), new CustomFieldsEnhancer()], }), ConfigModule.forRoot({ load: [configuration] }), ], controllers: [AppController, MainController], - providers: [AppService], + providers: [AppService, UsersService, CustomFieldsEnhancer], }) -export class AppModule {} +export class AppModule { + constructor( + private timestampEnhancer: TimestampEnhancer, + private customFieldsEnhancer: CustomFieldsEnhancer, + ) {} +} diff --git a/src/issue-enhancers/custom-fields-enhancer.ts b/src/issue-enhancers/custom-fields-enhancer.ts new file mode 100644 index 0000000..fbf7ce8 --- /dev/null +++ b/src/issue-enhancers/custom-fields-enhancer.ts @@ -0,0 +1,65 @@ +import { IssueEnhancerInterface } from '@app/event-emitter/issue-enhancers/issue-enhancer-interface'; +import { RedmineTypes } from '@app/event-emitter/models/redmine-types'; +import { UsersService } from '@app/event-emitter/users/users.service'; +import { Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +@Injectable() +export class CustomFieldsEnhancer implements IssueEnhancerInterface { + private usersService: UsersService; + + init(moduleRef: ModuleRef): void { + this.usersService = moduleRef.get(UsersService); + } + + async enhance( + issue: RedmineTypes.Issue, + ): Promise> { + const res: RedmineTypes.Issue & Record = { ...issue }; + + const customFields = issue.custom_fields; + if (!customFields) { + return issue; + } + + const qa = customFields.find( + (cf) => cf.name === 'Quality Assurance' || cf.name === 'QA', + ); + if (qa && this.isNumber(qa.value)) { + const qaUserId = Number(qa.value); + const qaUser = await this.usersService.getUser(qaUserId); + if (qaUser) { + res.qa = { ...qaUser }; + } + } + + const cr = customFields.find( + (cf) => cf.name === 'Code Reviewer' || cf.name === 'CR', + ); + if (cr && this.isNumber(cr.value)) { + const crUserId = Number(cr.value); + const crUser = await this.usersService.getUser(crUserId); + if (crUser) { + res.cr = { ...crUser }; + } + } + + const tags = customFields.find((cf) => cf.name === 'Tags'); + if (tags && tags.value) { + res.tags = tags.value.split(',; '); + } + + const sp = customFields.find( + (cf) => cf.name === 'Story Points' || cf.name === 'SP', + ); + if (sp && this.isNumber(sp.value)) { + res.sp = Number(sp.value); + } + + return res; + } + + private isNumber(value): boolean { + return !Number.isNaN(value) && Number.isFinite(Number(value)); + } +}