Добавлено сохранение отчёта для дейли и загрузка
This commit is contained in:
parent
ffc6757b5e
commit
bfb6c29138
9 changed files with 234 additions and 34 deletions
|
|
@ -3,6 +3,7 @@
|
|||
"dbs": {
|
||||
"changes": "",
|
||||
"userMetaInfo": "",
|
||||
"eccmDailyReports": ""
|
||||
}
|
||||
},
|
||||
"telegramBotToken": "",
|
||||
|
|
|
|||
27
package-lock.json
generated
27
package-lock.json
generated
|
|
@ -21,6 +21,7 @@
|
|||
"handlebars": "^4.7.7",
|
||||
"hbs": "^4.2.0",
|
||||
"imap-simple": "^5.1.0",
|
||||
"luxon": "^3.1.0",
|
||||
"nano": "^10.0.0",
|
||||
"node-telegram-bot-api": "^0.59.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
"@types/cache-manager": "^4.0.1",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/luxon": "^3.1.0",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/node-telegram-bot-api": "^0.57.1",
|
||||
"@types/supertest": "^2.0.11",
|
||||
|
|
@ -2088,6 +2090,12 @@
|
|||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/luxon": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
||||
|
|
@ -7195,6 +7203,14 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.0.tgz",
|
||||
"integrity": "sha512-7w6hmKC0/aoWnEsmPCu5Br54BmbmUp5GfcqBxQngRcXJ+q5fdfjEzn7dxmJh2YdDhgW8PccYtlWKSv4tQkrTQg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/macos-release": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
|
||||
|
|
@ -11901,6 +11917,12 @@
|
|||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/luxon": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
||||
|
|
@ -15760,6 +15782,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz",
|
||||
"integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA=="
|
||||
},
|
||||
"luxon": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.0.tgz",
|
||||
"integrity": "sha512-7w6hmKC0/aoWnEsmPCu5Br54BmbmUp5GfcqBxQngRcXJ+q5fdfjEzn7dxmJh2YdDhgW8PccYtlWKSv4tQkrTQg=="
|
||||
},
|
||||
"macos-release": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"handlebars": "^4.7.7",
|
||||
"hbs": "^4.2.0",
|
||||
"imap-simple": "^5.1.0",
|
||||
"luxon": "^3.1.0",
|
||||
"nano": "^10.0.0",
|
||||
"node-telegram-bot-api": "^0.59.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
"@types/cache-manager": "^4.0.1",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/luxon": "^3.1.0",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/node-telegram-bot-api": "^0.57.1",
|
||||
"@types/supertest": "^2.0.11",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { CurrentIssuesEccmReportController } from './reports/current-issues-eccm
|
|||
import { DailyEccmReportController } from './reports/daily-eccm.report.controller';
|
||||
import { DailyEccmReportService } from './reports/daily-eccm.report.service';
|
||||
import { ChangesService } from './changes/changes.service';
|
||||
import { DailyEccmReportsDatasource } from './couchdb-datasources/daily-eccm-reports.datasource';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -64,6 +65,7 @@ import { ChangesService } from './changes/changes.service';
|
|||
CurrentIssuesEccmBotHandlerService,
|
||||
DailyEccmReportService,
|
||||
ChangesService,
|
||||
DailyEccmReportsDatasource,
|
||||
],
|
||||
})
|
||||
export class AppModule implements OnModuleInit {
|
||||
|
|
@ -88,6 +90,7 @@ export class AppModule implements OnModuleInit {
|
|||
Users.getDatasource();
|
||||
Changes.getDatasource();
|
||||
UserMetaInfo.getDatasource();
|
||||
DailyEccmReportsDatasource.getDatasource();
|
||||
|
||||
this.enhancerService.addEnhancer([
|
||||
this.timestampEnhancer,
|
||||
|
|
|
|||
41
src/couchdb-datasources/daily-eccm-reports.datasource.ts
Normal file
41
src/couchdb-datasources/daily-eccm-reports.datasource.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { CouchDb } from '@app/event-emitter/couchdb-datasources/couchdb';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import nano from 'nano';
|
||||
import { DailyEccmReport } from 'src/reports/daily-eccm.report.service';
|
||||
import configuration from '../configs/app';
|
||||
|
||||
const config = configuration();
|
||||
|
||||
@Injectable()
|
||||
export class DailyEccmReportsDatasource {
|
||||
private static logger = new Logger(DailyEccmReportsDatasource.name);
|
||||
private static db = null;
|
||||
private static initilized = false;
|
||||
|
||||
static async getDatasource(): Promise<
|
||||
nano.DocumentScope<DailyEccmReport.Models.Report>
|
||||
> {
|
||||
if (DailyEccmReportsDatasource.initilized) {
|
||||
return DailyEccmReportsDatasource.db;
|
||||
}
|
||||
DailyEccmReportsDatasource.initilized = true;
|
||||
const n = CouchDb.getCouchDb();
|
||||
const dbName = config.couchDb.dbs.eccmDailyReports;
|
||||
const dbs = await n.db.list();
|
||||
if (!dbs.includes(dbName)) {
|
||||
await n.db.create(dbName);
|
||||
}
|
||||
DailyEccmReportsDatasource.db = await n.db.use(dbName);
|
||||
DailyEccmReportsDatasource.initilized = true;
|
||||
DailyEccmReportsDatasource.logger.log(
|
||||
`Connected to eccm-daily-reports db - ${dbName}`,
|
||||
);
|
||||
return DailyEccmReportsDatasource.db;
|
||||
}
|
||||
|
||||
async getDatasource(): Promise<
|
||||
nano.DocumentScope<DailyEccmReport.Models.Report>
|
||||
> {
|
||||
return await DailyEccmReportsDatasource.getDatasource();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ export type AppConfig = {
|
|||
dbs: {
|
||||
changes: string;
|
||||
userMetaInfo: string;
|
||||
eccmDailyReports: string;
|
||||
};
|
||||
};
|
||||
telegramBotToken: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Controller, Get, Query, Render } from '@nestjs/common';
|
||||
import { Controller, Get, Param, Query, Render } from '@nestjs/common';
|
||||
import {
|
||||
DailyEccmReport,
|
||||
DailyEccmReportService,
|
||||
|
|
@ -10,28 +10,61 @@ export class DailyEccmReportController {
|
|||
|
||||
@Get()
|
||||
@Render('daily-eccm-report')
|
||||
async getReportDefault(
|
||||
@Query('from') from: string,
|
||||
@Query('to') to: string,
|
||||
): Promise<DailyEccmReport.Models.Report> {
|
||||
return await this.getReport(from, to);
|
||||
}
|
||||
|
||||
@Get('/delete/:name')
|
||||
async deleteReport(@Param('name') name: string): Promise<boolean> {
|
||||
return await this.dailyEccmReportService.deleteReport(name);
|
||||
}
|
||||
|
||||
@Get('/load/:name')
|
||||
@Render('daily-eccm-report')
|
||||
async loadReport(
|
||||
@Param('name') name: string,
|
||||
): Promise<DailyEccmReport.Models.Report> {
|
||||
return await this.dailyEccmReportService.loadReport(name);
|
||||
}
|
||||
|
||||
@Get('/load/:name/raw')
|
||||
async loadReportRawData(
|
||||
@Param('name') name: string,
|
||||
): Promise<DailyEccmReport.Models.Report> {
|
||||
return await this.dailyEccmReportService.loadReport(name);
|
||||
}
|
||||
|
||||
@Get('/generate')
|
||||
@Render('daily-eccm-report')
|
||||
async getReport(
|
||||
@Query('from') from: string,
|
||||
@Query('to') to: string,
|
||||
@Query('name') name?: string,
|
||||
@Query('overwrite') overwrite?: boolean,
|
||||
): Promise<DailyEccmReport.Models.Report> {
|
||||
const now = new Date().toISOString();
|
||||
return await this.dailyEccmReportService.generateReport({
|
||||
from: from,
|
||||
to: to,
|
||||
reportDate: now,
|
||||
});
|
||||
return await this.getReportRawData(from, to, name, overwrite);
|
||||
}
|
||||
|
||||
@Get('/raw')
|
||||
@Get('/generate/raw')
|
||||
async getReportRawData(
|
||||
@Query('from') from: string,
|
||||
@Query('to') to: string,
|
||||
@Query('name') name?: string,
|
||||
@Query('overwrite') overwrite?: boolean,
|
||||
): Promise<DailyEccmReport.Models.Report> {
|
||||
const now = new Date().toISOString();
|
||||
return await this.dailyEccmReportService.generateReport({
|
||||
from: from,
|
||||
to: to,
|
||||
reportDate: now,
|
||||
});
|
||||
const params = this.dailyEccmReportService.generateParams(from, to, name);
|
||||
const data = await this.dailyEccmReportService.generateReport(params);
|
||||
if (name) {
|
||||
const saveResult = await this.dailyEccmReportService.saveReport(
|
||||
data,
|
||||
overwrite,
|
||||
);
|
||||
return saveResult ? data : null;
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import { TimestampConverter } from '@app/event-emitter/utils/timestamp-converter
|
|||
import { IssuesService } from '@app/event-emitter/issues/issues.service';
|
||||
import { UsersService } from '@app/event-emitter/users/users.service';
|
||||
import { GetParentsHint } from '@app/event-emitter/utils/get-parents-hint';
|
||||
import { DailyEccmReportsDatasource } from 'src/couchdb-datasources/daily-eccm-reports.datasource';
|
||||
import { Timestamped } from '@app/event-emitter/models/timestamped';
|
||||
import { TimestampNowFill } from '@app/event-emitter/utils/timestamp-now-fill';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace DailyEccmReport {
|
||||
|
|
@ -96,7 +100,9 @@ export namespace DailyEccmReport {
|
|||
export type Params = {
|
||||
from: string;
|
||||
to: string;
|
||||
reportDate: string;
|
||||
name: string;
|
||||
project: string;
|
||||
versions: string[];
|
||||
};
|
||||
|
||||
export type IssueAndChanges = {
|
||||
|
|
@ -140,28 +146,32 @@ export namespace DailyEccmReport {
|
|||
private eccmUsers: RedmineTypes.User[],
|
||||
private issuesService: IssuesService,
|
||||
) {
|
||||
const users: Models.UserReport[] = this.eccmUsers.map((user) => {
|
||||
if (!user) {
|
||||
this.logger.error(`Not found user data`);
|
||||
return;
|
||||
}
|
||||
const u: Models.User = {
|
||||
id: user.id,
|
||||
lastname: user.lastname,
|
||||
firstname: user.firstname,
|
||||
};
|
||||
return {
|
||||
user: u,
|
||||
activities: [],
|
||||
issuesGroupedByStatus: [],
|
||||
} as Models.UserReport;
|
||||
}).filter((user) => Boolean(user));
|
||||
const users: Models.UserReport[] = this.eccmUsers
|
||||
.map((user) => {
|
||||
if (!user) {
|
||||
this.logger.error(`Not found user data`);
|
||||
return;
|
||||
}
|
||||
const u: Models.User = {
|
||||
id: user.id,
|
||||
lastname: user.lastname,
|
||||
firstname: user.firstname,
|
||||
};
|
||||
return {
|
||||
user: u,
|
||||
activities: [],
|
||||
issuesGroupedByStatus: [],
|
||||
} as Models.UserReport;
|
||||
})
|
||||
.filter((user) => Boolean(user));
|
||||
this.report = {
|
||||
byUsers: users,
|
||||
params: {
|
||||
from: '',
|
||||
to: '',
|
||||
reportDate: '',
|
||||
name: '',
|
||||
project: '',
|
||||
versions: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -173,7 +183,9 @@ export namespace DailyEccmReport {
|
|||
setParams(params: Models.Params): void {
|
||||
this.report.params.from = params.from;
|
||||
this.report.params.to = params.to;
|
||||
this.report.params.reportDate = params.reportDate;
|
||||
this.report.params.name = params.name;
|
||||
this.report.params.versions = [...params.versions];
|
||||
this.report.params.project = params.project;
|
||||
}
|
||||
|
||||
async setCurrentIssues(issues: RedmineTypes.Issue[]): Promise<void> {
|
||||
|
|
@ -355,6 +367,7 @@ export class DailyEccmReportService {
|
|||
private changesService: ChangesService,
|
||||
private issuesService: IssuesService,
|
||||
private usersService: UsersService,
|
||||
private dailyEccmReportsDatasource: DailyEccmReportsDatasource,
|
||||
) {
|
||||
this.eccmConfig = this.configService.get<EccmConfig.Config>('redmineEccm');
|
||||
}
|
||||
|
|
@ -389,6 +402,83 @@ export class DailyEccmReportService {
|
|||
return report.get();
|
||||
}
|
||||
|
||||
generateParams(
|
||||
from: string,
|
||||
to: string,
|
||||
name?: string,
|
||||
): DailyEccmReport.Models.Params {
|
||||
const fromDate = DateTime.fromISO(from);
|
||||
if (!fromDate.isValid) throw new Error('from is invalid date');
|
||||
const toDate = DateTime.fromISO(to);
|
||||
if (!toDate.isValid) throw new Error('to is invalid date');
|
||||
let nameValue: string | null = name || null;
|
||||
if (!nameValue) {
|
||||
nameValue = DateTime.now().toFormat('yyyy-MM-dd');
|
||||
}
|
||||
return {
|
||||
from: fromDate.toISO(),
|
||||
to: toDate.toISO(),
|
||||
name: nameValue,
|
||||
project: this.eccmConfig.projectName,
|
||||
versions: [...this.eccmConfig.currentVersions],
|
||||
};
|
||||
}
|
||||
|
||||
async saveReport(
|
||||
report: DailyEccmReport.Models.Report,
|
||||
overwrite?: boolean,
|
||||
): Promise<boolean> {
|
||||
const name = report.params.name;
|
||||
const existsReport = await this.loadReport(name);
|
||||
if (existsReport && !overwrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
type ReportType = nano.DocumentGetResponse &
|
||||
DailyEccmReport.Models.Report &
|
||||
Timestamped;
|
||||
|
||||
const newReport: ReportType = TimestampNowFill({
|
||||
...report,
|
||||
_id: name,
|
||||
_rev: existsReport?._rev,
|
||||
});
|
||||
|
||||
const datasource = await this.dailyEccmReportsDatasource.getDatasource();
|
||||
await datasource.insert(newReport);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async loadReport(
|
||||
name: string,
|
||||
): Promise<
|
||||
| (DailyEccmReport.Models.Report & nano.DocumentGetResponse & Timestamped)
|
||||
| null
|
||||
> {
|
||||
const datasource = await this.dailyEccmReportsDatasource.getDatasource();
|
||||
let resp: any = null;
|
||||
try {
|
||||
resp = await datasource.get(name);
|
||||
} catch (ex) {
|
||||
this.logger.warn(
|
||||
`Cannot load report ${name} with error message ${ex.message}`,
|
||||
);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
async deleteReport(name: string): Promise<boolean> {
|
||||
const datasource = await this.dailyEccmReportsDatasource.getDatasource();
|
||||
const report = await this.loadReport(name);
|
||||
let res = false;
|
||||
try {
|
||||
await datasource.destroy(report._id, report._rev);
|
||||
res = true;
|
||||
} catch (ex) {}
|
||||
return res;
|
||||
}
|
||||
|
||||
private async createEmptyReport(): Promise<DailyEccmReport.Report> {
|
||||
const usersList: string[] = this.eccmConfig.groups.reduce((acc, group) => {
|
||||
group.people.forEach((fullname) => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
<ul>
|
||||
<li>От - {{this.params.from}}</li>
|
||||
<li>До - {{this.params.to}}</li>
|
||||
<li>Дата выгрузки - {{this.params.reportDate}}</li>
|
||||
<li>Имя отчёта - {{this.params.name}}</li>
|
||||
<li>Имя проекта - {{this.params.project}}</li>
|
||||
<li>Версии - {{this.params.versions}}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h1>Отчёт по работникам</h1>
|
||||
|
|
|
|||
Loading…
Reference in a new issue