Добавлено автообнаружение в виджетах cron-задач

* Поиск обновлённых виджетов daily-eccm-v2
* Автоматическая инициализация поиска со старта приложения
This commit is contained in:
Pavel Gnedov 2024-12-10 09:20:45 +07:00
parent f284d70d6b
commit a2131e9b03
4 changed files with 107 additions and 40 deletions

View file

@ -4,6 +4,7 @@ import * as DashboardModel from '../models/dashboard';
import nano from 'nano';
import { randomUUID } from 'crypto';
import { createAppError } from '../utils/result';
import { TimestampNowFill } from '../utils/timestamp-now-fill';
@Injectable()
export class DashboardsService {
@ -56,18 +57,18 @@ export class DashboardsService {
}
async save(id: string, data: DashboardModel.Data): Promise<void> {
this.logger.debug(
`Save dashboard id - ${id}, data - ${JSON.stringify(data)}`,
this.logger.log(
`Save dashboard id - ${id}, title - ${JSON.stringify(data.title)}`,
);
const ds = await this.db.getDatasource();
const prevValue = await this.loadRawData(id);
const newValue = {
const newValue = TimestampNowFill({
_id: prevValue._id,
_rev: prevValue._rev,
id: prevValue.id,
data: data,
};
});
await ds.insert(newValue);
return;
}
@ -89,17 +90,33 @@ export class DashboardsService {
async findDashboardsByWidgetType(
widgetType: string,
updatedAfter?: number,
): Promise<DashboardModel.Dashboard[]> {
const ds = await this.db.getDatasource();
const data = await ds.find({
selector: {
const selector = {
'data.widgets': {
$elemMatch: {
type: widgetType,
},
},
},
};
if (updatedAfter > 0) {
selector['timestamp__'] = {
$gte: updatedAfter,
};
}
this.logger.debug(
`Find dashboards by widget type - ${widgetType} ` +
`and selector - ${JSON.stringify(selector)}`,
);
const data = await ds.find({
selector: selector,
});
this.logger.debug(
`Found dashboards by widget type - ${widgetType} ` +
`, selector - ${JSON.stringify(selector)}` +
`, result - ${JSON.stringify(data)}`,
);
if (!data.docs) throw createAppError('DASHBOARDS_NOT_FOUND');
return data.docs;
}

View file

@ -54,6 +54,9 @@ import { CalendarEnhancer } from '@app/event-emitter/issue-enhancers/calendar-en
import { Dashboards as DashboardsDs } from '@app/event-emitter/couchdb-datasources/dashboards';
import { DashboardInitService } from './dashboards/dashboard-init.service';
import { TelegramBotController } from './telegram-bot/telegram-bot.controller';
import { DailyEccmV2ReportService } from './reports/daily-eccm-v2.report.service';
import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service';
import { DailyEccmReportsV2Datasource } from './couchdb-datasources/daily-eccm-reports-v2.datasource';
@Module({
imports: [
@ -115,6 +118,9 @@ import { TelegramBotController } from './telegram-bot/telegram-bot.controller';
},
CreateTagManagerServiceProvider('TAG_MANAGER_SERVICE'),
DashboardInitService,
DashboardsService,
DailyEccmReportsV2Datasource,
DailyEccmV2ReportService,
],
})
export class AppModule implements OnModuleInit {
@ -143,16 +149,22 @@ export class AppModule implements OnModuleInit {
private calendarEnhancer: CalendarEnhancer,
private dashboardInitService: DashboardInitService,
private dashboardsService: DashboardsService,
private dailyEccmV2ReportService: DailyEccmV2ReportService,
) {}
onModuleInit() {
Issues.getDatasource();
Users.getDatasource();
Changes.getDatasource();
UserMetaInfo.getDatasource();
DailyEccmReportsDatasource.getDatasource();
DailyEccmReportsUserCommentsDatasource.getDatasource();
DashboardsDs.getDatasource();
const datasources = [
Issues.getDatasource(),
Users.getDatasource(),
Changes.getDatasource(),
UserMetaInfo.getDatasource(),
DailyEccmReportsDatasource.getDatasource(),
DailyEccmReportsUserCommentsDatasource.getDatasource(),
DashboardsDs.getDatasource(),
DailyEccmReportsV2Datasource.getDatasource(),
];
this.enhancerService.addEnhancer([
this.timestampEnhancer,
@ -222,6 +234,13 @@ export class AppModule implements OnModuleInit {
this.initDailyEccmUserCommentsPipeline();
this.initDashbordProviders();
Promise.all(datasources).then(() => {
this.dailyEccmV2ReportService.setDashboardsService(
this.dashboardsService,
);
this.dailyEccmV2ReportService.initAutoScanJobs();
});
}
private initDailyEccmUserCommentsPipeline(): void {

View file

@ -1,10 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { CouchDb } from '@app/event-emitter/couchdb-datasources/couchdb';
import nano from 'nano';
import {
DailyEccmV2ReportService,
Report,
} from 'src/reports/daily-eccm-v2.report.service';
import { Report } from 'src/reports/daily-eccm-v2.report.service';
@Injectable()
export class DailyEccmReportsV2Datasource {

View file

@ -1,7 +1,7 @@
import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service';
import { Dashboard, Widget } from '@app/event-emitter/models/dashboard';
import { Injectable, Logger } from '@nestjs/common';
import { Cron, SchedulerRegistry } from '@nestjs/schedule';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
export type Params = {
@ -23,19 +23,38 @@ export type Job = {
params: Params;
};
export const WIDGET_TYPE = 'daily-eccm-v2';
export const WIDGET_TYPE = 'daily_eccm_v2';
export const JOB_PREFIX = 'daily_eccm_v2';
export const UPDATE_RATE = 60 * 1000;
@Injectable()
export class DailyEccmV2ReportService {
private logger = new Logger(DailyEccmV2ReportService.name);
private dashboardsService: DashboardsService;
private previousAutoScanTime = 0;
private cronJobs: Record<string, CronJob> = {};
constructor(private schedulerRegistry: SchedulerRegistry) {}
@Cron('* * * * *')
/**
* Auto scan jobs every UPDATE_RATE seconds.
* First call to autoScanJobs is done immediately.
* Each subsequent call is done after the timeout.
* The timeout is reset after each call to autoScanJobs.
*/
initAutoScanJobs() {
const tick = () => {
setTimeout(tick, UPDATE_RATE);
this.autoScanJobs();
};
tick();
}
async autoScanJobs() {
this.logger.debug('Auto scan jobs started');
const dbs = this.getDashboardsService();
@ -44,32 +63,43 @@ export class DailyEccmV2ReportService {
this.logger.debug('Auto scan jobs finished');
return;
}
const dashboards = await dbs.findDashboardsByWidgetType(WIDGET_TYPE);
const nowTime = new Date().getTime();
const dashboards = await dbs.findDashboardsByWidgetType(
WIDGET_TYPE,
this.previousAutoScanTime,
);
this.previousAutoScanTime = nowTime;
for (let i = 0; i < dashboards.length; i++) {
const dashboard: Dashboard = dashboards[i];
for (let j = 0; j < dashboard.data.widgets.length; j++) {
const widget = dashboard.data.widgets[j];
if (widget.type === WIDGET_TYPE) {
const jobId = `${JOB_PREFIX}_${dashboard.id}_${widget.id}`;
let cronJob = this.schedulerRegistry.getCronJob(jobId);
if (!cronJob) {
cronJob = new CronJob(
widget.dataLoaderParams.schedule,
async () => {
this.logger.debug(`Cron job ${jobId} started`);
// const report = await dbs.getReport(dashboard.id, widget.id);
this.logger.debug(`Cron job ${jobId} finished`);
// return report;
},
);
this.schedulerRegistry.addCronJob(jobId, cronJob);
}
this.updateCronJob(jobId, widget, dashboard);
}
}
}
this.logger.debug('Auto scan jobs finished');
}
private updateCronJob(
jobId: string,
widget: Widget,
dashboard: Dashboard,
): void {
if (this.cronJobs[jobId]) {
this.cronJobs[jobId].stop();
this.schedulerRegistry.deleteCronJob(jobId);
}
const job = new CronJob(
widget.dataLoaderParams?.schedule || '* * * * *',
this.createJobHandler(jobId, dashboard, widget),
);
this.cronJobs[jobId] = job;
this.schedulerRegistry.addCronJob(jobId, job);
job.start();
}
getDashboardsService(): DashboardsService | null {
if (!this.dashboardsService) {
this.logger.warn('Dashboards service not initialized');
@ -88,8 +118,12 @@ export class DailyEccmV2ReportService {
widget: Widget,
): () => void {
return async () => {
this.logger.debug(`Cron job ${jobId} started`);
this.logger.debug(`Cron job ${jobId} finished`);
this.logger.debug(
`Cron job ${jobId} started - dashboard ${dashboard.id}, widget ${widget.id}`,
);
this.logger.debug(
`Cron job ${jobId} finished - dashboard ${dashboard.id}, widget ${widget.id}`,
);
};
}
}