backup draft

This commit is contained in:
Pavel Gnedov 2025-11-26 18:19:04 +07:00
parent 628412ffc5
commit 5913609f96
8 changed files with 223 additions and 81 deletions

View file

@ -17,6 +17,7 @@
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"axios": "^1.4.0", "axios": "^1.4.0",
"jsonpath-plus": "^10.3.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"mobx": "^6.9.0", "mobx": "^6.9.0",
"mobx-react-lite": "^3.4.3", "mobx-react-lite": "^3.4.3",
@ -2974,6 +2975,28 @@
"@jridgewell/sourcemap-codec": "1.4.14" "@jridgewell/sourcemap-codec": "1.4.14"
} }
}, },
"node_modules/@jsep-plugin/assignment": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz",
"integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==",
"engines": {
"node": ">= 10.16.0"
},
"peerDependencies": {
"jsep": "^0.4.0||^1.0.0"
}
},
"node_modules/@jsep-plugin/regex": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz",
"integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==",
"engines": {
"node": ">= 10.16.0"
},
"peerDependencies": {
"jsep": "^0.4.0||^1.0.0"
}
},
"node_modules/@leichtgewicht/ip-codec": { "node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@ -11430,6 +11453,14 @@
} }
} }
}, },
"node_modules/jsep": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
"integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==",
"engines": {
"node": ">= 10.16.0"
}
},
"node_modules/jsesc": { "node_modules/jsesc": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@ -11483,6 +11514,23 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"node_modules/jsonpath-plus": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz",
"integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==",
"dependencies": {
"@jsep-plugin/assignment": "^1.3.0",
"@jsep-plugin/regex": "^1.0.4",
"jsep": "^1.4.0"
},
"bin": {
"jsonpath": "bin/jsonpath-cli.js",
"jsonpath-plus": "bin/jsonpath-cli.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/jsonpointer": { "node_modules/jsonpointer": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
@ -19116,6 +19164,18 @@
"@jridgewell/sourcemap-codec": "1.4.14" "@jridgewell/sourcemap-codec": "1.4.14"
} }
}, },
"@jsep-plugin/assignment": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz",
"integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==",
"requires": {}
},
"@jsep-plugin/regex": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz",
"integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==",
"requires": {}
},
"@leichtgewicht/ip-codec": { "@leichtgewicht/ip-codec": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@ -25275,6 +25335,11 @@
"xml-name-validator": "^3.0.0" "xml-name-validator": "^3.0.0"
} }
}, },
"jsep": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
"integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="
},
"jsesc": { "jsesc": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@ -25314,6 +25379,16 @@
"universalify": "^2.0.0" "universalify": "^2.0.0"
} }
}, },
"jsonpath-plus": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz",
"integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==",
"requires": {
"@jsep-plugin/assignment": "^1.3.0",
"@jsep-plugin/regex": "^1.0.4",
"jsep": "^1.4.0"
}
},
"jsonpointer": { "jsonpointer": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",

View file

@ -12,6 +12,7 @@
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"axios": "^1.4.0", "axios": "^1.4.0",
"jsonpath-plus": "^10.3.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"mobx": "^6.9.0", "mobx": "^6.9.0",
"mobx-react-lite": "^3.4.3", "mobx-react-lite": "^3.4.3",

View file

@ -58,6 +58,8 @@ import { DailyEccmV2ReportTaskRunnerService } from './reports/daily-eccm-v2-repo
import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service'; import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service';
import { DailyEccmReportsV2Datasource } from './couchdb-datasources/daily-eccm-reports-v2.datasource'; import { DailyEccmReportsV2Datasource } from './couchdb-datasources/daily-eccm-reports-v2.datasource';
import { DailyEccmReportsV2DataLoaderService } from './eccm-statistic/dashboards/widget-data-loader/daily-eccm-v2.widget-data-loader.service'; import { DailyEccmReportsV2DataLoaderService } from './eccm-statistic/dashboards/widget-data-loader/daily-eccm-v2.widget-data-loader.service';
import { DailyEccmV2ReportController } from './reports/daily-eccm-v2-report.controller';
import { DailyEccmV2ReportService } from './reports/daily-eccm-v2-report.service';
@Module({ @Module({
imports: [ imports: [
@ -82,6 +84,7 @@ import { DailyEccmReportsV2DataLoaderService } from './eccm-statistic/dashboards
SimpleIssuesListController, SimpleIssuesListController,
TagsManagerController, TagsManagerController,
TelegramBotController, TelegramBotController,
DailyEccmV2ReportController,
], ],
providers: [ providers: [
AppService, AppService,
@ -123,6 +126,7 @@ import { DailyEccmReportsV2DataLoaderService } from './eccm-statistic/dashboards
DailyEccmReportsV2Datasource, DailyEccmReportsV2Datasource,
DailyEccmV2ReportTaskRunnerService, DailyEccmV2ReportTaskRunnerService,
DailyEccmReportsV2DataLoaderService, DailyEccmReportsV2DataLoaderService,
DailyEccmV2ReportService,
], ],
}) })
export class AppModule implements OnModuleInit { export class AppModule implements OnModuleInit {

View file

@ -0,0 +1,13 @@
import { RedmineTypes } from '@app/event-emitter/models/redmine-types';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ReviewersService {
constructor() {
return;
}
async getTasks(): Promise<RedmineTypes.ExtendedIssue[]> {
return;
}
}

View file

@ -1,77 +0,0 @@
import { Logger } from '@nestjs/common';
import { IssuesService } from '@app/event-emitter/issues/issues.service';
export type Params = {
query: any; // TODO: add type
schedule: string;
};
export type Report = {
id: string;
name: string;
datetime: number;
datetimeFormatted: string;
jobInfo: JobInfo;
params: Params; // query?, schedule?, etc TODO
data: any;
};
export type JobInfo = {
jobId: string;
dashboardId: string;
widgetId: string;
};
export class DailyEccmV2ReportTaskHandlerService {
private logger = new Logger(DailyEccmV2ReportTaskHandlerService.name);
private issuesService: IssuesService | null = null;
constructor(public params: Params, public jobInfo: JobInfo) {
return;
}
setIssuesService(issuesService: IssuesService): void {
this.issuesService = issuesService;
}
getIssuesService(): IssuesService | null {
if (!this.issuesService) {
this.logger.warn(
'DailyEccmV2ReportTaskHandlerService is not initialized, issuesService is null',
);
return null;
}
return this.issuesService;
}
async createReport(): Promise<void> {
try {
const report = await this.prepareReportData();
await this.saveNewReport(report);
} catch (error) {
this.logger.error(error);
}
}
async prepareReportData(): Promise<Report> {
const issuesService = this.getIssuesService();
if (!issuesService) {
throw new Error(
'Cannot create report without issuesService, DailyEccmV2ReportTaskHandlerService is not initialized',
);
}
const issues = await this.issuesService.mergedTreesAndFind(
this.params.query,
);
return {} as Report;
}
async saveNewReport(report: Report): Promise<boolean> {
return true;
}
async updatePreviousReport(report: Report): Promise<boolean> {
return true;
}
}

View file

@ -8,11 +8,11 @@ import { DailyEccmReportsV2Datasource } from 'src/couchdb-datasources/daily-eccm
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { RedmineTypes } from '@app/event-emitter/models/redmine-types'; import { RedmineTypes } from '@app/event-emitter/models/redmine-types';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { Params } from './daily-eccm-v2-report-task-handler'; // import { Params } from './daily-eccm-v2-report-task-handler';
export type Job = { export type Job = {
id: string; id: string;
params: Params; params: any;
}; };
export type JobHandlerParams = { export type JobHandlerParams = {

View file

@ -0,0 +1,30 @@
import { Controller, Get, Param, Query, Logger } from '@nestjs/common';
import { DailyEccmV2ReportService } from './daily-eccm-v2-report.service';
@Controller('/api/daily-eccm-v2-report')
export class DailyEccmV2ReportController {
private logger = new Logger(DailyEccmV2ReportController.name);
constructor(private dailyEccmV2ReportService: DailyEccmV2ReportService) {
return;
}
@Get(':dashboardId/:widgetId/list')
async getList(@Param() params: any, @Query() query: any): Promise<any> {
const getListParams: any = {
dashboardId: params.dashboardId,
widgetId: params.widgetId,
};
if (query.limit) getListParams.limit = parseInt(query.limit);
if (query.after) getListParams.after = query.after;
if (query.before) getListParams.before = query.before;
return this.dailyEccmV2ReportService.getList(getListParams);
}
@Get(':dashboardId/:widgetId/latest')
async getLatest(@Param() params: any): Promise<any> {
const dashboardId = params.dashboardId;
const widgetId = params.widgetId;
return this.dailyEccmV2ReportService.getLatest(dashboardId, widgetId);
}
}

View file

@ -1,4 +1,100 @@
import { Injectable } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { DailyEccmReportsV2Datasource } from 'src/couchdb-datasources/daily-eccm-reports-v2.datasource';
import { DateTime } from 'luxon';
export type ListProps = {
dashboardId: string;
widgetId: string;
limit?: number;
/** Milliseconds since Unix epoch */
after?: number | string;
/** Milliseconds since Unix epoch */
before?: number | string;
};
@Injectable() @Injectable()
export class DailyEccmV2ReportService {} export class DailyEccmV2ReportService {
private logger = new Logger(DailyEccmV2ReportService.name);
constructor() {
return;
}
async getList(props: ListProps): Promise<any[]> {
const ds = await DailyEccmReportsV2Datasource.getDatasource();
const findQuery: any = {
selector: {
$and: [
{ dashboardId: props.dashboardId },
{ widgetId: props.widgetId },
],
},
fields: [
'id',
'dashboardId',
'widgetId',
'datetime',
'datetimeFormatted',
],
sort: [
{
datetime: 'asc',
},
],
};
if (props.after) {
const after =
typeof props.after === 'string'
? DateTime.fromISO(props.after).toMillis()
: Number(props.after);
findQuery.selector.$and.push({ datetime: { $gte: after } });
}
if (props.before) {
const before =
typeof props.before === 'string'
? DateTime.fromISO(props.before).toMillis()
: Number(props.before);
findQuery.selector.$and.push({ datetime: { $lte: before } });
}
if (props.limit) {
findQuery.limit = props.limit;
}
this.logger.log(
`Get list daily eccm v2 report with query: ${JSON.stringify(findQuery)}`,
);
const result = await ds.find(findQuery);
if (result && result.docs) {
return result.docs as any;
}
return [];
}
async getLatest(dashboardId: string, widgetId: string): Promise<any> {
const ds = await DailyEccmReportsV2Datasource.getDatasource();
const findQuery: any = {
selector: {
$and: [
{ dashboardId: dashboardId },
{ widgetId: widgetId },
{ latest: true },
],
},
fields: [
'id',
'dashboardId',
'widgetId',
'datetime',
'datetimeFormatted',
'reportIssues',
'issuesMetrics',
],
limit: 1,
};
const result = await ds.find(findQuery);
if (result && result.docs) {
return result.docs[0] as any;
}
return null;
}
}