Добавлена выгрузка по запросу текущих задач для проекта eccm через telegram

This commit is contained in:
Pavel Gnedov 2022-10-14 11:00:50 +07:00
parent c3213ccaa2
commit ebd630b25a
4 changed files with 276 additions and 0 deletions

View file

@ -23,6 +23,8 @@ import { UserMetaInfoService } from './user-meta-info/user-meta-info.service';
import { UserMetaInfo } from './couchdb-datasources/user-meta-info'; import { UserMetaInfo } from './couchdb-datasources/user-meta-info';
import { PersonalNotificationAdapterService } from './notifications/adapters/personal-notification.adapter/personal-notification.adapter.service'; import { PersonalNotificationAdapterService } from './notifications/adapters/personal-notification.adapter/personal-notification.adapter.service';
import { StatusChangeAdapterService } from './notifications/adapters/status-change.adapter.service'; import { StatusChangeAdapterService } from './notifications/adapters/status-change.adapter.service';
import { CurrentIssuesEccmReportService } from './reports/current-issues-eccm.report.service';
import { CurrentIssuesBotHandlerService } from './telegram-bot/handlers/current-issues.bot-handler.service';
@Module({ @Module({
imports: [ imports: [
@ -49,6 +51,8 @@ import { StatusChangeAdapterService } from './notifications/adapters/status-chan
UserMetaInfo, UserMetaInfo,
PersonalNotificationAdapterService, PersonalNotificationAdapterService,
StatusChangeAdapterService, StatusChangeAdapterService,
CurrentIssuesEccmReportService,
CurrentIssuesBotHandlerService,
], ],
}) })
export class AppModule implements OnModuleInit { export class AppModule implements OnModuleInit {

View file

@ -0,0 +1,223 @@
/* eslint-disable prettier/prettier */
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Issues } from '@app/event-emitter/couchdb-datasources/issues';
import { UNLIMITED } from '@app/event-emitter/consts/consts';
import { RedmineTypes } from '@app/event-emitter/models/redmine-types';
import nano from 'nano';
import Handlebars from 'handlebars';
import { RedminePublicUrlConverter } from 'src/converters/redmine-public-url.converter';
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace CurrentIssuesEccmReport {
export type Options = {
statuses?: string[];
versions?: string[];
userIds?: number[];
project?: string;
};
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Defaults {
export const statuses = [
'In Progress',
'Feedback',
'Re-opened',
'Code Review',
'Resolved',
'Testing',
];
export const projectName = 'ECCM';
}
export type User = {
id: number;
firstname: string;
lastname: string;
};
export type Status = {
id: number;
name: string;
};
export type IssuesAndStatus = {
status: Status;
issues: RedmineTypes.Issue[];
};
export type UserReport = {
user: User;
issuesGroupedByStatus: IssuesAndStatus[];
};
export class UsersReport {
private report: UserReport[] = [];
private reportFormatter: HandlebarsTemplateDelegate<UserReport[]>;
private reportTemplate = [
// context - this.report: UserReport[]
'{{#each this}}',
// context - UserReport
'{{user.firstname}} {{user.lastname}}:',
'',
'{{#each issuesGroupedByStatus}}',
// context - IssuesAndStatus
'{{status.name}}:',
'{{#each issues}}',
// context - RedmineTypes.Issue
` - {{>redmineIssueAHref issue=.}}: {{subject}} (прио - {{priority.name}}, версия - {{fixed_version.name}})`,
'{{/each}}',
'{{/each}}',
'',
'{{/each}}',
].join('\n');
constructor(
private redminePublicUrlConverter: RedminePublicUrlConverter,
private redminePublicUrl: string,
) {
this.reportFormatter = Handlebars.compile(this.reportTemplate);
Handlebars.registerPartial(
'redmineIssueAHref',
`<a href="${this.redminePublicUrl}/issues/{{issue.id}}">{{issue.tracker.name}} #{{issue.id}}</a>`,
);
}
push(item: any): void {
const user: User = {
id: item.current_user.id,
firstname: item.current_user.firstname,
lastname: item.current_user.lastname,
};
const status: Status = {
id: item.status.id,
name: item.status.name,
};
const issuesAndStatus = this.getOrCreateIssuesAndStatus(user, status);
issuesAndStatus.issues.push(item);
}
getOrCreateUserReport(user: User): UserReport {
let userReport: UserReport;
userReport = this.report.find((r) => r.user.id == user.id);
if (!userReport) {
userReport = {
user: user,
issuesGroupedByStatus: [],
};
this.report.push(userReport);
}
return userReport;
}
getOrCreateIssuesAndStatus(user: User, status: Status): IssuesAndStatus {
const userReport = this.getOrCreateUserReport(user);
let issuesAndStatus: IssuesAndStatus;
issuesAndStatus = userReport.issuesGroupedByStatus.find(
(i) => i.status.id == status.id,
);
if (!issuesAndStatus) {
issuesAndStatus = {
issues: [],
status: status,
};
userReport.issuesGroupedByStatus.push(issuesAndStatus);
}
return issuesAndStatus;
}
getAllUsersReportByTemplate(): string {
return this.reportFormatter(this.report);
}
}
}
@Injectable()
export class CurrentIssuesEccmReportService {
private eccmVersions: string[];
private logger = new Logger(CurrentIssuesEccmReportService.name);
constructor(
private configService: ConfigService,
private issuesDatasource: Issues,
private redminePublicUrlConverter: RedminePublicUrlConverter,
) {
this.eccmVersions = this.configService.get<string[]>('redmineEccmVersions');
}
async getData(
options?: CurrentIssuesEccmReport.Options,
): Promise<RedmineTypes.Issue[]> {
const datasource = await this.issuesDatasource.getDatasource();
const query: nano.MangoQuery = {
selector: {
'status.name': {
$in: CurrentIssuesEccmReport.Defaults.statuses,
},
'fixed_version.name': {
$in: this.eccmVersions,
},
'project.name':
options?.project || CurrentIssuesEccmReport.Defaults.projectName,
},
fields: [
'id',
'tracker.name',
'status.id',
'status.name',
'priority.id',
'priority.name',
'fixed_version.name',
'subject',
'updated_on',
'updated_on_timestamp',
'current_user.id',
'current_user.firstname',
'current_user.lastname',
],
limit: UNLIMITED,
};
if (options && options.statuses) {
query.selector['status.name'] = {
$in: options.statuses,
};
}
if (options && options.versions) {
query.selector['fixed_version.name'] = {
$in: options.versions,
};
}
if (options && options.userIds) {
query.selector['current_user.id'] = {
$in: options.userIds,
};
} else {
query.selector['current_user.id'] = {
$exists: true,
};
}
this.logger.debug(`Query for get report data: ${JSON.stringify(query)}`);
const rawData = await datasource.find(query);
this.logger.debug(`Raw data for report: ${JSON.stringify(rawData)}`);
const data = rawData.docs as RedmineTypes.Issue[];
return data;
}
async getReport(options?: CurrentIssuesEccmReport.Options): Promise<string> {
this.logger.debug(`Options for report: ${JSON.stringify(options || null)}`);
const data = await this.getData(options);
this.logger.debug(`Data for report ${JSON.stringify(data)}`);
const report = new CurrentIssuesEccmReport.UsersReport(
this.redminePublicUrlConverter,
this.configService.get<string>('redmineUrlPublic'),
);
data.forEach((item) => report.push(item));
return report.getAllUsersReportByTemplate();
}
}

View file

@ -0,0 +1,46 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import TelegramBot from 'node-telegram-bot-api';
import { EccmConfig } from 'src/models/eccm-config.model';
import { CurrentIssuesEccmReportService } from 'src/reports/current-issues-eccm.report.service';
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
import { TelegramBotService } from '../telegram-bot.service';
@Injectable()
export class CurrentIssuesBotHandlerService {
private forName = /\/current_issues_eccm (.+) (.+)/;
private forCurrentUser = /\/current_issues_eccm/;
private service: TelegramBotService;
private eccmConfig: EccmConfig.Config;
private logger = new Logger(CurrentIssuesBotHandlerService.name);
constructor(
private configService: ConfigService,
private currentIssuesEccmReportService: CurrentIssuesEccmReportService,
private userMetaInfoService: UserMetaInfoService,
) {
this.eccmConfig = this.configService.get<EccmConfig.Config>('redmineEccm');
}
async init(service: TelegramBotService, bot: TelegramBot): Promise<void> {
if (!this.service) {
this.service = service;
}
bot.onText(/\/current_issues_eccm/, async (msg) => {
const userMetaInfo = await this.userMetaInfoService.findByTelegramId(
msg.chat.id,
);
const redmineUserId = userMetaInfo.user_id;
const report = await this.currentIssuesEccmReportService.getReport({
userIds: [redmineUserId],
project: this.eccmConfig.projectName,
versions: this.eccmConfig.currentVersions,
statuses: this.eccmConfig.currentIssuesStatuses,
});
this.logger.debug(`Current issues eccm report: ${report}`);
bot.sendMessage(msg.chat.id, report || 'empty report', {
parse_mode: 'HTML',
});
});
}
}

View file

@ -5,6 +5,7 @@ import TelegramBot from 'node-telegram-bot-api';
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service'; import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
import axios from 'axios'; import axios from 'axios';
import { UserMetaInfoModel } from 'src/models/user-meta-info.model'; import { UserMetaInfoModel } from 'src/models/user-meta-info.model';
import { CurrentIssuesBotHandlerService } from './handlers/current-issues.bot-handler.service';
@Injectable() @Injectable()
export class TelegramBotService { export class TelegramBotService {
@ -19,6 +20,7 @@ export class TelegramBotService {
private userMetaInfoService: UserMetaInfoService, private userMetaInfoService: UserMetaInfoService,
private usersService: UsersService, private usersService: UsersService,
private configService: ConfigService, private configService: ConfigService,
private currentIssuesBotHandlerService: CurrentIssuesBotHandlerService,
) { ) {
this.telegramBotToken = this.configService.get<string>('telegramBotToken'); this.telegramBotToken = this.configService.get<string>('telegramBotToken');
this.redminePublicUrlPrefix = this.redminePublicUrlPrefix =
@ -41,6 +43,7 @@ export class TelegramBotService {
this.bot.onText(/\/leave/, async (msg) => { this.bot.onText(/\/leave/, async (msg) => {
await this.leave(msg); await this.leave(msg);
}); });
this.currentIssuesBotHandlerService.init(this, this.bot);
} }
private async showHelpMessage(msg: TelegramBot.Message): Promise<void> { private async showHelpMessage(msg: TelegramBot.Message): Promise<void> {