Добавлена выгрузка по запросу текущих задач для проекта 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 { 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 {
|
||||||
|
|
|
||||||
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 { 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> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue