mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(core): Check license config for insights max retention (#15256)
This commit is contained in:
committed by
GitHub
parent
15e62e6dfa
commit
3be05556f9
@@ -1,3 +1,4 @@
|
|||||||
|
import type { LicenseState } from '@n8n/backend-common';
|
||||||
import { Container } from '@n8n/di';
|
import { Container } from '@n8n/di';
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@@ -38,12 +39,21 @@ describe('InsightsPruningService', () => {
|
|||||||
let insightsConfig: InsightsConfig;
|
let insightsConfig: InsightsConfig;
|
||||||
let insightsByPeriodRepository: InsightsByPeriodRepository;
|
let insightsByPeriodRepository: InsightsByPeriodRepository;
|
||||||
let insightsPruningService: InsightsPruningService;
|
let insightsPruningService: InsightsPruningService;
|
||||||
|
let licenseState: LicenseState;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
insightsConfig = Container.get(InsightsConfig);
|
insightsConfig = Container.get(InsightsConfig);
|
||||||
insightsConfig.maxAgeDays = 10;
|
insightsConfig.maxAgeDays = 10;
|
||||||
insightsConfig.pruneCheckIntervalHours = 1;
|
insightsConfig.pruneCheckIntervalHours = 1;
|
||||||
insightsPruningService = Container.get(InsightsPruningService);
|
|
||||||
insightsByPeriodRepository = Container.get(InsightsByPeriodRepository);
|
insightsByPeriodRepository = Container.get(InsightsByPeriodRepository);
|
||||||
|
licenseState = mock<LicenseState>({
|
||||||
|
getInsightsRetentionMaxAge: () => insightsConfig.maxAgeDays,
|
||||||
|
});
|
||||||
|
insightsPruningService = new InsightsPruningService(
|
||||||
|
insightsByPeriodRepository,
|
||||||
|
insightsConfig,
|
||||||
|
licenseState,
|
||||||
|
mockLogger(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('old insights get pruned successfully', async () => {
|
test('old insights get pruned successfully', async () => {
|
||||||
@@ -90,6 +100,58 @@ describe('InsightsPruningService', () => {
|
|||||||
expect(await insightsByPeriodRepository.count()).toBe(1);
|
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<LicenseState>({
|
||||||
|
getInsightsRetentionMaxAge() {
|
||||||
|
return license;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const insightsPruningService = new InsightsPruningService(
|
||||||
|
insightsByPeriodRepository,
|
||||||
|
mock<InsightsConfig>({
|
||||||
|
maxAgeDays: config,
|
||||||
|
}),
|
||||||
|
licenseState,
|
||||||
|
mockLogger(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const maxAge = insightsPruningService.pruningMaxAgeInDays;
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(maxAge).toBe(result);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
describe('pruning scheduling', () => {
|
describe('pruning scheduling', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@@ -111,6 +173,7 @@ describe('InsightsPruningService', () => {
|
|||||||
const insightsPruningService = new InsightsPruningService(
|
const insightsPruningService = new InsightsPruningService(
|
||||||
insightsByPeriodRepository,
|
insightsByPeriodRepository,
|
||||||
insightsConfig,
|
insightsConfig,
|
||||||
|
licenseState,
|
||||||
mockLogger(),
|
mockLogger(),
|
||||||
);
|
);
|
||||||
const pruneSpy = jest.spyOn(insightsPruningService, 'pruneInsights');
|
const pruneSpy = jest.spyOn(insightsPruningService, 'pruneInsights');
|
||||||
@@ -134,6 +197,7 @@ describe('InsightsPruningService', () => {
|
|||||||
const insightsPruningService = new InsightsPruningService(
|
const insightsPruningService = new InsightsPruningService(
|
||||||
insightsByPeriodRepository,
|
insightsByPeriodRepository,
|
||||||
insightsConfig,
|
insightsConfig,
|
||||||
|
licenseState,
|
||||||
mockLogger(),
|
mockLogger(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -512,7 +512,6 @@ describe('getAvailableDateRanges', () => {
|
|||||||
mock<InsightsCollectionService>(),
|
mock<InsightsCollectionService>(),
|
||||||
mock<InsightsPruningService>(),
|
mock<InsightsPruningService>(),
|
||||||
licenseMock,
|
licenseMock,
|
||||||
mock<InsightsConfig>(),
|
|
||||||
mockLogger(),
|
mockLogger(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -615,7 +614,6 @@ describe('getMaxAgeInDaysAndGranularity', () => {
|
|||||||
mock<InsightsCollectionService>(),
|
mock<InsightsCollectionService>(),
|
||||||
mock<InsightsPruningService>(),
|
mock<InsightsPruningService>(),
|
||||||
licenseMock,
|
licenseMock,
|
||||||
mock<InsightsConfig>(),
|
|
||||||
mockLogger(),
|
mockLogger(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -704,7 +702,6 @@ describe('shutdown', () => {
|
|||||||
mockCollectionService,
|
mockCollectionService,
|
||||||
mockPruningService,
|
mockPruningService,
|
||||||
mock<LicenseState>(),
|
mock<LicenseState>(),
|
||||||
mock<InsightsConfig>(),
|
|
||||||
mockLogger(),
|
mockLogger(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -736,6 +733,7 @@ describe('timers', () => {
|
|||||||
const mockPruningService = mock<InsightsPruningService>({
|
const mockPruningService = mock<InsightsPruningService>({
|
||||||
startPruningTimer: jest.fn(),
|
startPruningTimer: jest.fn(),
|
||||||
stopPruningTimer: jest.fn(),
|
stopPruningTimer: jest.fn(),
|
||||||
|
isPruningEnabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockedLogger = mockLogger();
|
const mockedLogger = mockLogger();
|
||||||
@@ -750,7 +748,6 @@ describe('timers', () => {
|
|||||||
mockCollectionService,
|
mockCollectionService,
|
||||||
mockPruningService,
|
mockPruningService,
|
||||||
mock<LicenseState>(),
|
mock<LicenseState>(),
|
||||||
mockedConfig,
|
|
||||||
mockedLogger,
|
mockedLogger,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -768,6 +765,7 @@ describe('timers', () => {
|
|||||||
test('startTimers starts pruning timer', () => {
|
test('startTimers starts pruning timer', () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
mockedConfig.maxAgeDays = 30;
|
mockedConfig.maxAgeDays = 30;
|
||||||
|
Object.defineProperty(mockPruningService, 'isPruningEnabled', { value: true });
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
insightsService.startTimers();
|
insightsService.startTimers();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { LicenseState } from '@n8n/backend-common';
|
||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import { strict } from 'assert';
|
import { strict } from 'assert';
|
||||||
import { Logger } from 'n8n-core';
|
import { Logger } from 'n8n-core';
|
||||||
@@ -18,11 +19,25 @@ export class InsightsPruningService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly insightsByPeriodRepository: InsightsByPeriodRepository,
|
private readonly insightsByPeriodRepository: InsightsByPeriodRepository,
|
||||||
private readonly config: InsightsConfig,
|
private readonly config: InsightsConfig,
|
||||||
|
private readonly licenseState: LicenseState,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
) {
|
) {
|
||||||
this.logger = this.logger.scoped('insights');
|
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() {
|
startPruningTimer() {
|
||||||
strict(this.isStopped);
|
strict(this.isStopped);
|
||||||
this.clearPruningTimer();
|
this.clearPruningTimer();
|
||||||
@@ -57,7 +72,7 @@ export class InsightsPruningService {
|
|||||||
async pruneInsights() {
|
async pruneInsights() {
|
||||||
this.logger.info('Pruning old insights data');
|
this.logger.info('Pruning old insights data');
|
||||||
try {
|
try {
|
||||||
const result = await this.insightsByPeriodRepository.pruneOldData(this.config.maxAgeDays);
|
const result = await this.insightsByPeriodRepository.pruneOldData(this.pruningMaxAgeInDays);
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Deleted insights by period',
|
'Deleted insights by period',
|
||||||
result.affected ? { count: result.affected } : {},
|
result.affected ? { count: result.affected } : {},
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { InsightsByPeriodRepository } from './database/repositories/insights-by-
|
|||||||
import { InsightsCollectionService } from './insights-collection.service';
|
import { InsightsCollectionService } from './insights-collection.service';
|
||||||
import { InsightsCompactionService } from './insights-compaction.service';
|
import { InsightsCompactionService } from './insights-compaction.service';
|
||||||
import { InsightsPruningService } from './insights-pruning.service';
|
import { InsightsPruningService } from './insights-pruning.service';
|
||||||
import { InsightsConfig } from './insights.config';
|
|
||||||
|
|
||||||
const keyRangeToDays: Record<InsightsDateRange['key'], number> = {
|
const keyRangeToDays: Record<InsightsDateRange['key'], number> = {
|
||||||
day: 1,
|
day: 1,
|
||||||
@@ -35,20 +34,15 @@ export class InsightsService {
|
|||||||
private readonly collectionService: InsightsCollectionService,
|
private readonly collectionService: InsightsCollectionService,
|
||||||
private readonly pruningService: InsightsPruningService,
|
private readonly pruningService: InsightsPruningService,
|
||||||
private readonly licenseState: LicenseState,
|
private readonly licenseState: LicenseState,
|
||||||
private readonly config: InsightsConfig,
|
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
) {
|
) {
|
||||||
this.logger = this.logger.scoped('insights');
|
this.logger = this.logger.scoped('insights');
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPruningEnabled() {
|
|
||||||
return this.config.maxAgeDays > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
startTimers() {
|
startTimers() {
|
||||||
this.compactionService.startCompactionTimer();
|
this.compactionService.startCompactionTimer();
|
||||||
this.collectionService.startFlushingTimer();
|
this.collectionService.startFlushingTimer();
|
||||||
if (this.isPruningEnabled) {
|
if (this.pruningService.isPruningEnabled) {
|
||||||
this.pruningService.startPruningTimer();
|
this.pruningService.startPruningTimer();
|
||||||
}
|
}
|
||||||
this.logger.debug('Started compaction, flushing and pruning schedulers');
|
this.logger.debug('Started compaction, flushing and pruning schedulers');
|
||||||
|
|||||||
Reference in New Issue
Block a user