mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat: Enable partial exections v2 by default (#13344)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
073b05b10c
commit
29ae2396c9
|
@ -489,7 +489,11 @@ describe('Execution', () => {
|
|||
|
||||
cy.wait('@workflowRun').then((interception) => {
|
||||
expect(interception.request.body).to.have.property('runData').that.is.an('object');
|
||||
const expectedKeys = ['When clicking ‘Test workflow’', 'fetch 5 random users'];
|
||||
const expectedKeys = [
|
||||
'When clicking ‘Test workflow’',
|
||||
'fetch 5 random users',
|
||||
'do something with them',
|
||||
];
|
||||
|
||||
const { runData } = interception.request.body as Record<string, object>;
|
||||
expect(Object.keys(runData)).to.have.lengthOf(expectedKeys.length);
|
||||
|
|
|
@ -4,7 +4,7 @@ const canvas = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('Manual partial execution', () => {
|
||||
it('should execute parent nodes with no run data only once', () => {
|
||||
it('should not execute parent nodes with no run data', () => {
|
||||
canvas.actions.visit();
|
||||
|
||||
cy.fixture('manual-partial-execution.json').then((data) => {
|
||||
|
@ -22,8 +22,8 @@ describe('Manual partial execution', () => {
|
|||
|
||||
canvas.actions.openNode('Webhook1');
|
||||
|
||||
ndv.getters.nodeRunSuccessIndicator().should('exist');
|
||||
ndv.getters.nodeRunTooltipIndicator().should('exist');
|
||||
ndv.getters.outputRunSelector().should('not.exist'); // single run
|
||||
ndv.getters.nodeRunSuccessIndicator().should('not.exist');
|
||||
ndv.getters.nodeRunTooltipIndicator().should('not.exist');
|
||||
ndv.getters.outputRunSelector().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -181,6 +181,5 @@ export interface FrontendSettings {
|
|||
easyAIWorkflowOnboarded: boolean;
|
||||
partialExecution: {
|
||||
version: 1 | 2;
|
||||
enforce: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,9 +4,5 @@ import { Config, Env } from '../decorators';
|
|||
export class PartialExecutionsConfig {
|
||||
/** Partial execution logic version to use by default. */
|
||||
@Env('N8N_PARTIAL_EXECUTION_VERSION_DEFAULT')
|
||||
version: 1 | 2 = 1;
|
||||
|
||||
/** Set this to true to enforce using the default version. Users cannot use the other version then by setting a local storage key. */
|
||||
@Env('N8N_PARTIAL_EXECUTION_ENFORCE_VERSION')
|
||||
enforce: boolean = false;
|
||||
version: 1 | 2 = 2;
|
||||
}
|
||||
|
|
|
@ -305,8 +305,7 @@ describe('GlobalConfig', () => {
|
|||
disabled: false,
|
||||
},
|
||||
partialExecutions: {
|
||||
version: 1,
|
||||
enforce: false,
|
||||
version: 2,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,15 +1,69 @@
|
|||
import { mockLogger } from '@test/mocking';
|
||||
import { captor, mock } from 'jest-mock-extended';
|
||||
import type { Logger } from 'n8n-core';
|
||||
|
||||
import { DeprecationService } from '../deprecation.service';
|
||||
|
||||
describe('DeprecationService', () => {
|
||||
const toTest = (envVar: string, value: string, mustWarn: boolean) => {
|
||||
process.env[envVar] = value;
|
||||
const deprecationService = new DeprecationService(mockLogger());
|
||||
const logger = mock<Logger>();
|
||||
const deprecationService = new DeprecationService(logger);
|
||||
|
||||
beforeEach(() => {
|
||||
// Ignore environment variables coming in from the environment when running
|
||||
// this test suite.
|
||||
process.env = {};
|
||||
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('N8N_PARTIAL_EXECUTION_VERSION_DEFAULT', () => {
|
||||
test('supports multiple warnings for the same environment variable', () => {
|
||||
// ARRANGE
|
||||
process.env.N8N_PARTIAL_EXECUTION_VERSION_DEFAULT = '1';
|
||||
const dataCaptor = captor();
|
||||
|
||||
// ACT
|
||||
deprecationService.warn();
|
||||
|
||||
expect(deprecationService.mustWarn(envVar)).toBe(mustWarn);
|
||||
// ASSERT
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1);
|
||||
expect(logger.warn).toHaveBeenCalledWith(dataCaptor);
|
||||
expect(dataCaptor.value.split('\n')).toEqual(
|
||||
expect.arrayContaining([
|
||||
' - N8N_PARTIAL_EXECUTION_VERSION_DEFAULT -> Version 1 of partial executions is deprecated and will be removed as early as v1.85.0',
|
||||
' - N8N_PARTIAL_EXECUTION_VERSION_DEFAULT -> This environment variable is internal and should not be set.',
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const toTest = (envVar: string, value: string | undefined, mustWarn: boolean) => {
|
||||
const originalEnv = process.env[envVar];
|
||||
try {
|
||||
// ARRANGE
|
||||
if (value) {
|
||||
process.env[envVar] = value;
|
||||
} else {
|
||||
delete process.env[envVar];
|
||||
}
|
||||
|
||||
// ACT
|
||||
deprecationService.warn();
|
||||
|
||||
// ASSERT
|
||||
if (mustWarn) {
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1);
|
||||
expect(logger.warn.mock.lastCall?.[0]).toMatch(envVar);
|
||||
} else {
|
||||
expect(logger.warn.mock.lastCall?.[0] ?? '').not.toMatch(envVar);
|
||||
}
|
||||
} finally {
|
||||
// CLEANUP
|
||||
if (originalEnv) {
|
||||
process.env[envVar] = originalEnv;
|
||||
} else {
|
||||
delete process.env[envVar];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test.each([
|
||||
|
@ -18,7 +72,10 @@ describe('DeprecationService', () => {
|
|||
['EXECUTIONS_DATA_PRUNE_TIMEOUT', '1', true],
|
||||
['N8N_CONFIG_FILES', '1', true],
|
||||
['N8N_SKIP_WEBHOOK_DEREGISTRATION_SHUTDOWN', '1', true],
|
||||
])('should detect when %s is in use', (envVar, value, mustWarn) => {
|
||||
['N8N_PARTIAL_EXECUTION_VERSION_DEFAULT', '1', true],
|
||||
['N8N_PARTIAL_EXECUTION_VERSION_DEFAULT', '2', true],
|
||||
['N8N_PARTIAL_EXECUTION_VERSION_DEFAULT', undefined, false],
|
||||
])('should detect when %s is `%s`', (envVar, value, mustWarn) => {
|
||||
toTest(envVar, value, mustWarn);
|
||||
});
|
||||
|
||||
|
@ -48,15 +105,7 @@ describe('DeprecationService', () => {
|
|||
['true', false],
|
||||
[undefined /* warnIfMissing */, true],
|
||||
])('should handle value: %s', (value, mustWarn) => {
|
||||
if (value === undefined) {
|
||||
delete process.env[envVar];
|
||||
} else {
|
||||
process.env[envVar] = value;
|
||||
}
|
||||
|
||||
const deprecationService = new DeprecationService(mockLogger());
|
||||
deprecationService.warn();
|
||||
expect(deprecationService.mustWarn(envVar)).toBe(mustWarn);
|
||||
toTest(envVar, value, mustWarn);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Service } from '@n8n/di';
|
||||
import { Logger } from 'n8n-core';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
type EnvVarName = string;
|
||||
|
||||
|
@ -49,32 +48,41 @@ export class DeprecationService {
|
|||
checkValue: (value?: string) => value?.toLowerCase() !== 'true' && value !== '1',
|
||||
warnIfMissing: true,
|
||||
},
|
||||
{
|
||||
envVar: 'N8N_PARTIAL_EXECUTION_VERSION_DEFAULT',
|
||||
checkValue: (value: string) => value === '1',
|
||||
message:
|
||||
'Version 1 of partial executions is deprecated and will be removed as early as v1.85.0',
|
||||
},
|
||||
{
|
||||
envVar: 'N8N_PARTIAL_EXECUTION_VERSION_DEFAULT',
|
||||
message: 'This environment variable is internal and should not be set.',
|
||||
},
|
||||
];
|
||||
|
||||
/** Runtime state of deprecation-related env vars. */
|
||||
private readonly state: Record<EnvVarName, { mustWarn: boolean }> = {};
|
||||
private readonly state: Map<Deprecation, { mustWarn: boolean }> = new Map();
|
||||
|
||||
constructor(private readonly logger: Logger) {}
|
||||
|
||||
warn() {
|
||||
this.deprecations.forEach((d) => {
|
||||
const envValue = process.env[d.envVar];
|
||||
this.state[d.envVar] = {
|
||||
this.state.set(d, {
|
||||
mustWarn:
|
||||
(d.warnIfMissing !== undefined && envValue === undefined) ||
|
||||
(d.checkValue ? d.checkValue(envValue) : envValue !== undefined),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const mustWarn = Object.entries(this.state)
|
||||
.filter(([, d]) => d.mustWarn)
|
||||
.map(([envVar]) => {
|
||||
const deprecation = this.deprecations.find((d) => d.envVar === envVar);
|
||||
if (!deprecation) {
|
||||
throw new ApplicationError(`Deprecation not found for env var: ${envVar}`);
|
||||
const mustWarn: Deprecation[] = [];
|
||||
for (const [deprecation, metadata] of this.state.entries()) {
|
||||
if (!metadata.mustWarn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mustWarn.push(deprecation);
|
||||
}
|
||||
return deprecation;
|
||||
});
|
||||
|
||||
if (mustWarn.length === 0) return;
|
||||
|
||||
|
@ -87,8 +95,4 @@ export class DeprecationService {
|
|||
|
||||
this.logger.warn(`\n${header}:\n${deprecations}`);
|
||||
}
|
||||
|
||||
mustWarn(envVar: string) {
|
||||
return this.state[envVar]?.mustWarn ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,6 @@ export const defaultSettings: FrontendSettings = {
|
|||
easyAIWorkflowOnboarded: false,
|
||||
partialExecution: {
|
||||
version: 1,
|
||||
enforce: false,
|
||||
},
|
||||
folders: {
|
||||
enabled: false,
|
||||
|
|
|
@ -117,44 +117,32 @@ describe('settings.store', () => {
|
|||
{
|
||||
name: 'pick the default',
|
||||
default: 1 as const,
|
||||
enforce: false,
|
||||
userVersion: -1,
|
||||
result: 1,
|
||||
},
|
||||
{
|
||||
name: "pick the user' choice",
|
||||
default: 1 as const,
|
||||
enforce: false,
|
||||
userVersion: 2,
|
||||
name: 'pick the default',
|
||||
default: 2 as const,
|
||||
userVersion: -1,
|
||||
result: 2,
|
||||
},
|
||||
{
|
||||
name: 'enforce the default',
|
||||
name: "pick the user's choice",
|
||||
default: 1 as const,
|
||||
enforce: true,
|
||||
userVersion: 2,
|
||||
result: 1,
|
||||
},
|
||||
{
|
||||
name: 'enforce the default',
|
||||
default: 2 as const,
|
||||
enforce: true,
|
||||
userVersion: 1,
|
||||
result: 2,
|
||||
},
|
||||
{
|
||||
name: 'handle values that used to be allowed in local storage',
|
||||
default: 1 as const,
|
||||
enforce: false,
|
||||
userVersion: 0,
|
||||
result: 1,
|
||||
},
|
||||
])('%name', async ({ default: defaultVersion, userVersion, enforce, result }) => {
|
||||
])('%name', async ({ default: defaultVersion, userVersion, result }) => {
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
settingsStore.settings.partialExecution = {
|
||||
version: defaultVersion,
|
||||
enforce,
|
||||
};
|
||||
vi.mocked(useLocalStorage).mockReturnValueOnce(ref(userVersion));
|
||||
|
||||
|
|
|
@ -103,16 +103,11 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||
|
||||
const partialExecutionVersion = computed(() => {
|
||||
const defaultVersion = settings.value.partialExecution?.version ?? 1;
|
||||
const enforceVersion = settings.value.partialExecution?.enforce ?? false;
|
||||
// -1 means we pick the defaultVersion
|
||||
// 1 is the old flow
|
||||
// 2 is the new flow
|
||||
const userVersion = useLocalStorage('PartialExecution.version', -1).value;
|
||||
const version = enforceVersion
|
||||
? defaultVersion
|
||||
: userVersion === -1
|
||||
? defaultVersion
|
||||
: userVersion;
|
||||
const version = userVersion === -1 ? defaultVersion : userVersion;
|
||||
|
||||
// For backwards compatibility, e.g. if the user has 0 in their local
|
||||
// storage, which used to be allowed, but not anymore.
|
||||
|
|
|
@ -682,31 +682,21 @@ describe('useWorkflowsStore', () => {
|
|||
});
|
||||
|
||||
test.each([
|
||||
// enforce true cases - the version is always the defaultVersion
|
||||
[-1, 1, true, 1], // enforce true, use default (1)
|
||||
[0, 1, true, 1], // enforce true, use default (1)
|
||||
[1, 1, true, 1], // enforce true, use default (1)
|
||||
[2, 1, true, 1], // enforce true, use default (1)
|
||||
[-1, 2, true, 2], // enforce true, use default (2)
|
||||
[0, 2, true, 2], // enforce true, use default (2)
|
||||
[1, 2, true, 2], // enforce true, use default (2)
|
||||
[2, 2, true, 2], // enforce true, use default (2)
|
||||
|
||||
// enforce false cases - check userVersion behavior
|
||||
[-1, 1, false, 1], // userVersion -1, use default (1)
|
||||
[0, 1, false, 1], // userVersion 0, invalid, use default (1)
|
||||
[1, 1, false, 1], // userVersion 1, valid, use userVersion (1)
|
||||
[2, 1, false, 2], // userVersion 2, valid, use userVersion (2)
|
||||
[-1, 2, false, 2], // userVersion -1, use default (2)
|
||||
[0, 2, false, 1], // userVersion 0, invalid, use default (2)
|
||||
[1, 2, false, 1], // userVersion 1, valid, use userVersion (1)
|
||||
[2, 2, false, 2], // userVersion 2, valid, use userVersion (2)
|
||||
] as Array<[number, 1 | 2, boolean, number]>)(
|
||||
// check userVersion behavior
|
||||
[-1, 1, 1], // userVersion -1, use default (1)
|
||||
[0, 1, 1], // userVersion 0, invalid, use default (1)
|
||||
[1, 1, 1], // userVersion 1, valid, use userVersion (1)
|
||||
[2, 1, 2], // userVersion 2, valid, use userVersion (2)
|
||||
[-1, 2, 2], // userVersion -1, use default (2)
|
||||
[0, 2, 1], // userVersion 0, invalid, use default (2)
|
||||
[1, 2, 1], // userVersion 1, valid, use userVersion (1)
|
||||
[2, 2, 2], // userVersion 2, valid, use userVersion (2)
|
||||
] as Array<[number, 1 | 2, number]>)(
|
||||
'when { userVersion:%s, defaultVersion:%s, enforced:%s } run workflow should use partial execution version %s',
|
||||
async (userVersion, defaultVersion, enforce, expectedVersion) => {
|
||||
async (userVersion, defaultVersion, expectedVersion) => {
|
||||
vi.mocked(useLocalStorage).mockReturnValueOnce(ref(userVersion));
|
||||
settingsStore.settings = {
|
||||
partialExecution: { version: defaultVersion, enforce },
|
||||
partialExecution: { version: defaultVersion },
|
||||
} as FrontendSettings;
|
||||
|
||||
const workflowData = { id: '1', nodes: [], connections: {} };
|
||||
|
|
Loading…
Reference in a new issue