Добавлена выгрузка по запросу текущих задач для проекта eccm через telegram
This commit is contained in:
parent
c3213ccaa2
commit
ebd630b25a
4 changed files with 276 additions and 0 deletions
|
|
@ -23,6 +23,8 @@ import { UserMetaInfoService } from './user-meta-info/user-meta-info.service';
|
|||
import { UserMetaInfo } from './couchdb-datasources/user-meta-info';
|
||||
import { PersonalNotificationAdapterService } from './notifications/adapters/personal-notification.adapter/personal-notification.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({
|
||||
imports: [
|
||||
|
|
@ -49,6 +51,8 @@ import { StatusChangeAdapterService } from './notifications/adapters/status-chan
|
|||
UserMetaInfo,
|
||||
PersonalNotificationAdapterService,
|
||||
StatusChangeAdapterService,
|
||||
CurrentIssuesEccmReportService,
|
||||
CurrentIssuesBotHandlerService,
|
||||
],
|
||||
})
|
||||
export class AppModule implements OnModuleInit {
|
||||
|
|
|
|||
223
src/reports/current-issues-eccm.report.service.ts
Normal file
223
src/reports/current-issues-eccm.report.service.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import TelegramBot from 'node-telegram-bot-api';
|
|||
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
|
||||
import axios from 'axios';
|
||||
import { UserMetaInfoModel } from 'src/models/user-meta-info.model';
|
||||
import { CurrentIssuesBotHandlerService } from './handlers/current-issues.bot-handler.service';
|
||||
|
||||
@Injectable()
|
||||
export class TelegramBotService {
|
||||
|
|
@ -19,6 +20,7 @@ export class TelegramBotService {
|
|||
private userMetaInfoService: UserMetaInfoService,
|
||||
private usersService: UsersService,
|
||||
private configService: ConfigService,
|
||||
private currentIssuesBotHandlerService: CurrentIssuesBotHandlerService,
|
||||
) {
|
||||
this.telegramBotToken = this.configService.get<string>('telegramBotToken');
|
||||
this.redminePublicUrlPrefix =
|
||||
|
|
@ -41,6 +43,7 @@ export class TelegramBotService {
|
|||
this.bot.onText(/\/leave/, async (msg) => {
|
||||
await this.leave(msg);
|
||||
});
|
||||
this.currentIssuesBotHandlerService.init(this, this.bot);
|
||||
}
|
||||
|
||||
private async showHelpMessage(msg: TelegramBot.Message): Promise<void> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue