Добавлено автообнаружение в виджетах 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 nano from 'nano';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { createAppError } from '../utils/result'; import { createAppError } from '../utils/result';
import { TimestampNowFill } from '../utils/timestamp-now-fill';
@Injectable() @Injectable()
export class DashboardsService { export class DashboardsService {
@ -56,18 +57,18 @@ export class DashboardsService {
} }
async save(id: string, data: DashboardModel.Data): Promise<void> { async save(id: string, data: DashboardModel.Data): Promise<void> {
this.logger.debug( this.logger.log(
`Save dashboard id - ${id}, data - ${JSON.stringify(data)}`, `Save dashboard id - ${id}, title - ${JSON.stringify(data.title)}`,
); );
const ds = await this.db.getDatasource(); const ds = await this.db.getDatasource();
const prevValue = await this.loadRawData(id); const prevValue = await this.loadRawData(id);
const newValue = { const newValue = TimestampNowFill({
_id: prevValue._id, _id: prevValue._id,
_rev: prevValue._rev, _rev: prevValue._rev,
id: prevValue.id, id: prevValue.id,
data: data, data: data,
}; });
await ds.insert(newValue); await ds.insert(newValue);
return; return;
} }
@ -89,17 +90,33 @@ export class DashboardsService {
async findDashboardsByWidgetType( async findDashboardsByWidgetType(
widgetType: string, widgetType: string,
updatedAfter?: number,
): Promise<DashboardModel.Dashboard[]> { ): Promise<DashboardModel.Dashboard[]> {
const ds = await this.db.getDatasource(); const ds = await this.db.getDatasource();
const data = await ds.find({ const selector = {
selector: {
'data.widgets': { 'data.widgets': {
$elemMatch: { $elemMatch: {
type: widgetType, 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'); if (!data.docs) throw createAppError('DASHBOARDS_NOT_FOUND');
return data.docs; 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 { Dashboards as DashboardsDs } from '@app/event-emitter/couchdb-datasources/dashboards';
import { DashboardInitService } from './dashboards/dashboard-init.service'; import { DashboardInitService } from './dashboards/dashboard-init.service';
import { TelegramBotController } from './telegram-bot/telegram-bot.controller'; 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({ @Module({
imports: [ imports: [
@ -115,6 +118,9 @@ import { TelegramBotController } from './telegram-bot/telegram-bot.controller';
}, },
CreateTagManagerServiceProvider('TAG_MANAGER_SERVICE'), CreateTagManagerServiceProvider('TAG_MANAGER_SERVICE'),
DashboardInitService, DashboardInitService,
DashboardsService,
DailyEccmReportsV2Datasource,
DailyEccmV2ReportService,
], ],
}) })
export class AppModule implements OnModuleInit { export class AppModule implements OnModuleInit {
@ -143,16 +149,22 @@ export class AppModule implements OnModuleInit {
private calendarEnhancer: CalendarEnhancer, private calendarEnhancer: CalendarEnhancer,
private dashboardInitService: DashboardInitService, private dashboardInitService: DashboardInitService,
private dashboardsService: DashboardsService,
private dailyEccmV2ReportService: DailyEccmV2ReportService,
) {} ) {}
onModuleInit() { onModuleInit() {
Issues.getDatasource(); const datasources = [
Users.getDatasource(); Issues.getDatasource(),
Changes.getDatasource(); Users.getDatasource(),
UserMetaInfo.getDatasource(); Changes.getDatasource(),
DailyEccmReportsDatasource.getDatasource(); UserMetaInfo.getDatasource(),
DailyEccmReportsUserCommentsDatasource.getDatasource(); DailyEccmReportsDatasource.getDatasource(),
DashboardsDs.getDatasource(); DailyEccmReportsUserCommentsDatasource.getDatasource(),
DashboardsDs.getDatasource(),
DailyEccmReportsV2Datasource.getDatasource(),
];
this.enhancerService.addEnhancer([ this.enhancerService.addEnhancer([
this.timestampEnhancer, this.timestampEnhancer,
@ -222,6 +234,13 @@ export class AppModule implements OnModuleInit {
this.initDailyEccmUserCommentsPipeline(); this.initDailyEccmUserCommentsPipeline();
this.initDashbordProviders(); this.initDashbordProviders();
Promise.all(datasources).then(() => {
this.dailyEccmV2ReportService.setDashboardsService(
this.dashboardsService,
);
this.dailyEccmV2ReportService.initAutoScanJobs();
});
} }
private initDailyEccmUserCommentsPipeline(): void { private initDailyEccmUserCommentsPipeline(): void {

View file

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

View file

@ -1,7 +1,7 @@
import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service'; import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service';
import { Dashboard, Widget } from '@app/event-emitter/models/dashboard'; import { Dashboard, Widget } from '@app/event-emitter/models/dashboard';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { Cron, SchedulerRegistry } from '@nestjs/schedule'; import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron'; import { CronJob } from 'cron';
export type Params = { export type Params = {
@ -23,19 +23,38 @@ export type Job = {
params: Params; 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 JOB_PREFIX = 'daily_eccm_v2';
export const UPDATE_RATE = 60 * 1000;
@Injectable() @Injectable()
export class DailyEccmV2ReportService { export class DailyEccmV2ReportService {
private logger = new Logger(DailyEccmV2ReportService.name); private logger = new Logger(DailyEccmV2ReportService.name);
private dashboardsService: DashboardsService; private dashboardsService: DashboardsService;
private previousAutoScanTime = 0;
private cronJobs: Record<string, CronJob> = {};
constructor(private schedulerRegistry: SchedulerRegistry) {} 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() { async autoScanJobs() {
this.logger.debug('Auto scan jobs started'); this.logger.debug('Auto scan jobs started');
const dbs = this.getDashboardsService(); const dbs = this.getDashboardsService();
@ -44,32 +63,43 @@ export class DailyEccmV2ReportService {
this.logger.debug('Auto scan jobs finished'); this.logger.debug('Auto scan jobs finished');
return; 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++) { for (let i = 0; i < dashboards.length; i++) {
const dashboard: Dashboard = dashboards[i]; const dashboard: Dashboard = dashboards[i];
for (let j = 0; j < dashboard.data.widgets.length; j++) { for (let j = 0; j < dashboard.data.widgets.length; j++) {
const widget = dashboard.data.widgets[j]; const widget = dashboard.data.widgets[j];
if (widget.type === WIDGET_TYPE) { if (widget.type === WIDGET_TYPE) {
const jobId = `${JOB_PREFIX}_${dashboard.id}_${widget.id}`; const jobId = `${JOB_PREFIX}_${dashboard.id}_${widget.id}`;
let cronJob = this.schedulerRegistry.getCronJob(jobId); this.updateCronJob(jobId, widget, dashboard);
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.logger.debug('Auto scan jobs finished'); 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 { getDashboardsService(): DashboardsService | null {
if (!this.dashboardsService) { if (!this.dashboardsService) {
this.logger.warn('Dashboards service not initialized'); this.logger.warn('Dashboards service not initialized');
@ -88,8 +118,12 @@ export class DailyEccmV2ReportService {
widget: Widget, widget: Widget,
): () => void { ): () => void {
return async () => { return async () => {
this.logger.debug(`Cron job ${jobId} started`); this.logger.debug(
this.logger.debug(`Cron job ${jobId} finished`); `Cron job ${jobId} started - dashboard ${dashboard.id}, widget ${widget.id}`,
);
this.logger.debug(
`Cron job ${jobId} finished - dashboard ${dashboard.id}, widget ${widget.id}`,
);
}; };
} }
} }