diff --git a/packages/core/src/ExtractValue.ts b/packages/core/src/ExtractValue.ts new file mode 100644 index 0000000000..ffd31aee30 --- /dev/null +++ b/packages/core/src/ExtractValue.ts @@ -0,0 +1,183 @@ +import { + INode, + INodeParameters, + INodeProperties, + INodePropertyCollection, + INodePropertyOptions, + INodeType, + NodeOperationError, + NodeParameterValueType, + NodeHelpers, + LoggerProxy, +} from 'n8n-workflow'; + +function findPropertyFromParameterName( + parameterName: string, + nodeType: INodeType, + node: INode, + nodeParameters: INodeParameters, +): INodePropertyOptions | INodeProperties | INodePropertyCollection { + let property: INodePropertyOptions | INodeProperties | INodePropertyCollection | undefined; + const paramParts = parameterName.split('.'); + let currentParamPath = ''; + + const findProp = ( + name: string, + options: Array, + ): INodePropertyOptions | INodeProperties | INodePropertyCollection | undefined => { + return options.find( + (i) => + i.name === name && + NodeHelpers.displayParameterPath(nodeParameters, i, currentParamPath, node), + ); + }; + + // eslint-disable-next-line no-restricted-syntax + for (const p of paramParts) { + const param = p.split('[')[0]; + if (!property) { + property = findProp(param, nodeType.description.properties); + } else if ('options' in property && property.options) { + property = findProp(param, property.options); + currentParamPath += `.${param}`; + } else if ('values' in property) { + property = findProp(param, property.values); + currentParamPath += `.${param}`; + } else { + throw new Error(`Couldn't not find property "${parameterName}"`); + } + if (!property) { + throw new Error(`Couldn't not find property "${parameterName}"`); + } + } + if (!property) { + throw new Error(`Couldn't not find property "${parameterName}"`); + } + + return property; +} + +function executeRegexExtractValue( + value: string, + regex: RegExp, + parameterName: string, + parameterDisplayName: string, +): NodeParameterValueType | object { + const extracted = regex.exec(value); + if (!extracted) { + throw new Error( + `ERROR: ${parameterDisplayName} parameter's value is invalid. This is likely because the URL entered is incorrect`, + ); + } + if (extracted.length < 2 || extracted.length > 2) { + throw new Error( + `Property "${parameterName}" has an invalid extractValue regex "${regex.source}". extractValue expects exactly one group to be returned.`, + ); + } + return extracted[1]; +} + +function extractValueRLC( + value: NodeParameterValueType | object, + property: INodeProperties, + parameterName: string, +): NodeParameterValueType | object { + // Not an RLC value + if (typeof value !== 'object' || !value || !('mode' in value) || !('value' in value)) { + return value; + } + const modeProp = (property.modes ?? []).find((i) => i.name === value.mode); + if (!modeProp) { + return value.value; + } + if (!('extractValue' in modeProp) || !modeProp.extractValue) { + return value.value; + } + + if (typeof value.value !== 'string') { + let typeName: string | undefined = value.value?.constructor.name; + if (value.value === null) { + typeName = 'null'; + } else if (typeName === undefined) { + typeName = 'undefined'; + } + LoggerProxy.error( + `Only strings can be passed to extractValue. Parameter "${parameterName}" passed "${typeName}"`, + ); + throw new Error( + `ERROR: ${property.displayName} parameter's value is invalid. Please enter a valid ${modeProp.displayName}.`, + ); + } + + if (modeProp.extractValue.type !== 'regex') { + throw new Error( + `Property "${parameterName}" has an unknown extractValue type "${ + modeProp.extractValue.type as string + }"`, + ); + } + + const regex = new RegExp(modeProp.extractValue.regex); + return executeRegexExtractValue(value.value, regex, parameterName, property.displayName); +} + +function extractValueOther( + value: NodeParameterValueType | object, + property: INodeProperties | INodePropertyCollection, + parameterName: string, +): NodeParameterValueType | object { + if (!('extractValue' in property) || !property.extractValue) { + return value; + } + + if (typeof value !== 'string') { + let typeName: string | undefined = value?.constructor.name; + if (value === null) { + typeName = 'null'; + } else if (typeName === undefined) { + typeName = 'undefined'; + } + LoggerProxy.error( + `Only strings can be passed to extractValue. Parameter "${parameterName}" passed "${typeName}"`, + ); + throw new Error( + `ERROR: ${property.displayName} parameter's value is invalid. Please enter a valid value.`, + ); + } + + if (property.extractValue.type !== 'regex') { + throw new Error( + `Property "${parameterName}" has an unknown extractValue type "${ + property.extractValue.type as string + }"`, + ); + } + + const regex = new RegExp(property.extractValue.regex); + return executeRegexExtractValue(value, regex, parameterName, property.displayName); +} + +export function extractValue( + value: NodeParameterValueType | object, + parameterName: string, + node: INode, + nodeType: INodeType, +): NodeParameterValueType | object { + let property: INodePropertyOptions | INodeProperties | INodePropertyCollection; + try { + property = findPropertyFromParameterName(parameterName, nodeType, node, node.parameters); + + // Definitely doesn't have value extractor + if (!('type' in property)) { + return value; + } + + if (property.type === 'resourceLocator') { + return extractValueRLC(value, property, parameterName); + } + return extractValueOther(value, property, parameterName); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + throw new NodeOperationError(node, error); + } +} diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index ec4d15f62a..75b50dafcc 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -59,6 +59,7 @@ import { LoggerProxy as Logger, IExecuteData, OAuth2GrantType, + IGetNodeParameterOptions, NodeParameterValueType, NodeExecutionWithMetadata, IPairedItemData, @@ -99,6 +100,7 @@ import { IWorkflowSettings, PLACEHOLDER_EMPTY_EXECUTION_ID, } from '.'; +import { extractValue } from './ExtractValue'; axios.defaults.timeout = 300000; // Prevent axios from adding x-form-www-urlencoded headers by default @@ -1725,6 +1727,7 @@ export function getNodeParameter( additionalKeys: IWorkflowDataProxyAdditionalKeys, executeData?: IExecuteData, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object { const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); if (nodeType === undefined) { @@ -1759,6 +1762,11 @@ export function getNodeParameter( throw e; } + // This is outside the try/catch because it throws errors with proper messages + if (options?.extractValue) { + returnData = extractValue(returnData, parameterName, node, nodeType); + } + return returnData; } @@ -1931,6 +1939,7 @@ export function getExecutePollFunctions( getNodeParameter: ( parameterName: string, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { const runExecutionData: IRunExecutionData | null = null; const itemIndex = 0; @@ -1950,6 +1959,7 @@ export function getExecutePollFunctions( getAdditionalKeys(additionalData), undefined, fallbackValue, + options, ); }, getRestApiUrl: (): string => { @@ -2084,6 +2094,7 @@ export function getExecuteTriggerFunctions( getNodeParameter: ( parameterName: string, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { const runExecutionData: IRunExecutionData | null = null; const itemIndex = 0; @@ -2103,6 +2114,7 @@ export function getExecuteTriggerFunctions( getAdditionalKeys(additionalData), undefined, fallbackValue, + options, ); }, getRestApiUrl: (): string => { @@ -2302,6 +2314,7 @@ export function getExecuteFunctions( parameterName: string, itemIndex: number, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { return getNodeParameter( workflow, @@ -2316,6 +2329,7 @@ export function getExecuteFunctions( getAdditionalKeys(additionalData), executeData, fallbackValue, + options, ); }, getMode: (): WorkflowExecuteMode => { @@ -2575,6 +2589,7 @@ export function getExecuteSingleFunctions( getNodeParameter: ( parameterName: string, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { return getNodeParameter( workflow, @@ -2589,6 +2604,7 @@ export function getExecuteSingleFunctions( getAdditionalKeys(additionalData), executeData, fallbackValue, + options, ); }, getWorkflow: () => { @@ -2742,6 +2758,7 @@ export function getLoadOptionsFunctions( getNodeParameter: ( parameterName: string, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { const runExecutionData: IRunExecutionData | null = null; const itemIndex = 0; @@ -2761,6 +2778,7 @@ export function getLoadOptionsFunctions( getAdditionalKeys(additionalData), undefined, fallbackValue, + options, ); }, getTimezone: (): string => { @@ -2868,6 +2886,7 @@ export function getExecuteHookFunctions( getNodeParameter: ( parameterName: string, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { const runExecutionData: IRunExecutionData | null = null; const itemIndex = 0; @@ -2887,6 +2906,7 @@ export function getExecuteHookFunctions( getAdditionalKeys(additionalData), undefined, fallbackValue, + options, ); }, getNodeWebhookUrl: (name: string): string | undefined => { @@ -3026,6 +3046,7 @@ export function getExecuteWebhookFunctions( getNodeParameter: ( parameterName: string, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { const runExecutionData: IRunExecutionData | null = null; const itemIndex = 0; @@ -3045,6 +3066,7 @@ export function getExecuteWebhookFunctions( getAdditionalKeys(additionalData), undefined, fallbackValue, + options, ); }, getParamsData(): object { diff --git a/packages/nodes-base/nodes/Airtable/Airtable.node.ts b/packages/nodes-base/nodes/Airtable/Airtable.node.ts index 1d72396f27..4ae888f4e1 100644 --- a/packages/nodes-base/nodes/Airtable/Airtable.node.ts +++ b/packages/nodes-base/nodes/Airtable/Airtable.node.ts @@ -508,8 +508,15 @@ export class Airtable implements INodeType { const operation = this.getNodeParameter('operation', 0) as string; - const application = this.getNodeParameter('application', 0) as string; - const table = encodeURI(this.getNodeParameter('table', 0) as string); + const application = this.getNodeParameter('application', 0, undefined, { + extractValue: true, + }) as string; + + const table = encodeURI( + this.getNodeParameter('table', 0, undefined, { + extractValue: true, + }) as string, + ); let returnAll = false; let endpoint = ''; diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts index 178cebd4a9..0d5eb544f4 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts @@ -2153,7 +2153,9 @@ export class GoogleDrive implements INodeType { // delete // ---------------------------------- - const driveId = this.getNodeParameter('driveId', i) as string; + const driveId = this.getNodeParameter('driveId', i, undefined, { + extractValue: true, + }) as string; await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`); @@ -2169,7 +2171,9 @@ export class GoogleDrive implements INodeType { // get // ---------------------------------- - const driveId = this.getNodeParameter('driveId', i) as string; + const driveId = this.getNodeParameter('driveId', i, undefined, { + extractValue: true, + }) as string; const qs: IDataObject = {}; @@ -2229,7 +2233,9 @@ export class GoogleDrive implements INodeType { // update // ---------------------------------- - const driveId = this.getNodeParameter('driveId', i) as string; + const driveId = this.getNodeParameter('driveId', i, undefined, { + extractValue: true, + }) as string; const body: IDataObject = {}; @@ -2256,7 +2262,9 @@ export class GoogleDrive implements INodeType { // copy // ---------------------------------- - const fileId = this.getNodeParameter('fileId', i) as string; + const fileId = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; const body: IDataObject = { fields: queryFields, @@ -2292,7 +2300,9 @@ export class GoogleDrive implements INodeType { // download // ---------------------------------- - const fileId = this.getNodeParameter('fileId', i) as string; + const fileId = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; const options = this.getNodeParameter('options', i) as IDataObject; const requestOptions = { @@ -2635,7 +2645,9 @@ export class GoogleDrive implements INodeType { // file:update // ---------------------------------- - const id = this.getNodeParameter('fileId', i) as string; + const id = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; const updateFields = this.getNodeParameter('updateFields', i, {}) as IDataObject; const qs: IDataObject = { @@ -2709,7 +2721,9 @@ export class GoogleDrive implements INodeType { // delete // ---------------------------------- - const fileId = this.getNodeParameter('fileId', i) as string; + const fileId = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; await googleApiRequest.call( this, @@ -2731,7 +2745,9 @@ export class GoogleDrive implements INodeType { returnData.push(...executionData); } if (operation === 'share') { - const fileId = this.getNodeParameter('fileId', i) as string; + const fileId = this.getNodeParameter('fileId', i, undefined, { + extractValue: true, + }) as string; const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject; diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index 0966bc15a9..ad1050adfe 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -239,7 +239,9 @@ export class Trello implements INodeType { requestMethod = 'DELETE'; - const id = this.getNodeParameter('id', i) as string; + const id = this.getNodeParameter('id', i, undefined, { + extractValue: true, + }) as string; endpoint = `boards/${id}`; } else if (operation === 'get') { @@ -249,7 +251,7 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const id = this.getNodeParameter('id', i); + const id = this.getNodeParameter('id', i, undefined, { extractValue: true }); endpoint = `boards/${id}`; @@ -262,7 +264,7 @@ export class Trello implements INodeType { requestMethod = 'PUT'; - const id = this.getNodeParameter('id', i); + const id = this.getNodeParameter('id', i, undefined, { extractValue: true }); endpoint = `boards/${id}`; @@ -365,7 +367,7 @@ export class Trello implements INodeType { requestMethod = 'DELETE'; - const id = this.getNodeParameter('id', i); + const id = this.getNodeParameter('id', i, undefined, { extractValue: true }); endpoint = `cards/${id}`; } else if (operation === 'get') { @@ -375,7 +377,7 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const id = this.getNodeParameter('id', i); + const id = this.getNodeParameter('id', i, undefined, { extractValue: true }); endpoint = `cards/${id}`; @@ -388,7 +390,7 @@ export class Trello implements INodeType { requestMethod = 'PUT'; - const id = this.getNodeParameter('id', i); + const id = this.getNodeParameter('id', i, undefined, { extractValue: true }); endpoint = `cards/${id}`; @@ -407,7 +409,9 @@ export class Trello implements INodeType { // create // ---------------------------------- - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; qs.text = this.getNodeParameter('text', i) as string; @@ -421,7 +425,9 @@ export class Trello implements INodeType { requestMethod = 'DELETE'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const commentId = this.getNodeParameter('commentId', i) as string; @@ -433,7 +439,9 @@ export class Trello implements INodeType { requestMethod = 'PUT'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const commentId = this.getNodeParameter('commentId', i) as string; @@ -552,7 +560,9 @@ export class Trello implements INodeType { requestMethod = 'POST'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const url = this.getNodeParameter('url', i) as string; @@ -571,7 +581,9 @@ export class Trello implements INodeType { requestMethod = 'DELETE'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const id = this.getNodeParameter('id', i) as string; @@ -583,7 +595,9 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const id = this.getNodeParameter('id', i) as string; @@ -598,7 +612,9 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; endpoint = `cards/${cardId}/attachments`; @@ -619,7 +635,9 @@ export class Trello implements INodeType { requestMethod = 'POST'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const name = this.getNodeParameter('name', i) as string; @@ -636,7 +654,9 @@ export class Trello implements INodeType { requestMethod = 'DELETE'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const id = this.getNodeParameter('id', i) as string; @@ -661,7 +681,9 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; endpoint = `cards/${cardId}/checklists`; @@ -674,7 +696,9 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const checkItemId = this.getNodeParameter('checkItemId', i) as string; @@ -703,7 +727,9 @@ export class Trello implements INodeType { requestMethod = 'DELETE'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const checkItemId = this.getNodeParameter('checkItemId', i) as string; @@ -715,7 +741,9 @@ export class Trello implements INodeType { requestMethod = 'PUT'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const checkItemId = this.getNodeParameter('checkItemId', i) as string; @@ -730,7 +758,9 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; endpoint = `cards/${cardId}/checkItemStates`; @@ -751,7 +781,9 @@ export class Trello implements INodeType { requestMethod = 'POST'; - const idBoard = this.getNodeParameter('boardId', i) as string; + const idBoard = this.getNodeParameter('boardId', i, undefined, { + extractValue: true, + }) as string; const name = this.getNodeParameter('name', i) as string; const color = this.getNodeParameter('color', i) as string; @@ -793,7 +825,9 @@ export class Trello implements INodeType { requestMethod = 'GET'; - const idBoard = this.getNodeParameter('boardId', i) as string; + const idBoard = this.getNodeParameter('boardId', i, undefined, { + extractValue: true, + }) as string; endpoint = `board/${idBoard}/labels`; @@ -820,7 +854,9 @@ export class Trello implements INodeType { requestMethod = 'POST'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const id = this.getNodeParameter('id', i) as string; @@ -834,7 +870,9 @@ export class Trello implements INodeType { requestMethod = 'DELETE'; - const cardId = this.getNodeParameter('cardId', i) as string; + const cardId = this.getNodeParameter('cardId', i, undefined, { + extractValue: true, + }) as string; const id = this.getNodeParameter('id', i) as string; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index e5da2277f0..d3f3908a60 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -543,6 +543,10 @@ export interface IN8nRequestOperationPaginationOffset extends IN8nRequestOperati }; } +export interface IGetNodeParameterOptions { + extractValue?: boolean; +} + export interface IExecuteFunctions { continueOnFail(): boolean; evaluateExpression(expression: string, itemIndex: number): NodeParameterValueType; @@ -564,6 +568,7 @@ export interface IExecuteFunctions { parameterName: string, itemIndex: number, fallbackValue?: any, + options?: IGetNodeParameterOptions, ): NodeParameterValueType | object; getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData; getWorkflowStaticData(type: string): IDataObject; @@ -601,7 +606,11 @@ export interface IExecuteSingleFunctions { getItemIndex(): number; getMode(): WorkflowExecuteMode; getNode(): INode; - getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValueType | object; + getNodeParameter( + parameterName: string, + fallbackValue?: any, + options?: IGetNodeParameterOptions, + ): NodeParameterValueType | object; getRestApiUrl(): string; getTimezone(): string; getExecuteData(): IExecuteData; @@ -647,7 +656,11 @@ export interface ICredentialTestFunctions { export interface ILoadOptionsFunctions { getCredentials(type: string): Promise; getNode(): INode; - getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValueType | object; + getNodeParameter( + parameterName: string, + fallbackValue?: any, + options?: IGetNodeParameterOptions, + ): NodeParameterValueType | object; getCurrentNodeParameter(parameterName: string): NodeParameterValueType | object | undefined; getCurrentNodeParameters(): INodeParameters | undefined; getTimezone(): string; @@ -680,7 +693,11 @@ export interface IHookFunctions { getActivationMode(): WorkflowActivateMode; getNode(): INode; getNodeWebhookUrl: (name: string) => string | undefined; - getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValueType | object; + getNodeParameter( + parameterName: string, + fallbackValue?: any, + options?: IGetNodeParameterOptions, + ): NodeParameterValueType | object; getTimezone(): string; getWebhookDescription(name: string): IWebhookDescription | undefined; getWebhookName(): string; @@ -706,7 +723,11 @@ export interface IPollFunctions { getMode(): WorkflowExecuteMode; getActivationMode(): WorkflowActivateMode; getNode(): INode; - getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValueType | object; + getNodeParameter( + parameterName: string, + fallbackValue?: any, + options?: IGetNodeParameterOptions, + ): NodeParameterValueType | object; getRestApiUrl(): string; getTimezone(): string; getWorkflow(): IWorkflowMetadata; @@ -736,7 +757,11 @@ export interface ITriggerFunctions { getMode(): WorkflowExecuteMode; getActivationMode(): WorkflowActivateMode; getNode(): INode; - getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValueType | object; + getNodeParameter( + parameterName: string, + fallbackValue?: any, + options?: IGetNodeParameterOptions, + ): NodeParameterValueType | object; getRestApiUrl(): string; getTimezone(): string; getWorkflow(): IWorkflowMetadata; @@ -761,7 +786,11 @@ export interface IWebhookFunctions { getHeaderData(): object; getMode(): WorkflowExecuteMode; getNode(): INode; - getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValueType | object; + getNodeParameter( + parameterName: string, + fallbackValue?: any, + options?: IGetNodeParameterOptions, + ): NodeParameterValueType | object; getNodeWebhookUrl: (name: string) => string | undefined; getParamsData(): object; getQueryData(): object; diff --git a/packages/workflow/src/RoutingNode.ts b/packages/workflow/src/RoutingNode.ts index 0973d3f377..6cbc35ef20 100644 --- a/packages/workflow/src/RoutingNode.ts +++ b/packages/workflow/src/RoutingNode.ts @@ -643,8 +643,15 @@ export class RoutingNode { if (nodeProperties.routing) { let parameterValue: string | undefined; if (basePath + nodeProperties.name && 'type' in nodeProperties) { + // Extract value if it has extractValue defined or if it's a + // resourceLocator component. Resource locators are likely to have extractors + // and we can't know if the mode has one unless we dig all the way in. + const shouldExtractValue = + nodeProperties.extractValue !== undefined || nodeProperties.type === 'resourceLocator'; parameterValue = executeSingleFunctions.getNodeParameter( basePath + nodeProperties.name, + undefined, + { extractValue: shouldExtractValue }, ) as string; }