refactor(core): Port workflow history config (#14689)

This commit is contained in:
Iván Ovejero
2025-04-17 13:09:40 +02:00
committed by GitHub
parent 8325ca1a45
commit 3cdc8b41be
8 changed files with 43 additions and 34 deletions

View File

@@ -0,0 +1,12 @@
import { Config, Env } from '../decorators';
@Config
export class WorkflowHistoryConfig {
/** Whether to save workflow history versions. */
@Env('N8N_WORKFLOW_HISTORY_ENABLED')
enabled: boolean = true;
/** Time (in hours) to keep workflow history versions for. `-1` means forever. */
@Env('N8N_WORKFLOW_HISTORY_PRUNE_TIME')
pruneTime: number = -1;
}

View File

@@ -27,6 +27,7 @@ import { TagsConfig } from './configs/tags.config';
import { TemplatesConfig } from './configs/templates.config';
import { UserManagementConfig } from './configs/user-management.config';
import { VersionNotificationsConfig } from './configs/version-notifications.config';
import { WorkflowHistoryConfig } from './configs/workflow-history.config';
import { WorkflowsConfig } from './configs/workflows.config';
import { Config, Env, Nested } from './decorators';
@@ -149,4 +150,7 @@ export class GlobalConfig {
@Nested
partialExecutions: PartialExecutionsConfig;
@Nested
workflowHistory: WorkflowHistoryConfig;
}

View File

@@ -315,6 +315,10 @@ describe('GlobalConfig', () => {
partialExecutions: {
version: 2,
},
workflowHistory: {
enabled: true,
pruneTime: -1,
},
};
it('should use all default values when no env variables are defined', () => {

View File

@@ -301,22 +301,6 @@ export const schema = {
},
},
workflowHistory: {
enabled: {
doc: 'Whether to save workflow history versions',
format: Boolean,
default: true,
env: 'N8N_WORKFLOW_HISTORY_ENABLED',
},
pruneTime: {
doc: 'Time (in hours) to keep workflow history versions for',
format: Number,
default: -1,
env: 'N8N_WORKFLOW_HISTORY_PRUNE_TIME',
},
},
proxy_hops: {
format: Number,
default: 0,

View File

@@ -320,7 +320,7 @@ export class FrontendService {
debugInEditor: this.license.isDebugInEditorLicensed(),
binaryDataS3: isS3Available && isS3Selected && isS3Licensed,
workflowHistory:
this.license.isWorkflowHistoryLicensed() && config.getEnv('workflowHistory.enabled'),
this.license.isWorkflowHistoryLicensed() && this.globalConfig.workflowHistory.enabled,
workerView: this.license.isWorkerViewLicensed(),
advancedPermissions: this.license.isAdvancedPermissionsLicensed(),
apiKeyScopes: this.license.isApiKeyScopesEnabled(),
@@ -344,7 +344,7 @@ export class FrontendService {
this.settings.variables.limit = this.license.getVariablesLimit();
}
if (this.license.isWorkflowHistoryLicensed() && config.getEnv('workflowHistory.enabled')) {
if (this.globalConfig.workflowHistory.enabled && this.license.isWorkflowHistoryLicensed()) {
Object.assign(this.settings.workflowHistory, {
pruneTime: getWorkflowHistoryPruneTime(),
licensePruneTime: getWorkflowHistoryLicensePruneTime(),

View File

@@ -1,9 +1,12 @@
import config from '@/config';
import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import { License } from '@/license';
import { getWorkflowHistoryPruneTime } from '@/workflows/workflow-history.ee/workflow-history-helper.ee';
import { mockInstance } from '@test/mocking';
let licensePruneTime = -1;
const globalConfig = Container.get(GlobalConfig);
beforeAll(async () => {
mockInstance(License, {
@@ -15,39 +18,39 @@ beforeAll(async () => {
beforeEach(() => {
licensePruneTime = -1;
config.set('workflowHistory.pruneTime', -1);
globalConfig.workflowHistory.pruneTime = -1;
});
describe('getWorkflowHistoryPruneTime', () => {
test('should return -1 (infinite) if config and license are -1', () => {
licensePruneTime = -1;
config.set('workflowHistory.pruneTime', -1);
globalConfig.workflowHistory.pruneTime = -1;
expect(getWorkflowHistoryPruneTime()).toBe(-1);
});
test('should return config time if license is infinite and config is not', () => {
licensePruneTime = -1;
config.set('workflowHistory.pruneTime', 24);
globalConfig.workflowHistory.pruneTime = 24;
expect(getWorkflowHistoryPruneTime()).toBe(24);
});
test('should return license time if config is infinite and license is not', () => {
licensePruneTime = 25;
config.set('workflowHistory.pruneTime', -1);
globalConfig.workflowHistory.pruneTime = -1;
expect(getWorkflowHistoryPruneTime()).toBe(25);
});
test('should return lowest of config and license time if both are not -1', () => {
licensePruneTime = 26;
config.set('workflowHistory.pruneTime', 100);
globalConfig.workflowHistory.pruneTime = 100;
expect(getWorkflowHistoryPruneTime()).toBe(26);
licensePruneTime = 100;
config.set('workflowHistory.pruneTime', 27);
globalConfig.workflowHistory.pruneTime = 27;
expect(getWorkflowHistoryPruneTime()).toBe(27);
});

View File

@@ -1,6 +1,6 @@
import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import config from '@/config';
import { License } from '@/license';
export function isWorkflowHistoryLicensed() {
@@ -9,7 +9,7 @@ export function isWorkflowHistoryLicensed() {
}
export function isWorkflowHistoryEnabled() {
return isWorkflowHistoryLicensed() && config.getEnv('workflowHistory.enabled');
return isWorkflowHistoryLicensed() && Container.get(GlobalConfig).workflowHistory.enabled;
}
export function getWorkflowHistoryLicensePruneTime() {
@@ -19,7 +19,7 @@ export function getWorkflowHistoryLicensePruneTime() {
// Time in hours
export function getWorkflowHistoryPruneTime(): number {
const licenseTime = Container.get(License).getWorkflowHistoryPruneLimit();
const configTime = config.getEnv('workflowHistory.pruneTime');
const configTime = Container.get(GlobalConfig).workflowHistory.pruneTime;
// License is infinite and config time is infinite
if (licenseTime === -1) {

View File

@@ -1,8 +1,8 @@
import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import { In } from '@n8n/typeorm';
import { DateTime } from 'luxon';
import config from '@/config';
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
import { License } from '@/license';
import { WorkflowHistoryManager } from '@/workflows/workflow-history.ee/workflow-history-manager.ee';
@@ -16,19 +16,21 @@ describe('Workflow History Manager', () => {
const license = mockInstance(License);
let repo: WorkflowHistoryRepository;
let manager: WorkflowHistoryManager;
let globalConfig: GlobalConfig;
beforeAll(async () => {
await testDb.init();
repo = Container.get(WorkflowHistoryRepository);
manager = Container.get(WorkflowHistoryManager);
globalConfig = Container.get(GlobalConfig);
});
beforeEach(async () => {
await testDb.truncate(['Workflow']);
jest.clearAllMocks();
config.set('workflowHistory.enabled', true);
config.set('workflowHistory.pruneTime', -1);
globalConfig.workflowHistory.enabled = true;
globalConfig.workflowHistory.pruneTime = -1;
license.isWorkflowHistoryLicensed.mockReturnValue(true);
license.getWorkflowHistoryPruneLimit.mockReturnValue(-1);
@@ -64,7 +66,7 @@ describe('Workflow History Manager', () => {
});
test('should not prune when licensed but disabled', async () => {
config.set('workflowHistory.enabled', false);
globalConfig.workflowHistory.enabled = false;
await createWorkflowHistory();
await pruneAndAssertCount();
});
@@ -75,7 +77,7 @@ describe('Workflow History Manager', () => {
});
test('should prune when config prune time is not -1 (infinite)', async () => {
config.set('workflowHistory.pruneTime', 24);
globalConfig.workflowHistory.pruneTime = 24;
await createWorkflowHistory();
await pruneAndAssertCount(0);
});
@@ -88,7 +90,7 @@ describe('Workflow History Manager', () => {
});
test('should only prune versions older than prune time', async () => {
config.set('workflowHistory.pruneTime', 24);
globalConfig.workflowHistory.pruneTime = 24;
const recentVersions = await createWorkflowHistory(0);
const oldVersions = await createWorkflowHistory();