diff --git a/packages/cli/src/modules/insights/__tests__/insights-pruning.service.test.ts b/packages/cli/src/modules/insights/__tests__/insights-pruning.service.test.ts index e8eb96f9f5..14384b60dd 100644 --- a/packages/cli/src/modules/insights/__tests__/insights-pruning.service.test.ts +++ b/packages/cli/src/modules/insights/__tests__/insights-pruning.service.test.ts @@ -1,3 +1,4 @@ +import type { LicenseState } from '@n8n/backend-common'; import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; import { DateTime } from 'luxon'; @@ -38,12 +39,21 @@ describe('InsightsPruningService', () => { let insightsConfig: InsightsConfig; let insightsByPeriodRepository: InsightsByPeriodRepository; let insightsPruningService: InsightsPruningService; + let licenseState: LicenseState; beforeAll(async () => { insightsConfig = Container.get(InsightsConfig); insightsConfig.maxAgeDays = 10; insightsConfig.pruneCheckIntervalHours = 1; - insightsPruningService = Container.get(InsightsPruningService); insightsByPeriodRepository = Container.get(InsightsByPeriodRepository); + licenseState = mock({ + getInsightsRetentionMaxAge: () => insightsConfig.maxAgeDays, + }); + insightsPruningService = new InsightsPruningService( + insightsByPeriodRepository, + insightsConfig, + licenseState, + mockLogger(), + ); }); test('old insights get pruned successfully', async () => { @@ -90,6 +100,58 @@ describe('InsightsPruningService', () => { expect(await insightsByPeriodRepository.count()).toBe(1); }); + test.each<{ config: number; license: number; result: number }>([ + { + config: -1, + license: -1, + result: Number.MAX_SAFE_INTEGER, + }, + { + config: -1, + license: 5, + result: 5, + }, + { + config: 5, + license: -1, + result: 5, + }, + { + config: 5, + license: 10, + result: 5, + }, + { + config: 10, + license: 5, + result: 5, + }, + ])( + 'pruningMaxAgeInDays is minimal age between license and config max age', + async ({ config, license, result }) => { + // ARRANGE + const licenseState = mock({ + getInsightsRetentionMaxAge() { + return license; + }, + }); + const insightsPruningService = new InsightsPruningService( + insightsByPeriodRepository, + mock({ + maxAgeDays: config, + }), + licenseState, + mockLogger(), + ); + + // ACT + const maxAge = insightsPruningService.pruningMaxAgeInDays; + + // ASSERT + expect(maxAge).toBe(result); + }, + ); + describe('pruning scheduling', () => { beforeEach(() => { jest.useFakeTimers(); @@ -111,6 +173,7 @@ describe('InsightsPruningService', () => { const insightsPruningService = new InsightsPruningService( insightsByPeriodRepository, insightsConfig, + licenseState, mockLogger(), ); const pruneSpy = jest.spyOn(insightsPruningService, 'pruneInsights'); @@ -134,6 +197,7 @@ describe('InsightsPruningService', () => { const insightsPruningService = new InsightsPruningService( insightsByPeriodRepository, insightsConfig, + licenseState, mockLogger(), ); diff --git a/packages/cli/src/modules/insights/__tests__/insights.service.test.ts b/packages/cli/src/modules/insights/__tests__/insights.service.test.ts index 989cc80689..c198ad71c7 100644 --- a/packages/cli/src/modules/insights/__tests__/insights.service.test.ts +++ b/packages/cli/src/modules/insights/__tests__/insights.service.test.ts @@ -512,7 +512,6 @@ describe('getAvailableDateRanges', () => { mock(), mock(), licenseMock, - mock(), mockLogger(), ); }); @@ -615,7 +614,6 @@ describe('getMaxAgeInDaysAndGranularity', () => { mock(), mock(), licenseMock, - mock(), mockLogger(), ); }); @@ -704,7 +702,6 @@ describe('shutdown', () => { mockCollectionService, mockPruningService, mock(), - mock(), mockLogger(), ); }); @@ -736,6 +733,7 @@ describe('timers', () => { const mockPruningService = mock({ startPruningTimer: jest.fn(), stopPruningTimer: jest.fn(), + isPruningEnabled: false, }); const mockedLogger = mockLogger(); @@ -750,7 +748,6 @@ describe('timers', () => { mockCollectionService, mockPruningService, mock(), - mockedConfig, mockedLogger, ); }); @@ -768,6 +765,7 @@ describe('timers', () => { test('startTimers starts pruning timer', () => { // ARRANGE mockedConfig.maxAgeDays = 30; + Object.defineProperty(mockPruningService, 'isPruningEnabled', { value: true }); // ACT insightsService.startTimers(); diff --git a/packages/cli/src/modules/insights/insights-pruning.service.ts b/packages/cli/src/modules/insights/insights-pruning.service.ts index c119416ab2..5305e744e6 100644 --- a/packages/cli/src/modules/insights/insights-pruning.service.ts +++ b/packages/cli/src/modules/insights/insights-pruning.service.ts @@ -1,3 +1,4 @@ +import { LicenseState } from '@n8n/backend-common'; import { Service } from '@n8n/di'; import { strict } from 'assert'; import { Logger } from 'n8n-core'; @@ -18,11 +19,25 @@ export class InsightsPruningService { constructor( private readonly insightsByPeriodRepository: InsightsByPeriodRepository, private readonly config: InsightsConfig, + private readonly licenseState: LicenseState, private readonly logger: Logger, ) { this.logger = this.logger.scoped('insights'); } + get isPruningEnabled() { + return this.licenseState.getInsightsRetentionMaxAge() > -1 || this.config.maxAgeDays > -1; + } + + get pruningMaxAgeInDays() { + const toMaxSafeIfUnlimited = (days: number) => (days === -1 ? Number.MAX_SAFE_INTEGER : days); + + const licenseMaxAge = toMaxSafeIfUnlimited(this.licenseState.getInsightsRetentionMaxAge()); + const configMaxAge = toMaxSafeIfUnlimited(this.config.maxAgeDays); + + return Math.min(licenseMaxAge, configMaxAge); + } + startPruningTimer() { strict(this.isStopped); this.clearPruningTimer(); @@ -57,7 +72,7 @@ export class InsightsPruningService { async pruneInsights() { this.logger.info('Pruning old insights data'); try { - const result = await this.insightsByPeriodRepository.pruneOldData(this.config.maxAgeDays); + const result = await this.insightsByPeriodRepository.pruneOldData(this.pruningMaxAgeInDays); this.logger.debug( 'Deleted insights by period', result.affected ? { count: result.affected } : {}, diff --git a/packages/cli/src/modules/insights/insights.service.ts b/packages/cli/src/modules/insights/insights.service.ts index b85b720eca..92ea76f1d4 100644 --- a/packages/cli/src/modules/insights/insights.service.ts +++ b/packages/cli/src/modules/insights/insights.service.ts @@ -15,7 +15,6 @@ import { InsightsByPeriodRepository } from './database/repositories/insights-by- import { InsightsCollectionService } from './insights-collection.service'; import { InsightsCompactionService } from './insights-compaction.service'; import { InsightsPruningService } from './insights-pruning.service'; -import { InsightsConfig } from './insights.config'; const keyRangeToDays: Record = { day: 1, @@ -35,20 +34,15 @@ export class InsightsService { private readonly collectionService: InsightsCollectionService, private readonly pruningService: InsightsPruningService, private readonly licenseState: LicenseState, - private readonly config: InsightsConfig, private readonly logger: Logger, ) { this.logger = this.logger.scoped('insights'); } - get isPruningEnabled() { - return this.config.maxAgeDays > -1; - } - startTimers() { this.compactionService.startCompactionTimer(); this.collectionService.startFlushingTimer(); - if (this.isPruningEnabled) { + if (this.pruningService.isPruningEnabled) { this.pruningService.startPruningTimer(); } this.logger.debug('Started compaction, flushing and pruning schedulers');