refactor(core): Avoid passing around static state like default timezone (no-changelog) (#7221)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2023-10-27 14:17:52 +02:00
committed by GitHub
parent 62c096710f
commit 35bb42c1b9
31 changed files with 76 additions and 224 deletions

View File

@@ -23,6 +23,7 @@ import { extend, extendOptional } from './Extensions';
import { extendedFunctions } from './Extensions/ExtendedFunctions';
import { extendSyntax } from './Extensions/ExpressionExtension';
import { evaluateExpression, setErrorHandler } from './ExpressionEvaluatorProxy';
import { getGlobalState } from './GlobalState';
const IS_FRONTEND_IN_DEV_MODE =
typeof process === 'object' &&
@@ -32,13 +33,13 @@ const IS_FRONTEND_IN_DEV_MODE =
const IS_FRONTEND = typeof process === 'undefined' || IS_FRONTEND_IN_DEV_MODE;
export const isSyntaxError = (error: unknown): error is SyntaxError =>
const isSyntaxError = (error: unknown): error is SyntaxError =>
error instanceof SyntaxError || (error instanceof Error && error.name === 'SyntaxError');
export const isExpressionError = (error: unknown): error is ExpressionError =>
const isExpressionError = (error: unknown): error is ExpressionError =>
error instanceof ExpressionError || error instanceof ExpressionExtensionError;
export const isTypeError = (error: unknown): error is TypeError =>
const isTypeError = (error: unknown): error is TypeError =>
error instanceof TypeError || (error instanceof Error && error.name === 'TypeError');
// Make sure that error get forwarded
@@ -58,11 +59,7 @@ const fnConstructors = {
};
export class Expression {
workflow: Workflow;
constructor(workflow: Workflow) {
this.workflow = workflow;
}
constructor(private readonly workflow: Workflow) {}
static resolveWithoutWorkflow(expression: string, data: IDataObject = {}) {
return tmpl.tmpl(expression, data);
@@ -84,7 +81,7 @@ export class Expression {
if (value instanceof Date) {
// We don't want to use JSON.stringify for dates since it disregards workflow timezone
result = DateTime.fromJSDate(value, {
zone: this.workflow.settings?.timezone ?? 'default',
zone: this.workflow.settings?.timezone ?? getGlobalState().defaultTimezone,
}).toISO();
} else {
result = JSON.stringify(value);
@@ -114,7 +111,6 @@ export class Expression {
activeNodeName: string,
connectionInputData: INodeExecutionData[],
mode: WorkflowExecuteMode,
timezone: string,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData?: IExecuteData,
returnObjectAsString = false,
@@ -143,7 +139,6 @@ export class Expression {
connectionInputData,
siblingParameters,
mode,
timezone,
additionalKeys,
executeData,
-1,
@@ -371,7 +366,6 @@ export class Expression {
node: INode,
parameterValue: string | boolean | undefined,
mode: WorkflowExecuteMode,
timezone: string,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData?: IExecuteData,
defaultValue?: boolean | number | string | unknown[],
@@ -399,7 +393,6 @@ export class Expression {
node.name,
connectionInputData,
mode,
timezone,
additionalKeys,
executeData,
) as boolean | number | string | undefined;
@@ -415,7 +408,6 @@ export class Expression {
node: INode,
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
mode: WorkflowExecuteMode,
timezone: string,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData?: IExecuteData,
defaultValue: NodeParameterValueType | undefined = undefined,
@@ -445,7 +437,6 @@ export class Expression {
node.name,
connectionInputData,
mode,
timezone,
additionalKeys,
executeData,
false,
@@ -461,7 +452,6 @@ export class Expression {
node.name,
connectionInputData,
mode,
timezone,
additionalKeys,
executeData,
false,
@@ -487,7 +477,6 @@ export class Expression {
activeNodeName: string,
connectionInputData: INodeExecutionData[],
mode: WorkflowExecuteMode,
timezone: string,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData?: IExecuteData,
returnObjectAsString = false,
@@ -513,7 +502,6 @@ export class Expression {
activeNodeName,
connectionInputData,
mode,
timezone,
additionalKeys,
executeData,
returnObjectAsString,
@@ -531,7 +519,6 @@ export class Expression {
activeNodeName,
connectionInputData,
mode,
timezone,
additionalKeys,
executeData,
returnObjectAsString,
@@ -551,7 +538,6 @@ export class Expression {
activeNodeName,
connectionInputData,
mode,
timezone,
additionalKeys,
executeData,
returnObjectAsString,

View File

@@ -0,0 +1,15 @@
import { deepCopy } from './utils';
export interface GlobalState {
defaultTimezone: string;
}
let globalState: GlobalState = { defaultTimezone: 'America/New_York' };
export function setGlobalState(state: GlobalState) {
globalState = state;
}
export function getGlobalState() {
return deepCopy(globalState);
}

View File

@@ -196,7 +196,6 @@ export abstract class ICredentialsHelper {
requestOptions: IHttpRequestOptions | IRequestOptionsSimplified,
workflow: Workflow,
node: INode,
defaultTimezone: string,
): Promise<IHttpRequestOptions>;
abstract preAuthentication(
@@ -217,7 +216,6 @@ export abstract class ICredentialsHelper {
nodeCredentials: INodeCredentialsDetails,
type: string,
mode: WorkflowExecuteMode,
defaultTimezone: string,
raw?: boolean,
expressionResolveValues?: ICredentialsExpressionResolveValues,
): Promise<ICredentialDataDecryptedObject>;
@@ -1862,7 +1860,6 @@ export interface IWorkflowExecuteAdditionalData {
instanceBaseUrl: string;
setExecutionStatus?: (status: ExecutionStatus) => void;
sendDataToUI?: (type: string, data: IDataObject | IDataObject[]) => void;
timezone: string;
webhookBaseUrl: string;
webhookWaitingBaseUrl: string;
webhookTestBaseUrl: string;

View File

@@ -885,7 +885,6 @@ export function getNodeWebhooks(
node,
webhookDescription.path,
mode,
additionalData.timezone,
{},
);
if (nodeWebhookPath === undefined) {
@@ -909,7 +908,6 @@ export function getNodeWebhooks(
node,
webhookDescription.isFullPath,
'internal',
additionalData.timezone,
{},
undefined,
false,
@@ -918,7 +916,6 @@ export function getNodeWebhooks(
node,
webhookDescription.restartWebhook,
'internal',
additionalData.timezone,
{},
undefined,
false,
@@ -929,7 +926,6 @@ export function getNodeWebhooks(
node,
webhookDescription.httpMethod,
mode,
additionalData.timezone,
{},
undefined,
'GET',
@@ -1037,7 +1033,6 @@ export function getNodeInputs(
node,
nodeTypeData.inputs,
'internal',
'',
{},
) || []) as ConnectionTypes[];
} catch (e) {
@@ -1060,7 +1055,6 @@ export function getNodeOutputs(
node,
nodeTypeData.outputs,
'internal',
'',
{},
) || []) as ConnectionTypes[];
} catch (e) {

View File

@@ -701,7 +701,6 @@ export class RoutingNode {
this.node.name,
this.connectionInputData,
this.mode,
this.additionalData.timezone,
additionalKeys ?? {},
executeData,
returnObjectAsString,

View File

@@ -28,6 +28,7 @@ import { ExpressionError } from './ExpressionError';
import type { Workflow } from './Workflow';
import { augmentArray, augmentObject } from './AugmentObject';
import { deepCopy } from './utils';
import { getGlobalState } from './GlobalState';
export function isResourceLocatorValue(value: unknown): value is INodeParameterResourceLocator {
return Boolean(
@@ -48,57 +49,28 @@ const isScriptingNode = (nodeName: string, workflow: Workflow) => {
};
export class WorkflowDataProxy {
private workflow: Workflow;
private runExecutionData: IRunExecutionData | null;
private defaultReturnRunIndex: number;
private runIndex: number;
private itemIndex: number;
private activeNodeName: string;
private contextNodeName: string;
private connectionInputData: INodeExecutionData[];
private siblingParameters: INodeParameters;
private mode: WorkflowExecuteMode;
private selfData: IDataObject;
private additionalKeys: IWorkflowDataProxyAdditionalKeys;
private executeData: IExecuteData | undefined;
private defaultTimezone: string;
private timezone: string;
// TODO: Clean that up at some point and move all the options into an options object
constructor(
workflow: Workflow,
private workflow: Workflow,
runExecutionData: IRunExecutionData | null,
runIndex: number,
itemIndex: number,
activeNodeName: string,
private runIndex: number,
private itemIndex: number,
private activeNodeName: string,
connectionInputData: INodeExecutionData[],
siblingParameters: INodeParameters,
mode: WorkflowExecuteMode,
defaultTimezone: string,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData?: IExecuteData,
defaultReturnRunIndex = -1,
selfData = {},
contextNodeName?: string,
private siblingParameters: INodeParameters,
private mode: WorkflowExecuteMode,
private additionalKeys: IWorkflowDataProxyAdditionalKeys,
private executeData?: IExecuteData,
private defaultReturnRunIndex = -1,
private selfData: IDataObject = {},
private contextNodeName: string = activeNodeName,
) {
this.activeNodeName = activeNodeName;
this.contextNodeName = contextNodeName || activeNodeName;
this.workflow = workflow;
this.runExecutionData = isScriptingNode(this.contextNodeName, workflow)
? runExecutionData !== null
? augmentObject(runExecutionData)
@@ -109,16 +81,7 @@ export class WorkflowDataProxy {
? augmentArray(connectionInputData)
: connectionInputData;
this.defaultReturnRunIndex = defaultReturnRunIndex;
this.runIndex = runIndex;
this.itemIndex = itemIndex;
this.siblingParameters = siblingParameters;
this.mode = mode;
this.defaultTimezone = defaultTimezone;
this.timezone = workflow.settings?.timezone ?? defaultTimezone;
this.selfData = selfData;
this.additionalKeys = additionalKeys;
this.executeData = executeData;
this.timezone = workflow.settings?.timezone ?? getGlobalState().defaultTimezone;
Settings.defaultZone = this.timezone;
}
@@ -266,7 +229,6 @@ export class WorkflowDataProxy {
that.activeNodeName,
that.connectionInputData,
that.mode,
that.timezone,
that.additionalKeys,
that.executeData,
false,
@@ -1185,7 +1147,6 @@ export class WorkflowDataProxy {
that.activeNodeName,
that.connectionInputData,
that.mode,
that.timezone,
that.additionalKeys,
that.executeData,
false,
@@ -1204,10 +1165,10 @@ export class WorkflowDataProxy {
this.connectionInputData,
that.siblingParameters,
that.mode,
that.defaultTimezone,
that.additionalKeys,
that.executeData,
defaultReturnRunIndex,
{},
that.contextNodeName,
);
return dataProxy.getDataProxy();

View File

@@ -9,6 +9,7 @@ export * from './Authentication';
export * from './Constants';
export * from './Cron';
export * from './DeferredPromise';
export * from './GlobalState';
export * from './Interfaces';
export * from './MessageEventBus';
export * from './ExecutionStatus';

View File

@@ -3,7 +3,6 @@
*/
import { DateTime, Duration, Interval } from 'luxon';
import { Expression } from '@/Expression';
import { Workflow } from '@/Workflow';
import * as Helpers from './Helpers';
import type { ExpressionTestEvaluation, ExpressionTestTransform } from './ExpressionFixtures/base';
@@ -21,6 +20,7 @@ for (const evaluator of ['tmpl', 'tournament'] as const) {
describe('getParameterValue()', () => {
const nodeTypes = Helpers.NodeTypes();
const workflow = new Workflow({
id: '1',
nodes: [
{
name: 'node',
@@ -35,10 +35,10 @@ for (const evaluator of ['tmpl', 'tournament'] as const) {
active: false,
nodeTypes,
});
const expression = new Expression(workflow);
const expression = workflow.expression;
const evaluate = (value: string) =>
expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', '', {});
expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', {});
it('should not be able to use global built-ins from denylist', () => {
expect(evaluate('={{document}}')).toEqual({});
@@ -84,9 +84,13 @@ for (const evaluator of ['tmpl', 'tournament'] as const) {
expect(evaluate('={{DateTime.now().toLocaleString()}}')).toEqual(
DateTime.now().toLocaleString(),
);
jest.useFakeTimers({ now: new Date() });
expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual(
Interval.after(new Date(), 100),
);
jest.useRealTimers();
expect(evaluate('={{Duration.fromMillis(100)}}')).toEqual(Duration.fromMillis(100));
expect(evaluate('={{new Object()}}')).toEqual(new Object());
@@ -170,6 +174,7 @@ for (const evaluator of ['tmpl', 'tournament'] as const) {
describe('Test all expression value fixtures', () => {
const nodeTypes = Helpers.NodeTypes();
const workflow = new Workflow({
id: '1',
nodes: [
{
name: 'node',
@@ -185,21 +190,11 @@ for (const evaluator of ['tmpl', 'tournament'] as const) {
nodeTypes,
});
const expression = new Expression(workflow);
const expression = workflow.expression;
const evaluate = (value: string, data: INodeExecutionData[]) => {
const itemIndex = data.length === 0 ? -1 : 0;
return expression.getParameterValue(
value,
null,
0,
itemIndex,
'node',
data,
'manual',
'',
{},
);
return expression.getParameterValue(value, null, 0, itemIndex, 'node', data, 'manual', {});
};
for (const t of baseFixtures) {

View File

@@ -3,7 +3,10 @@
*/
import { DateTime } from 'luxon';
import { evaluate, getLocalISOString, TEST_TIMEZONE } from './Helpers';
import { getGlobalState } from '@/GlobalState';
import { evaluate, getLocalISOString } from './Helpers';
const { defaultTimezone } = getGlobalState();
describe('Data Transformation Functions', () => {
describe('Date Data Transformation Functions', () => {
@@ -16,17 +19,17 @@ describe('Data Transformation Functions', () => {
test('.beginningOf("week") should work correctly on a date', () => {
expect(evaluate('={{ DateTime.local(2023, 1, 20).beginningOf("week") }}')).toEqual(
DateTime.local(2023, 1, 16, { zone: TEST_TIMEZONE }),
DateTime.local(2023, 1, 16, { zone: defaultTimezone }),
);
expect(evaluate('={{ new Date(2023, 0, 20).beginningOf("week") }}')).toEqual(
DateTime.local(2023, 1, 16, { zone: TEST_TIMEZONE }).toJSDate(),
DateTime.local(2023, 1, 16, { zone: defaultTimezone }).toJSDate(),
);
});
test('.beginningOf("week") should work correctly on a string', () => {
const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("week") }}');
const expectedDate = DateTime.local(2023, 1, 23, { zone: TEST_TIMEZONE }).toJSDate();
const expectedDate = DateTime.local(2023, 1, 23, { zone: defaultTimezone }).toJSDate();
if (evaluatedDate && evaluatedDate instanceof Date) {
expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString());
@@ -35,7 +38,7 @@ describe('Data Transformation Functions', () => {
test('.beginningOf("month") should work correctly on a string', () => {
const evaluatedDate = evaluate('={{ "2023-06-16".toDate().beginningOf("month") }}');
const expectedDate = DateTime.local(2023, 6, 1, { zone: TEST_TIMEZONE }).toJSDate();
const expectedDate = DateTime.local(2023, 6, 1, { zone: defaultTimezone }).toJSDate();
if (evaluatedDate && evaluatedDate instanceof Date) {
expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString());
@@ -44,7 +47,7 @@ describe('Data Transformation Functions', () => {
test('.beginningOf("year") should work correctly on a string', () => {
const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("year") }}');
const expectedDate = DateTime.local(2023, 1, 1, { zone: TEST_TIMEZONE }).toJSDate();
const expectedDate = DateTime.local(2023, 1, 1, { zone: defaultTimezone }).toJSDate();
if (evaluatedDate && evaluatedDate instanceof Date) {
expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString());
@@ -53,10 +56,10 @@ describe('Data Transformation Functions', () => {
test('.endOfMonth() should work correctly on a date', () => {
expect(evaluate('={{ DateTime.local(2023, 1, 16).endOfMonth() }}')).toEqual(
DateTime.local(2023, 1, 31, 23, 59, 59, 999, { zone: TEST_TIMEZONE }),
DateTime.local(2023, 1, 31, 23, 59, 59, 999, { zone: defaultTimezone }),
);
expect(evaluate('={{ new Date(2023, 0, 16).endOfMonth() }}')).toEqual(
DateTime.local(2023, 1, 31, 23, 59, 59, 999, { zone: TEST_TIMEZONE }).toJSDate(),
DateTime.local(2023, 1, 31, 23, 59, 59, 999, { zone: defaultTimezone }).toJSDate(),
);
});

View File

@@ -1,10 +1,7 @@
import type { IDataObject } from '@/Interfaces';
import { Expression } from '@/Expression';
import { Workflow } from '@/Workflow';
import * as Helpers from '../Helpers';
export const TEST_TIMEZONE = 'America/New_York';
export const nodeTypes = Helpers.NodeTypes();
export const workflow = new Workflow({
nodes: [
@@ -20,11 +17,8 @@ export const workflow = new Workflow({
connections: {},
active: false,
nodeTypes,
settings: {
timezone: TEST_TIMEZONE,
},
});
export const expression = new Expression(workflow);
export const expression = workflow.expression;
export const evaluate = (value: string, values?: IDataObject[]) =>
expression.getParameterValue(
@@ -35,7 +29,6 @@ export const evaluate = (value: string, values?: IDataObject[]) =>
'node',
values?.map((v) => ({ json: v })) ?? [],
'manual',
TEST_TIMEZONE,
{},
);

View File

@@ -37,6 +37,8 @@ import type { Workflow } from '@/Workflow';
import { WorkflowDataProxy } from '@/WorkflowDataProxy';
import { WorkflowHooks } from '@/WorkflowHooks';
import * as NodeHelpers from '@/NodeHelpers';
import { deepCopy } from '@/utils';
import { getGlobalState } from '@/GlobalState';
export interface INodeTypesObject {
[key: string]: INodeType;
@@ -127,7 +129,6 @@ export function getNodeParameter(
parameterName: string,
itemIndex: number,
mode: WorkflowExecuteMode,
timezone: string,
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData: IExecuteData,
fallbackValue?: any,
@@ -153,7 +154,6 @@ export function getNodeParameter(
node.name,
connectionInputData,
mode,
timezone,
additionalKeys,
);
} catch (e) {
@@ -240,7 +240,6 @@ export function getExecuteFunctions(
parameterName,
itemIndex,
mode,
additionalData.timezone,
{},
fallbackValue,
);
@@ -255,7 +254,7 @@ export function getExecuteFunctions(
return additionalData.restApiUrl;
},
getTimezone: (): string => {
return additionalData.timezone;
return workflow.settings.timezone ?? getGlobalState().defaultTimezone;
},
getExecuteData: (): IExecuteData => {
return executeData;
@@ -277,7 +276,6 @@ export function getExecuteFunctions(
connectionInputData,
{},
mode,
additionalData.timezone,
{},
executeData,
);
@@ -421,7 +419,7 @@ export function getExecuteSingleFunctions(
return additionalData.restApiUrl;
},
getTimezone: (): string => {
return additionalData.timezone;
return workflow.settings.timezone ?? getGlobalState().defaultTimezone;
},
getExecuteData: (): IExecuteData => {
return executeData;
@@ -444,7 +442,6 @@ export function getExecuteSingleFunctions(
parameterName,
itemIndex,
mode,
additionalData.timezone,
{},
fallbackValue,
);
@@ -466,7 +463,6 @@ export function getExecuteSingleFunctions(
connectionInputData,
{},
mode,
additionalData.timezone,
{},
executeData,
);
@@ -679,7 +675,6 @@ export function WorkflowExecuteAdditionalData(): IWorkflowExecuteAdditionalData
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {},
sendDataToUI: (message: string) => {},
restApiUrl: '',
timezone: 'America/New_York',
webhookBaseUrl: 'webhook',
webhookWaitingBaseUrl: 'webhook-waiting',
webhookTestBaseUrl: 'webhook-test',

View File

@@ -1182,7 +1182,6 @@ describe('Workflow', () => {
];
const nodeTypes = Helpers.NodeTypes();
const timezone = 'America/New_York';
for (const testData of tests) {
test(testData.description, () => {
@@ -1310,7 +1309,6 @@ describe('Workflow', () => {
activeNodeName,
connectionInputData,
'manual',
timezone,
{},
);
expect(result).toEqual(testData.output[parameterName]);
@@ -1465,7 +1463,6 @@ describe('Workflow', () => {
activeNodeName,
connectionInputData,
'manual',
timezone,
{},
);

View File

@@ -253,10 +253,9 @@ describe('WorkflowDataProxy', () => {
0,
0,
nameLastNode,
lastNodeConnectionInputData || [],
lastNodeConnectionInputData ?? [],
{},
'manual',
'America/New_York',
{},
executeData,
);