From 9b17d703ed694ec1e021d35c1c769035a62dd5e2 Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Thu, 15 Dec 2022 19:18:31 +0700 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=20?= =?UTF-8?q?=D0=B2=20=D0=BE=D1=82=D1=87=D1=91=D1=82=D0=B5=20=D0=B4=D0=B5?= =?UTF-8?q?=D0=B9=D0=BB=D0=B8=20=D1=81=D0=BE=20=D1=81=D1=81=D1=8B=D0=BB?= =?UTF-8?q?=D0=BA=D0=B0=D0=BC=D0=B8=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B8=20=D0=B2=20redmine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 18 +++++ .../redmine-public-url.converter.ts | 46 +++++++++++++ .../daily-eccm-with-extra-data.service.ts | 43 ++++++++++++ src/reports/daily-eccm.report.controller.ts | 21 +++++- ...y-eccm-user-comment.bot-handler.service.ts | 69 +++++++++++-------- tsconfig.json | 2 +- views/daily-eccm-report-extended.hbs | 57 +++++++++++++++ 7 files changed, 225 insertions(+), 31 deletions(-) create mode 100644 src/reports/daily-eccm-with-extra-data.service.ts create mode 100644 views/daily-eccm-report-extended.hbs diff --git a/src/app.module.ts b/src/app.module.ts index 5f24362..6f3a43d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -35,6 +35,7 @@ import { DailyEccmReportTask } from './reports/daily-eccm.report.task'; import { DailyEccmReportsUserCommentsDatasource } from './couchdb-datasources/daily-eccm-reports-user-comments.datasource'; import { DailyEccmUserCommentsService } from './reports/daily-eccm-user-comments.service'; import { SetDailyEccmUserCommentBotHandlerService } from './telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service'; +import { DailyEccmWithExtraDataService } from './reports/daily-eccm-with-extra-data.service'; @Module({ imports: [ @@ -76,6 +77,7 @@ import { SetDailyEccmUserCommentBotHandlerService } from './telegram-bot/handler DailyEccmReportsUserCommentsDatasource, DailyEccmUserCommentsService, SetDailyEccmUserCommentBotHandlerService, + DailyEccmWithExtraDataService, ], }) export class AppModule implements OnModuleInit { @@ -93,6 +95,8 @@ export class AppModule implements OnModuleInit { private telegramBotService: TelegramBotService, private personalNotificationAdapterService: PersonalNotificationAdapterService, private statusChangeAdapterService: StatusChangeAdapterService, + private dailyEccmUserCommentsService: DailyEccmUserCommentsService, + private setDailyEccmUserCommentBotHandlerService: SetDailyEccmUserCommentBotHandlerService, ) {} onModuleInit() { @@ -165,5 +169,19 @@ export class AppModule implements OnModuleInit { `Save result process success finished, issue_id = ${args.saveResult.current.id}`, ); }); + + this.initDailyEccmUserCommentsPipeline(); + } + + private initDailyEccmUserCommentsPipeline(): void { + this.setDailyEccmUserCommentBotHandlerService.$messages.subscribe( + (data) => { + this.dailyEccmUserCommentsService.setComment( + data.userId, + data.date, + data.comment, + ); + }, + ); } } diff --git a/src/converters/redmine-public-url.converter.ts b/src/converters/redmine-public-url.converter.ts index da7b4d2..245d979 100644 --- a/src/converters/redmine-public-url.converter.ts +++ b/src/converters/redmine-public-url.converter.ts @@ -2,6 +2,8 @@ import { RedmineTypes } from '@app/event-emitter/models/redmine-types'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +// TODO: Этот сервис возможно перенести в lib event-emitter + @Injectable() export class RedminePublicUrlConverter { private redminePublicUrlPrefix: string; @@ -24,4 +26,48 @@ export class RedminePublicUrlConverter { const url = this.getUrl(issue.id); return `${issue.tracker.name} #${issue.id}`; } + + getMinHtmlHref(issueId: number | string): string { + const url = this.getUrl(issueId); + return `#${issueId}`; + } + + /** + * Обогащение текста с идентификаторами задач html ссылками на эти задачи + * + * Например текст `"Эта ошибка будет решена в рамках задач #123 и #456"` + * будет заменён на `"Эта ошибка будет решена в рамках задач + * #123 и + * #456"` + * + * @param text + * @param linkGenerator функция замены отдельного идентификатора на html ссылку. По умолчанию + * будет использоваться собственная функция this.getMinHtmlHref + * @see convert + */ + enrichTextWithIssues( + text: string, + linkGenerator?: (issueId: number | string) => string, + ): string { + const generator = linkGenerator + ? linkGenerator + : (issueId) => this.getMinHtmlHref(issueId); + + const regexp = /^\d+/; + + const parts = text.split('#'); + for (let i = 0; i < parts.length; i++) { + let part = parts[i]; + const match = part.match(regexp); + if (!match) { + continue; + } + const issueId = match[0]; + const replacment = generator(issueId); + part = part.replace(new RegExp(`^${issueId}`), replacment); + parts[i] = part; + } + + return parts.join(''); + } } diff --git a/src/reports/daily-eccm-with-extra-data.service.ts b/src/reports/daily-eccm-with-extra-data.service.ts new file mode 100644 index 0000000..c798a4c --- /dev/null +++ b/src/reports/daily-eccm-with-extra-data.service.ts @@ -0,0 +1,43 @@ +import { Timestamped } from '@app/event-emitter/models/timestamped'; +import { Injectable } from '@nestjs/common'; +import nano from 'nano'; +import { RedminePublicUrlConverter } from 'src/converters/redmine-public-url.converter'; +import { DailyEccmUserCommentsService } from './daily-eccm-user-comments.service'; +import { + DailyEccmReport, + DailyEccmReportService, +} from './daily-eccm.report.service'; + +@Injectable() +export class DailyEccmWithExtraDataService { + constructor( + private dailyEccmReportService: DailyEccmReportService, + private dailyEccmUserCommentsService: DailyEccmUserCommentsService, + private redminePublicUrlConverter: RedminePublicUrlConverter, + ) {} + + async loadReport( + name: string, + ): Promise< + | (DailyEccmReport.Models.Report & nano.DocumentGetResponse & Timestamped) + | null + > { + const baseReportData = await this.dailyEccmReportService.loadReport(name); + if (!baseReportData) return null; + const userIds = baseReportData.byUsers.map((item) => item.user.id); + const userComments = await this.dailyEccmUserCommentsService.loadComments( + userIds, + name, + ); + for (let i = 0; i < baseReportData.byUsers.length; i++) { + const byUser = baseReportData.byUsers[i]; + if (userComments[byUser.user.id]) { + byUser.dailyMessage = + this.redminePublicUrlConverter.enrichTextWithIssues( + userComments[byUser.user.id], + ); + } + } + return baseReportData; + } +} diff --git a/src/reports/daily-eccm.report.controller.ts b/src/reports/daily-eccm.report.controller.ts index 3c9f971..1c05b14 100644 --- a/src/reports/daily-eccm.report.controller.ts +++ b/src/reports/daily-eccm.report.controller.ts @@ -1,4 +1,5 @@ import { Controller, Get, Param, Query, Render } from '@nestjs/common'; +import { DailyEccmWithExtraDataService } from './daily-eccm-with-extra-data.service'; import { DailyEccmReport, DailyEccmReportService, @@ -6,7 +7,10 @@ import { @Controller('daily-eccm') export class DailyEccmReportController { - constructor(private dailyEccmReportService: DailyEccmReportService) {} + constructor( + private dailyEccmReportService: DailyEccmReportService, + private dailyEccmWithExtraDataService: DailyEccmWithExtraDataService, + ) {} @Get() @Render('daily-eccm-report') @@ -67,4 +71,19 @@ export class DailyEccmReportController { return data; } } + + @Get('/load/:name/extended/raw') + async loadExtendedReportRawData( + @Param('name') name: string, + ): Promise { + return await this.dailyEccmWithExtraDataService.loadReport(name); + } + + @Get('/load/:name/extended') + @Render('daily-eccm-report-extended') + async loadExtendedReport( + @Param('name') name: string, + ): Promise { + return await this.dailyEccmWithExtraDataService.loadReport(name); + } } diff --git a/src/telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service.ts b/src/telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service.ts index ba8ab6f..3a188b2 100644 --- a/src/telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service.ts +++ b/src/telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service.ts @@ -25,7 +25,6 @@ export class SetDailyEccmUserCommentBotHandlerService private service: TelegramBotService; private regexp = /\/set_daily_eccm_user_comment (.+)/; private logger = new Logger(SetDailyEccmUserCommentBotHandlerService.name); - private dateParamRegexp = /^date=([\d\-]+) (.+)$/; $messages = new Subject(); @@ -43,46 +42,58 @@ export class SetDailyEccmUserCommentBotHandlerService msg.chat.id, ); const redmineUserId = userMetaInfo.user_id; - const data = this.parseDate(msg.text); - this.logger.debug( - `Setting user comment for daily eccm ` + - `by redmineUserId = ${redmineUserId}, ` + - `full text - ${msg.text}, ` + - `parsed data - ${JSON.stringify(data)}`, - ); - this.$messages.next({ - userId: redmineUserId, - date: data.date, - comment: data.msg, - }); - return; + const data = this.parseData(msg.text); + if (data) { + this.logger.debug( + `Setting user comment for daily eccm ` + + `by redmineUserId = ${redmineUserId}, ` + + `full text - ${msg.text}, ` + + `parsed data - ${JSON.stringify(data)}`, + ); + this.$messages.next({ + userId: redmineUserId, + date: data.date, + comment: data.msg, + }); + } else { + this.logger.error( + `For some reason, it was not possible to get data from an incoming message - ${msg.text}`, + ); + } }); } getHelpMsg(): string { return ( `/set_daily_eccm_user_comment ` + - `[дата=yyyy-MM-dd] <комментарий> ` + + `[date=yyyy-MM-dd] <комментарий> ` + `- дополнительный комментарий для дейли` ); } - private parseDate(src: string): { date: string; msg: string } | null { - let msgWithoutCommand: any = src.match(this.regexp); - if (!msgWithoutCommand || !msgWithoutCommand[1]) { + private parseData(src: string): { date: string; msg: string } | null { + let text = src; + + text = text.replace('/set_daily_eccm_user_comment', '').trim(); + if (!text) { return null; } - msgWithoutCommand = msgWithoutCommand[1]; - const msgWithDate: any = msgWithoutCommand.match(this.dateParamRegexp); - let date: any = ''; - if (msgWithDate && msgWithDate[1] && msgWithDate[2]) { - date = msgWithDate[1]; - if (DateTime.fromFormat(date, ISO_DATE_FORMAT).isValid) { - msgWithoutCommand = msgWithDate[2]; - } else { - date = ''; - } + + const dateMatch = text.match(/^date=[\d-]+/); + if (!dateMatch) { + return { date: DateTime.now().toFormat(ISO_DATE_FORMAT), msg: text }; } - return { date: date, msg: msgWithoutCommand }; + + const datePart = dateMatch[0]; + let dateRaw = datePart.replace('date=', ''); + text = text.replace('date=', '').trim(); + const date = DateTime.fromFormat(dateRaw, ISO_DATE_FORMAT); + if (!date.isValid) { + this.logger.error(`Wrong date in message - ${src}`); + return null; + } + text = text.replace(dateRaw, '').trim(); + dateRaw = date.toFormat(ISO_DATE_FORMAT); + return { date: dateRaw, msg: text }; } } diff --git a/tsconfig.json b/tsconfig.json index 85879f4..0408fb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", + "target": "es2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", diff --git a/views/daily-eccm-report-extended.hbs b/views/daily-eccm-report-extended.hbs new file mode 100644 index 0000000..ec346da --- /dev/null +++ b/views/daily-eccm-report-extended.hbs @@ -0,0 +1,57 @@ + + + + + Daily Eccm Report + + +
+ Параметры отчёта +
    +
  • От - {{this.params.from}}
  • +
  • До - {{this.params.to}}
  • +
  • Имя отчёта - {{this.params.name}}
  • +
  • Имя проекта - {{this.params.project}}
  • +
  • Версии - {{this.params.versions}}
  • +
+
+

Отчёт по работникам

+ {{#each this.byUsers}} + +

{{this.user.firstname}} {{this.user.lastname}}

+ + {{#if this.dailyMessage}} +

Комментарий

+
{{{this.dailyMessage}}}
+ {{/if}} + +

Текущие задачи

+
    + {{#each this.issuesGroupedByStatus}} +
  • + {{this.status.name}}: +
      + {{#each this.issues}} +
    • {{>redmineIssueAHref issue=this.issue}} (приоритет {{this.issue.priority.name}}; версия {{this.issue.fixed_version.name}}) - {{this.issue.subject}}
    • + {{/each}} +
    +
  • + {{/each}} +
+ +

Активности за период

+
    + {{#each this.activities}} +
  • + {{>redmineIssueAHref issue=this.issue}} (приоритет {{this.issue.priority.name}}; версия {{this.issue.fixed_version.name}}; статус {{this.issue.status.name}}) - {{this.issue.subject}} +
      + {{#each this.changes}} +
    • {{this.created_on}}: {{this.change_message}}
    • + {{/each}} +
    +
  • + {{/each}} +
+ {{/each}} + + \ No newline at end of file