From c5d38ed189c74168f087bdd71bcac5b331c8d6a3 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Wed, 11 Sep 2024 13:06:17 +0200 Subject: [PATCH 01/19] fix(editor): Add missing node parameter values --- .../editor-ui/src/stores/assistant.store.ts | 9 ++- .../editor-ui/src/types/assistant.types.ts | 5 +- .../editor-ui/src/utils/nodeTypesUtils.ts | 65 ++++++------------- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/packages/editor-ui/src/stores/assistant.store.ts b/packages/editor-ui/src/stores/assistant.store.ts index 7ea0b84bc1b68..1afc0186697f7 100644 --- a/packages/editor-ui/src/stores/assistant.store.ts +++ b/packages/editor-ui/src/stores/assistant.store.ts @@ -16,7 +16,7 @@ import { useRoute } from 'vue-router'; import { useSettingsStore } from './settings.store'; import { assert } from '@/utils/assert'; import { useWorkflowsStore } from './workflows.store'; -import type { INodeParameters } from 'n8n-workflow'; +import type { IDataObject, INodeParameters } from 'n8n-workflow'; import { deepCopy } from 'n8n-workflow'; import { ndvEventBus, codeNodeEditorEventBus } from '@/event-bus'; import { useNDVStore } from './ndv.store'; @@ -376,6 +376,12 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { const availableAuthOptions = getNodeAuthOptions(nodeType); authType = availableAuthOptions.find((option) => option.value === credentialInUse); } + const inputData = ndvStore.ndvInputData[0].json; + const inputNodeName = ndvStore.input.nodeName; + const nodeInputData: { inputNodeName?: string; inputData?: IDataObject } = { + inputNodeName, + inputData, + }; addLoadingAssistantMessage(locale.baseText('aiAssistant.thinkingSteps.analyzingError')); openChat(); @@ -391,6 +397,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { }, error: context.error, node: pruneNodeProperties(context.node, ['position']), + nodeInputData, executionSchema: schemas, authType, }, diff --git a/packages/editor-ui/src/types/assistant.types.ts b/packages/editor-ui/src/types/assistant.types.ts index 59f18294937da..9f74569cfd3c5 100644 --- a/packages/editor-ui/src/types/assistant.types.ts +++ b/packages/editor-ui/src/types/assistant.types.ts @@ -1,8 +1,8 @@ import type { Schema } from '@/Interface'; -import type { INode, INodeParameters } from 'n8n-workflow'; +import type { IDataObject, INode, INodeParameters } from 'n8n-workflow'; export namespace ChatRequest { - interface NodeExecutionSchema { + export interface NodeExecutionSchema { nodeName: string; schema: Schema; } @@ -21,6 +21,7 @@ export namespace ChatRequest { stack?: string; }; node: INode; + nodeInputData?: IDataObject; } export interface InitErrorHelper extends ErrorContext, WorkflowContext { diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index 70fa803411f12..c37a3b3c02a7a 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -5,7 +5,6 @@ import type { ITemplatesNode, IVersionNode, NodeAuthenticationOption, - Schema, SimplifiedNodeType, } from '@/Interface'; import { useDataSchema } from '@/composables/useDataSchema'; @@ -20,10 +19,10 @@ import { i18n as locale } from '@/plugins/i18n'; import { useCredentialsStore } from '@/stores/credentials.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; +import type { ChatRequest } from '@/types/assistant.types'; import { isResourceLocatorValue } from '@/utils/typeGuards'; import { isJsonKeyObject } from '@/utils/typesUtils'; import type { - AssignmentCollectionValue, IDataObject, INode, INodeCredentialDescription, @@ -523,42 +522,22 @@ function extractNodeNames(template: string): string[] { * Extract the node names from the expressions in the node parameters. */ export function getReferencedNodes(node: INode): string[] { - const referencedNodes: string[] = []; + const referencedNodes: Set = new Set(); if (!node) { - return referencedNodes; + return []; } - // Special case for code node - if (node.type === 'n8n-nodes-base.set' && node.parameters.assignments) { - const assignments = node.parameters.assignments as AssignmentCollectionValue; - if (assignments.assignments?.length) { - assignments.assignments.forEach((assignment) => { - if (assignment.name && assignment.value && String(assignment.value).startsWith('=')) { - const nodeNames = extractNodeNames(String(assignment.value)); - if (nodeNames.length) { - referencedNodes.push(...nodeNames); - } - } - }); - } - } else { - Object.values(node.parameters).forEach((value) => { - if (!value) { - return; - } - let strValue = String(value); - // Handle resource locator - if (typeof value === 'object' && 'value' in value) { - strValue = String(value.value); - } - if (strValue.startsWith('=')) { - const nodeNames = extractNodeNames(strValue); - if (nodeNames.length) { - referencedNodes.push(...nodeNames); - } + // Go through all parameters and check if they contain expressions on any level + for (const key in node.parameters) { + if (node.parameters[key] && typeof node.parameters[key] === 'object') { + const names = extractNodeNames(JSON.stringify(node.parameters[key])); + if (names.length) { + names.forEach((name) => { + referencedNodes.add(name); + }); } - }); + } } - return referencedNodes; + return referencedNodes.size ? Array.from(referencedNodes) : []; } /** @@ -579,22 +558,18 @@ export function pruneNodeProperties(node: INode, propsToRemove: string[]): INode * @returns An array of objects containing the node name and the schema */ export function getNodesSchemas(nodeNames: string[]) { - return nodeNames.map((name) => { + const schemas: ChatRequest.NodeExecutionSchema[] = []; + nodeNames.forEach((name) => { const node = useWorkflowsStore().getNodeByName(name); if (!node) { - return { - nodeName: name, - schema: {} as Schema, - }; + return; } const { getSchemaForExecutionData, getInputDataWithPinned } = useDataSchema(); - const schema = getSchemaForExecutionData( - executionDataToJson(getInputDataWithPinned(node)), - true, - ); - return { + const schema = getSchemaForExecutionData(executionDataToJson(getInputDataWithPinned(node))); + schemas.push({ nodeName: node.name, schema, - }; + }); }); + return schemas; } From 7ce1d389f6489315b1e47cce15b2741cacca6b7c Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Wed, 11 Sep 2024 13:38:04 +0200 Subject: [PATCH 02/19] =?UTF-8?q?=E2=9A=A1=20Sending=20resolved=20expressi?= =?UTF-8?q?on=20values=20to=20assistant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor-ui/src/utils/nodeTypesUtils.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index c37a3b3c02a7a..297de8fa9b692 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -8,6 +8,7 @@ import type { SimplifiedNodeType, } from '@/Interface'; import { useDataSchema } from '@/composables/useDataSchema'; +import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers'; import { CORE_NODES_CATEGORY, MAIN_AUTH_FIELD_NAME, @@ -33,6 +34,7 @@ import type { ResourceMapperField, Themed, } from 'n8n-workflow'; +import { useRouter } from 'vue-router'; /* Constants and utility functions mainly used to get information about @@ -549,6 +551,29 @@ export function pruneNodeProperties(node: INode, propsToRemove: string[]): INode propsToRemove.forEach((key) => { delete prunedNode[key as keyof INode]; }); + // TODO: Rename this function, fix types and test for different node types + for (const key in prunedNode.parameters) { + const paramName = key; + const paramValue = node.parameters[key]; + const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); + let resolvedExpressionValue = paramValue; + if (typeof paramValue === 'string' && paramValue.startsWith('=')) { + resolvedExpressionValue = workflowHelpers.resolveExpression(paramValue, node.parameters); + } else if ( + typeof paramValue === 'object' && + 'value' in paramValue && + typeof paramValue.value === 'string' && + paramValue.value.startsWith('=') + ) { + resolvedExpressionValue = workflowHelpers.resolveExpression( + paramValue.value, + node.parameters, + ); + } + if (resolvedExpressionValue !== paramValue) { + node.parameters[paramName] = { value: paramValue, resolvedExpressionValue }; + } + } return prunedNode; } From c5a05a545f07a5e5c72146a55c80ea95dae27c78 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Wed, 11 Sep 2024 15:21:33 +0200 Subject: [PATCH 03/19] =?UTF-8?q?=E2=9A=A1=20Not=20sending=20ndv=20inputs?= =?UTF-8?q?=20if=20node=20is=20not=20connected.=20Adding=20a=20function=20?= =?UTF-8?q?to=20extract=20all=20expressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor-ui/src/stores/assistant.store.ts | 16 +++++++----- .../editor-ui/src/utils/nodeTypesUtils.ts | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/editor-ui/src/stores/assistant.store.ts b/packages/editor-ui/src/stores/assistant.store.ts index 1afc0186697f7..fe8542b49316c 100644 --- a/packages/editor-ui/src/stores/assistant.store.ts +++ b/packages/editor-ui/src/stores/assistant.store.ts @@ -376,12 +376,16 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { const availableAuthOptions = getNodeAuthOptions(nodeType); authType = availableAuthOptions.find((option) => option.value === credentialInUse); } - const inputData = ndvStore.ndvInputData[0].json; - const inputNodeName = ndvStore.input.nodeName; - const nodeInputData: { inputNodeName?: string; inputData?: IDataObject } = { - inputNodeName, - inputData, - }; + let nodeInputData: { inputNodeName?: string; inputData?: IDataObject } | undefined = undefined; + const ndvInput = ndvStore.ndvInputData; + if (ndvInput?.length) { + const inputData = ndvStore.ndvInputData[0].json; + const inputNodeName = ndvStore.input.nodeName; + nodeInputData = { + inputNodeName, + inputData, + }; + } addLoadingAssistantMessage(locale.baseText('aiAssistant.thinkingSteps.analyzingError')); openChat(); diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index 297de8fa9b692..913691f2f5a53 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -528,6 +528,8 @@ export function getReferencedNodes(node: INode): string[] { if (!node) { return []; } + console.log('========================== EXPRESSIONS =============================='); + console.log(extractExpressions(node.parameters)); // Go through all parameters and check if they contain expressions on any level for (const key in node.parameters) { if (node.parameters[key] && typeof node.parameters[key] === 'object') { @@ -539,9 +541,33 @@ export function getReferencedNodes(node: INode): string[] { } } } + console.log('========================================================'); return referencedNodes.size ? Array.from(referencedNodes) : []; } +// TODO: Fix types +function extractExpressions(obj: object): Array<{ key: string; expression: string }> { + const expressions: Array<{ key: string; expression: string }> = []; + + function recurse(currentObj: object, currentPath: string) { + for (const key in currentObj) { + const value = currentObj[key]; + const path = currentPath ? `${currentPath}.${key}` : key; + + // If the value is an object, recurse + if (typeof value === 'object' && value !== null) { + recurse(value, path); + } else if (typeof value === 'string' && value.startsWith('={{')) { + // Check for the custom expression syntax + expressions.push({ key: path, expression: value }); + } + } + } + + recurse(obj, ''); + return expressions; +} + /** * Remove properties from a node based on the provided list of property names. * Reruns a new node object with the properties removed. From a0bdf5b3eb5abfe8e96d0048fffcacb821f3ad02 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Wed, 11 Sep 2024 16:55:04 +0200 Subject: [PATCH 04/19] =?UTF-8?q?=E2=9A=A1=20Resolving=20all=20expressions?= =?UTF-8?q?=20and=20sending=20the=20values=20to=20assistant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor-ui/src/stores/assistant.store.ts | 4 +- .../editor-ui/src/utils/nodeTypesUtils.ts | 82 +++++++++---------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/packages/editor-ui/src/stores/assistant.store.ts b/packages/editor-ui/src/stores/assistant.store.ts index fe8542b49316c..3b1c8852ae459 100644 --- a/packages/editor-ui/src/stores/assistant.store.ts +++ b/packages/editor-ui/src/stores/assistant.store.ts @@ -26,7 +26,7 @@ import { getNodeAuthOptions, getReferencedNodes, getNodesSchemas, - pruneNodeProperties, + processNodeForLLM, } from '@/utils/nodeTypesUtils'; import { useNodeTypesStore } from './nodeTypes.store'; import { usePostHog } from './posthog.store'; @@ -400,7 +400,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { firstName: usersStore.currentUser?.firstName ?? '', }, error: context.error, - node: pruneNodeProperties(context.node, ['position']), + node: processNodeForLLM(context.node, ['position']), nodeInputData, executionSchema: schemas, authType, diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index 913691f2f5a53..62e8160deb324 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -28,6 +28,7 @@ import type { INode, INodeCredentialDescription, INodeExecutionData, + INodeParameters, INodeProperties, INodeTypeDescription, NodeParameterValueType, @@ -528,8 +529,6 @@ export function getReferencedNodes(node: INode): string[] { if (!node) { return []; } - console.log('========================== EXPRESSIONS =============================='); - console.log(extractExpressions(node.parameters)); // Go through all parameters and check if they contain expressions on any level for (const key in node.parameters) { if (node.parameters[key] && typeof node.parameters[key] === 'object') { @@ -541,14 +540,29 @@ export function getReferencedNodes(node: INode): string[] { } } } - console.log('========================================================'); return referencedNodes.size ? Array.from(referencedNodes) : []; } -// TODO: Fix types -function extractExpressions(obj: object): Array<{ key: string; expression: string }> { - const expressions: Array<{ key: string; expression: string }> = []; +/** + * Processes node object before sending it to AI assistant + * - Removes unnecessary properties + * - Extracts expressions from the parameters and resolves them + * @param node + * @param propsToRemove + * @returns processed node + */ +export function processNodeForLLM(node: INode, propsToRemove: string[]): INode { + // Make a copy of the node object so we don't modify the original + const nodeForLLM = { ...node }; + propsToRemove.forEach((key) => { + delete nodeForLLM[key as keyof INode]; + }); + resolveNodeExpressions(nodeForLLM.parameters); + return nodeForLLM; +} +// TODO: Fix types, add tests +function resolveNodeExpressions(nodeParameters: INodeParameters): void { function recurse(currentObj: object, currentPath: string) { for (const key in currentObj) { const value = currentObj[key]; @@ -559,48 +573,32 @@ function extractExpressions(obj: object): Array<{ key: string; expression: strin recurse(value, path); } else if (typeof value === 'string' && value.startsWith('={{')) { // Check for the custom expression syntax - expressions.push({ key: path, expression: value }); + const resolved = resolveExpression( + { key: path, expression: value }, + nodeParameters as INode, + ); + currentObj[key] = { + value, + resolvedExpressionValue: resolved, + }; } } } - - recurse(obj, ''); - return expressions; + recurse(nodeParameters, ''); } -/** - * Remove properties from a node based on the provided list of property names. - * Reruns a new node object with the properties removed. - */ -export function pruneNodeProperties(node: INode, propsToRemove: string[]): INode { - const prunedNode = { ...node }; - propsToRemove.forEach((key) => { - delete prunedNode[key as keyof INode]; - }); - // TODO: Rename this function, fix types and test for different node types - for (const key in prunedNode.parameters) { - const paramName = key; - const paramValue = node.parameters[key]; - const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); - let resolvedExpressionValue = paramValue; - if (typeof paramValue === 'string' && paramValue.startsWith('=')) { - resolvedExpressionValue = workflowHelpers.resolveExpression(paramValue, node.parameters); - } else if ( - typeof paramValue === 'object' && - 'value' in paramValue && - typeof paramValue.value === 'string' && - paramValue.value.startsWith('=') - ) { - resolvedExpressionValue = workflowHelpers.resolveExpression( - paramValue.value, - node.parameters, - ); - } - if (resolvedExpressionValue !== paramValue) { - node.parameters[paramName] = { value: paramValue, resolvedExpressionValue }; - } +function resolveExpression(expression: { key: string; expression: string }, node: INode) { + const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); + let resolvedExpressionValue = ''; + try { + resolvedExpressionValue = workflowHelpers.resolveExpression( + expression.expression, + node.parameters + ); + } catch (error) { + resolvedExpressionValue = `Error in expression: "${error.message}"`; } - return prunedNode; + return resolvedExpressionValue; } /** From 990ebbfcc57ae518aa7606c5598a4101f7914721 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 12 Sep 2024 11:30:17 +0200 Subject: [PATCH 05/19] =?UTF-8?q?=E2=9A=A1=20Moving=20util=20method=20to?= =?UTF-8?q?=20`workflowHelpers`=20fixing=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useWorkflowHelpers.ts | 29 ++++++++++++ .../editor-ui/src/stores/assistant.store.ts | 3 +- .../editor-ui/src/utils/nodeTypesUtils.ts | 45 +++---------------- 3 files changed, 37 insertions(+), 40 deletions(-) diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index fd203a9ecc22d..a3098de9050d3 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -69,6 +69,7 @@ import { useTagsStore } from '@/stores/tags.store'; import useWorkflowsEEStore from '@/stores/workflows.ee.store'; import { useNpsSurveyStore } from '@/stores/npsSurvey.store'; import type { NavigationGuardNext } from 'vue-router'; +import { ResolvedNodeParameters } from '@/types/assistant.types'; type ResolveParameterOptions = { targetItem?: TargetItem; @@ -693,6 +694,33 @@ export function useWorkflowHelpers(options: { router: ReturnType { } let nodeInputData: { inputNodeName?: string; inputData?: IDataObject } | undefined = undefined; const ndvInput = ndvStore.ndvInputData; - if (ndvInput?.length) { + if (isNodeReferencingInputData(context.node) && ndvInput?.length) { const inputData = ndvStore.ndvInputData[0].json; const inputNodeName = ndvStore.input.nodeName; nodeInputData = { diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index 62e8160deb324..c2526c8660861 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -557,48 +557,15 @@ export function processNodeForLLM(node: INode, propsToRemove: string[]): INode { propsToRemove.forEach((key) => { delete nodeForLLM[key as keyof INode]; }); - resolveNodeExpressions(nodeForLLM.parameters); + const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); + workflowHelpers.resolveNodeExpressions(nodeForLLM.parameters); return nodeForLLM; } -// TODO: Fix types, add tests -function resolveNodeExpressions(nodeParameters: INodeParameters): void { - function recurse(currentObj: object, currentPath: string) { - for (const key in currentObj) { - const value = currentObj[key]; - const path = currentPath ? `${currentPath}.${key}` : key; - - // If the value is an object, recurse - if (typeof value === 'object' && value !== null) { - recurse(value, path); - } else if (typeof value === 'string' && value.startsWith('={{')) { - // Check for the custom expression syntax - const resolved = resolveExpression( - { key: path, expression: value }, - nodeParameters as INode, - ); - currentObj[key] = { - value, - resolvedExpressionValue: resolved, - }; - } - } - } - recurse(nodeParameters, ''); -} - -function resolveExpression(expression: { key: string; expression: string }, node: INode) { - const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); - let resolvedExpressionValue = ''; - try { - resolvedExpressionValue = workflowHelpers.resolveExpression( - expression.expression, - node.parameters - ); - } catch (error) { - resolvedExpressionValue = `Error in expression: "${error.message}"`; - } - return resolvedExpressionValue; +export function isNodeReferencingInputData(node: INode): boolean { + const parametersString = JSON.stringify(node.parameters); + const references = ['$json', '$input', '$binary']; + return references.some((ref) => parametersString.includes(ref)); } /** From e6c447bcc49ff6876bae0e0c6b134572abc35a58 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 12 Sep 2024 12:05:19 +0200 Subject: [PATCH 06/19] =?UTF-8?q?=E2=9A=A1=20Improving=20typing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useWorkflowHelpers.ts | 16 +++++++++------- packages/editor-ui/src/utils/nodeTypesUtils.ts | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index a3098de9050d3..f71dfd1c7045a 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -8,6 +8,7 @@ import { } from '@/constants'; import type { + GenericValue, IConnections, IDataObject, IExecuteData, @@ -694,23 +695,23 @@ export function useWorkflowHelpers(options: { router: ReturnType, currentPath: string): void { for (const key in currentObj) { const value = currentObj[key]; const path = currentPath ? `${currentPath}.${key}` : key; - // If the value is an object, recurse - if (typeof value === 'object' && value !== null) { - recurse(value, path); + + if (value && typeof value === 'object' && !Array.isArray(value)) { + recurse(value as Record, path); } else if (typeof value === 'string' && value.startsWith('={{')) { - // Check for the custom expression syntax + // Resolve expression if it is one let resolved; try { resolved = resolveExpression(value, nodeParameters); } catch (error) { - resolved = `Error in expression: "${error.message}"`; + resolved = `Error in expression: "${(error as Error).message}"`; } + // Update current value to include resolved value currentObj[key] = { value, resolvedExpressionValue: resolved, @@ -718,6 +719,7 @@ export function useWorkflowHelpers(options: { router: ReturnType Date: Thu, 12 Sep 2024 13:01:13 +0200 Subject: [PATCH 07/19] =?UTF-8?q?=F0=9F=94=A8=20Renaming=20and=20refactori?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useWorkflowHelpers.ts | 18 ++++++++---------- .../editor-ui/src/stores/assistant.store.ts | 4 ++-- packages/editor-ui/src/utils/nodeTypesUtils.ts | 3 ++- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index f71dfd1c7045a..a6054f2edfeec 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -696,22 +696,21 @@ export function useWorkflowHelpers(options: { router: ReturnType, currentPath: string): void { + function recurse(currentObj: { [key: string]: unknown }, currentPath: string) { for (const key in currentObj) { - const value = currentObj[key]; + const value = currentObj[key as keyof typeof currentObj]; const path = currentPath ? `${currentPath}.${key}` : key; - - if (value && typeof value === 'object' && !Array.isArray(value)) { - recurse(value as Record, path); - } else if (typeof value === 'string' && value.startsWith('={{')) { - // Resolve expression if it is one + if (typeof value === 'object' && value !== null) { + recurse(value as { [key: string]: unknown }, path); + } else if (typeof value === 'string' && String(value).startsWith('={{')) { + // Resolve the expression if it is one let resolved; try { resolved = resolveExpression(value, nodeParameters); } catch (error) { - resolved = `Error in expression: "${(error as Error).message}"`; + resolved = `Error in expression: "${error.message}"`; } - // Update current value to include resolved value + // Updating the original object with the resolved value currentObj[key] = { value, resolvedExpressionValue: resolved, @@ -719,7 +718,6 @@ export function useWorkflowHelpers(options: { router: ReturnType { firstName: usersStore.currentUser?.firstName ?? '', }, error: context.error, - node: processNodeForLLM(context.node, ['position']), + node: processNodeForAssistant(context.node, ['position']), nodeInputData, executionSchema: schemas, authType, diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index 8e87f6b268c1c..7b2d816e07c8b 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -550,7 +550,7 @@ export function getReferencedNodes(node: INode): string[] { * @param propsToRemove * @returns processed node */ -export function processNodeForLLM(node: INode, propsToRemove: string[]): INode { +export function processNodeForAssistant(node: INode, propsToRemove: string[]): INode { // Make a copy of the node object so we don't modify the original const nodeForLLM = { ...node }; propsToRemove.forEach((key) => { @@ -558,6 +558,7 @@ export function processNodeForLLM(node: INode, propsToRemove: string[]): INode { }); const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); workflowHelpers.resolveNodeExpressions(nodeForLLM.parameters); + console.log('nodeForLLM', nodeForLLM.parameters); return nodeForLLM; } From 7532510ef8996a6d4b15ca5b5006615feaf968b8 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 12 Sep 2024 13:01:26 +0200 Subject: [PATCH 08/19] =?UTF-8?q?=E2=9C=85=20Adding=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/useWorkflowHelpers.spec.ts | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts index f50261a1aa654..a6112c8c75dca 100644 --- a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts @@ -70,6 +70,152 @@ describe('useWorkflowHelpers', () => { vi.clearAllMocks(); }); + describe('resolveNodeExpressions', () => { + it('should correctly detect and resolve expressions in a regular node ', () => { + const nodeParameters = { + curlImport: '', + method: 'GET', + url: '={{ $json.name }}', + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }; + const workflowHelpers = useWorkflowHelpers({ router }); + workflowHelpers.resolveNodeExpressions(nodeParameters); + expect(nodeParameters.url).toHaveProperty('resolvedExpressionValue'); + }); + + it('should correctly detect and resolve expressions in a node with assignments (set node) ', () => { + const nodeParameters = { + mode: 'manual', + duplicateItem: false, + assignments: { + assignments: [ + { + id: '25d2d012-089b-424d-bfc6-642982a0711f', + name: 'date', + value: + "={{ DateTime.fromFormat('2023-12-12', 'dd/MM/yyyy').toISODate().plus({7, 'days' }) }}", + type: 'number', + }, + ], + }, + includeOtherFields: false, + options: {}, + }; + const workflowHelpers = useWorkflowHelpers({ router }); + workflowHelpers.resolveNodeExpressions(nodeParameters); + expect(nodeParameters.assignments.assignments[0].value).toHaveProperty( + 'resolvedExpressionValue', + ); + }); + + it('should correctly detect and resolve expressions in a node with filter component', () => { + const nodeParameters = { + mode: 'rules', + rules: { + values: [ + { + conditions: { + options: { + caseSensitive: true, + leftValue: '', + typeValidation: 'strict', + version: 2, + }, + conditions: [ + { + leftValue: "={{ $('Edit Fields 1').item.json.name }}", + rightValue: 12, + operator: { + type: 'number', + operation: 'equals', + }, + }, + ], + combinator: 'and', + }, + renameOutput: false, + }, + ], + }, + looseTypeValidation: false, + options: {}, + }; + const workflowHelpers = useWorkflowHelpers({ router }); + workflowHelpers.resolveNodeExpressions(nodeParameters); + expect(nodeParameters.rules.values[0].conditions.conditions[0].leftValue).toHaveProperty( + 'resolvedExpressionValue', + ); + }); + it('should correctly detect and resolve expressions in a node with resource locator component', () => { + const nodeParameters = { + authentication: 'oAuth2', + resource: 'sheet', + operation: 'read', + documentId: { + __rl: true, + value: "={{ $('Edit Fields').item.json.document }}", + mode: 'id', + }, + sheetName: { + __rl: true, + value: "={{ $('Edit Fields').item.json.sheet }}", + mode: 'id', + }, + filtersUI: {}, + combineFilters: 'AND', + options: {}, + }; + const workflowHelpers = useWorkflowHelpers({ router }); + workflowHelpers.resolveNodeExpressions(nodeParameters); + expect(nodeParameters.documentId.value).toHaveProperty('resolvedExpressionValue'); + expect(nodeParameters.sheetName.value).toHaveProperty('resolvedExpressionValue'); + }); + it('should correctly detect and resolve expressions in a node with resource mapper component', () => { + const nodeParameters = { + authentication: 'oAuth2', + resource: 'sheet', + operation: 'read', + documentId: { + __rl: true, + value: '1BAjxEhlUu5tXDCMQcjqjguIZDFuct3FYkdo7flxl3yc', + mode: 'list', + cachedResultName: 'Mapping sheet', + cachedResultUrl: + 'https://docs.google.com/spreadsheets/d/1BAjxEhlUu5tXDCMQcjqjguIZDFuct3FYkdo7flxl3yc/edit?usp=drivesdk', + }, + sheetName: { + __rl: true, + value: 'gid=0', + mode: 'list', + cachedResultName: 'Users', + cachedResultUrl: + 'https://docs.google.com/spreadsheets/d/1BAjxEhlUu5tXDCMQcjqjguIZDFuct3FYkdo7flxl3yc/edit#gid=0', + }, + filtersUI: { + values: [ + { + lookupColumn: 'First name', + lookupValue: "={{ $('Edit Fields 1').item.json.userName }}", + }, + ], + }, + combineFilters: 'AND', + options: {}, + }; + const workflowHelpers = useWorkflowHelpers({ router }); + workflowHelpers.resolveNodeExpressions(nodeParameters); + expect(nodeParameters.filtersUI.values[0].lookupValue).toHaveProperty( + 'resolvedExpressionValue', + ); + }); + }); + describe('saveAsNewWorkflow', () => { it('should respect `resetWebhookUrls: false` when duplicating workflows', async () => { const workflow = getDuplicateTestWorkflow(); From ab7c00f280963db28f934b8bf246d34bdc49dbb8 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 12 Sep 2024 13:03:18 +0200 Subject: [PATCH 09/19] =?UTF-8?q?=F0=9F=94=A5=20Removing=20unused=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/composables/useWorkflowHelpers.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index a6054f2edfeec..910a7b44c560a 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -8,7 +8,6 @@ import { } from '@/constants'; import type { - GenericValue, IConnections, IDataObject, IExecuteData, @@ -70,7 +69,6 @@ import { useTagsStore } from '@/stores/tags.store'; import useWorkflowsEEStore from '@/stores/workflows.ee.store'; import { useNpsSurveyStore } from '@/stores/npsSurvey.store'; import type { NavigationGuardNext } from 'vue-router'; -import { ResolvedNodeParameters } from '@/types/assistant.types'; type ResolveParameterOptions = { targetItem?: TargetItem; From 60132e754a2e2c748a8883824f2ff96134849013 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 12 Sep 2024 14:04:25 +0200 Subject: [PATCH 10/19] =?UTF-8?q?=E2=9C=94=EF=B8=8F=20Adding=20missing=20r?= =?UTF-8?q?outer=20mock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/stores/__tests__/assistant.store.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts b/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts index fad5bae0c74b0..133e5778e811a 100644 --- a/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts +++ b/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts @@ -38,6 +38,7 @@ vi.mock('vue-router', () => ({ name: ENABLED_VIEWS[0], }), ), + useRouter: vi.fn(), RouterLink: vi.fn(), })); From dad019c6bcecc302032d8250d2e51581698fe289 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 13 Sep 2024 11:40:15 +0200 Subject: [PATCH 11/19] =?UTF-8?q?=F0=9F=91=8C=20Converting=20`resolveNodeE?= =?UTF-8?q?xpressions`=20to=20pure=20function,=20using=20`deepCopy`=20to?= =?UTF-8?q?=20copy=20nodes,=20updating=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/useWorkflowHelpers.spec.ts | 36 ++++++++++++------- .../src/composables/useWorkflowHelpers.ts | 19 +++++----- .../editor-ui/src/utils/nodeTypesUtils.ts | 27 +++++++------- 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts index a6112c8c75dca..2887e7cd71ce1 100644 --- a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts @@ -7,6 +7,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsEEStore } from '@/stores/workflows.ee.store'; import { useTagsStore } from '@/stores/tags.store'; import { createTestWorkflow } from '@/__tests__/mocks'; +import type { AssignmentCollectionValue } from 'n8n-workflow'; const getDuplicateTestWorkflow = (): IWorkflowDataUpdate => ({ name: 'Duplicate webhook test', @@ -85,8 +86,8 @@ describe('useWorkflowHelpers', () => { infoMessage: '', }; const workflowHelpers = useWorkflowHelpers({ router }); - workflowHelpers.resolveNodeExpressions(nodeParameters); - expect(nodeParameters.url).toHaveProperty('resolvedExpressionValue'); + const resolvedParameters = workflowHelpers.resolveNodeExpressions(nodeParameters); + expect(resolvedParameters.url).toHaveProperty('resolvedExpressionValue'); }); it('should correctly detect and resolve expressions in a node with assignments (set node) ', () => { @@ -108,10 +109,11 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - workflowHelpers.resolveNodeExpressions(nodeParameters); - expect(nodeParameters.assignments.assignments[0].value).toHaveProperty( - 'resolvedExpressionValue', - ); + const resolvedParameters = workflowHelpers.resolveNodeExpressions(nodeParameters); + expect(resolvedParameters).toHaveProperty('assignments'); + const assignments = resolvedParameters.assignments as AssignmentCollectionValue; + expect(assignments).toHaveProperty('assignments'); + expect(assignments.assignments[0].value).toHaveProperty('resolvedExpressionValue'); }); it('should correctly detect and resolve expressions in a node with filter component', () => { @@ -147,8 +149,12 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - workflowHelpers.resolveNodeExpressions(nodeParameters); - expect(nodeParameters.rules.values[0].conditions.conditions[0].leftValue).toHaveProperty( + const resolvedParameters = workflowHelpers.resolveNodeExpressions( + nodeParameters, + ) as typeof nodeParameters; + expect(resolvedParameters).toHaveProperty('rules'); + expect(resolvedParameters.rules).toHaveProperty('values'); + expect(resolvedParameters.rules.values[0].conditions.conditions[0].leftValue).toHaveProperty( 'resolvedExpressionValue', ); }); @@ -172,9 +178,11 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - workflowHelpers.resolveNodeExpressions(nodeParameters); - expect(nodeParameters.documentId.value).toHaveProperty('resolvedExpressionValue'); - expect(nodeParameters.sheetName.value).toHaveProperty('resolvedExpressionValue'); + const resolvedParameters = workflowHelpers.resolveNodeExpressions( + nodeParameters, + ) as typeof nodeParameters; + expect(resolvedParameters.documentId.value).toHaveProperty('resolvedExpressionValue'); + expect(resolvedParameters.sheetName.value).toHaveProperty('resolvedExpressionValue'); }); it('should correctly detect and resolve expressions in a node with resource mapper component', () => { const nodeParameters = { @@ -209,8 +217,10 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - workflowHelpers.resolveNodeExpressions(nodeParameters); - expect(nodeParameters.filtersUI.values[0].lookupValue).toHaveProperty( + const resolvedParameters = workflowHelpers.resolveNodeExpressions( + nodeParameters, + ) as typeof nodeParameters; + expect(resolvedParameters.filtersUI.values[0].lookupValue).toHaveProperty( 'resolvedExpressionValue', ); }); diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 910a7b44c560a..9ede7c918d189 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -693,14 +693,15 @@ export function useWorkflowHelpers(options: { router: ReturnType { delete nodeForLLM[key as keyof INode]; }); const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); - workflowHelpers.resolveNodeExpressions(nodeForLLM.parameters); - console.log('nodeForLLM', nodeForLLM.parameters); + const resolvedParameters = workflowHelpers.resolveNodeExpressions(nodeForLLM.parameters); + nodeForLLM.parameters = resolvedParameters; return nodeForLLM; } From 7fa9b4f2c5051ab9060d3ac8be0bf3a58ef63be0 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 13 Sep 2024 14:09:58 +0200 Subject: [PATCH 12/19] =?UTF-8?q?=F0=9F=91=8C=20Refactoring=20for=20more?= =?UTF-8?q?=20readability,=20adding=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/useWorkflowHelpers.spec.ts | 12 +- .../src/composables/useWorkflowHelpers.ts | 11 +- .../utils/__tests__/nodeTypesUtils.spec.ts | 168 ++++++++++++++++++ .../editor-ui/src/utils/nodeTypesUtils.ts | 21 ++- 4 files changed, 195 insertions(+), 17 deletions(-) create mode 100644 packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts diff --git a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts index 2887e7cd71ce1..ce34b0b2c6925 100644 --- a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts @@ -71,7 +71,7 @@ describe('useWorkflowHelpers', () => { vi.clearAllMocks(); }); - describe('resolveNodeExpressions', () => { + describe('getNodeParametersWithResolvedExpressions', () => { it('should correctly detect and resolve expressions in a regular node ', () => { const nodeParameters = { curlImport: '', @@ -86,7 +86,7 @@ describe('useWorkflowHelpers', () => { infoMessage: '', }; const workflowHelpers = useWorkflowHelpers({ router }); - const resolvedParameters = workflowHelpers.resolveNodeExpressions(nodeParameters); + const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters); expect(resolvedParameters.url).toHaveProperty('resolvedExpressionValue'); }); @@ -109,7 +109,7 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - const resolvedParameters = workflowHelpers.resolveNodeExpressions(nodeParameters); + const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters); expect(resolvedParameters).toHaveProperty('assignments'); const assignments = resolvedParameters.assignments as AssignmentCollectionValue; expect(assignments).toHaveProperty('assignments'); @@ -149,7 +149,7 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - const resolvedParameters = workflowHelpers.resolveNodeExpressions( + const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions( nodeParameters, ) as typeof nodeParameters; expect(resolvedParameters).toHaveProperty('rules'); @@ -178,7 +178,7 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - const resolvedParameters = workflowHelpers.resolveNodeExpressions( + const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions( nodeParameters, ) as typeof nodeParameters; expect(resolvedParameters.documentId.value).toHaveProperty('resolvedExpressionValue'); @@ -217,7 +217,7 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - const resolvedParameters = workflowHelpers.resolveNodeExpressions( + const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions( nodeParameters, ) as typeof nodeParameters; expect(resolvedParameters.filtersUI.values[0].lookupValue).toHaveProperty( diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 9ede7c918d189..21b2056d570b0 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -693,7 +693,14 @@ export function useWorkflowHelpers(options: { router: ReturnType = [ + { + caseName: 'Should return an empty array if no referenced nodes', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: 'https://httpbin.org/get1', + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: [], + }, + { + caseName: 'Should return an array of references for regular node', + node: { + parameters: { + authentication: 'oAuth2', + resource: 'sheet', + operation: 'read', + documentId: { + __rl: true, + value: "={{ $('Edit Fields').item.json.document }}", + mode: 'id', + }, + sheetName: { + __rl: true, + value: "={{ $('Edit Fields 2').item.json.sheet }}", + mode: 'id', + }, + filtersUI: {}, + combineFilters: 'AND', + options: {}, + }, + type: 'n8n-nodes-base.googleSheets', + typeVersion: 4.5, + position: [440, 0], + id: '9a95ad27-06cf-4076-af6b-52846a109a8b', + name: 'Google Sheets', + credentials: { + googleSheetsOAuth2Api: { + id: '8QEpi028oHDLXntS', + name: 'milorad@n8n.io', + }, + }, + }, + expected: ['Edit Fields', 'Edit Fields 2'], + }, + { + caseName: 'Should return an array of references for set node', + node: { + parameters: { + mode: 'manual', + duplicateItem: false, + assignments: { + assignments: [ + { + id: '135e0eb0-f412-430d-8990-731c57cf43ae', + name: 'document', + value: "={{ $('Edit Fields 2').item.json.document}}", + type: 'string', + }, + ], + }, + includeOtherFields: false, + options: {}, + }, + type: 'n8n-nodes-base.set', + typeVersion: 3.4, + position: [560, -140], + id: '7306745f-ba8c-451d-ae1a-c627f60fbdd3', + name: 'Edit Fields 2', + }, + expected: ['Edit Fields 2'], + }, + { + caseName: 'Should handle expressions with single and double quotes', + node: { + parameters: { + authentication: 'oAuth2', + resource: 'sheet', + operation: 'read', + documentId: { + __rl: true, + value: "={{ $('Edit Fields').item.json.document }}", + mode: 'id', + }, + sheetName: { + __rl: true, + value: '={{ $("Edit Fields 2").item.json.sheet }}', + mode: 'id', + }, + filtersUI: {}, + combineFilters: 'AND', + options: {}, + }, + type: 'n8n-nodes-base.googleSheets', + typeVersion: 4.5, + position: [440, 0], + id: '9a95ad27-06cf-4076-af6b-52846a109a8b', + name: 'Google Sheets', + credentials: { + googleSheetsOAuth2Api: { + id: '8QEpi028oHDLXntS', + name: 'milorad@n8n.io', + }, + }, + }, + expected: ['Edit Fields', 'Edit Fields 2'], + }, + { + caseName: 'Should only add one reference for each referenced node', + node: { + parameters: { + authentication: 'oAuth2', + resource: 'sheet', + operation: 'read', + documentId: { + __rl: true, + value: "={{ $('Edit Fields').item.json.document }}", + mode: 'id', + }, + sheetName: { + __rl: true, + value: "={{ $('Edit Fields').item.json.sheet }}", + mode: 'id', + }, + filtersUI: {}, + combineFilters: 'AND', + options: {}, + }, + type: 'n8n-nodes-base.googleSheets', + typeVersion: 4.5, + position: [440, 0], + id: '9a95ad27-06cf-4076-af6b-52846a109a8b', + name: 'Google Sheets', + credentials: { + googleSheetsOAuth2Api: { + id: '8QEpi028oHDLXntS', + name: 'milorad@n8n.io', + }, + }, + }, + expected: ['Edit Fields'], + }, +]; + +describe.each(testCases)('getReferencedNodes', (testCase) => { + const caseName = testCase.caseName; + it(`${caseName}`, () => { + expect(getReferencedNodes(testCase.node)).toEqual(testCase.expected); + }); +}); diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index b2159fbe75de5..777d4c5c9b2c9 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -505,9 +505,10 @@ export const getNodeIconColor = ( /** Regular expression to extract the node names from the expressions in the template. - Example: $(expression) => expression + Example: $('expression') => expression + Or: $("expression") => expression */ -const entityRegex = /\$\((['"])(.*?)\1\)/g; +const entityRegex = /\$\((\\?['"])(.*?)\1\)/g; /** * Extract the node names from the expressions in the template. @@ -547,8 +548,8 @@ export function getReferencedNodes(node: INode): string[] { * Processes node object before sending it to AI assistant * - Removes unnecessary properties * - Extracts expressions from the parameters and resolves them - * @param node - * @param propsToRemove + * @param node original node object + * @param propsToRemove properties to remove from the node object * @returns processed node */ export function processNodeForAssistant(node: INode, propsToRemove: string[]): INode { @@ -558,7 +559,9 @@ export function processNodeForAssistant(node: INode, propsToRemove: string[]): I delete nodeForLLM[key as keyof INode]; }); const workflowHelpers = useWorkflowHelpers({ router: useRouter() }); - const resolvedParameters = workflowHelpers.resolveNodeExpressions(nodeForLLM.parameters); + const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions( + nodeForLLM.parameters, + ); nodeForLLM.parameters = resolvedParameters; return nodeForLLM; } @@ -572,14 +575,14 @@ export function isNodeReferencingInputData(node: INode): boolean { /** * Get the schema for the referenced nodes as expected by the AI assistant * @param nodeNames The names of the nodes to get the schema for - * @returns An array of objects containing the node name and the schema + * @returns An array of NodeExecutionSchema objects */ export function getNodesSchemas(nodeNames: string[]) { const schemas: ChatRequest.NodeExecutionSchema[] = []; - nodeNames.forEach((name) => { + for (const name of nodeNames) { const node = useWorkflowsStore().getNodeByName(name); if (!node) { - return; + continue; } const { getSchemaForExecutionData, getInputDataWithPinned } = useDataSchema(); const schema = getSchemaForExecutionData(executionDataToJson(getInputDataWithPinned(node))); @@ -587,6 +590,6 @@ export function getNodesSchemas(nodeNames: string[]) { nodeName: node.name, schema, }); - }); + } return schemas; } From 5ef46b20d70c4fb3594bad5b33fb318d19a356dc Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 13 Sep 2024 14:13:20 +0200 Subject: [PATCH 13/19] =?UTF-8?q?=F0=9F=91=95=20Removing=20unused=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts index 2ea32c693e276..3ca6e3e87573f 100644 --- a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts +++ b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts @@ -1,8 +1,8 @@ -import { describe, it, expect, suite } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { getReferencedNodes } from '../nodeTypesUtils'; import type { INode } from 'n8n-workflow'; -const testCases: Array<{ caseName: string; node: INode; expected: string[] }> = [ +const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: string[] }> = [ { caseName: 'Should return an empty array if no referenced nodes', node: { @@ -160,7 +160,7 @@ const testCases: Array<{ caseName: string; node: INode; expected: string[] }> = }, ]; -describe.each(testCases)('getReferencedNodes', (testCase) => { +describe.each(referencedNodesTestCases)('getReferencedNodes', (testCase) => { const caseName = testCase.caseName; it(`${caseName}`, () => { expect(getReferencedNodes(testCase.node)).toEqual(testCase.expected); From e47fdd05c860297ccd3cebd079d3c67b6727011c Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 13 Sep 2024 14:32:51 +0200 Subject: [PATCH 14/19] =?UTF-8?q?=F0=9F=91=95=20Breaking=20up=20long=20lin?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/__tests__/useWorkflowHelpers.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts index ce34b0b2c6925..ac7d5addabbad 100644 --- a/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useWorkflowHelpers.spec.ts @@ -86,7 +86,8 @@ describe('useWorkflowHelpers', () => { infoMessage: '', }; const workflowHelpers = useWorkflowHelpers({ router }); - const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters); + const resolvedParameters = + workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters); expect(resolvedParameters.url).toHaveProperty('resolvedExpressionValue'); }); @@ -109,7 +110,8 @@ describe('useWorkflowHelpers', () => { options: {}, }; const workflowHelpers = useWorkflowHelpers({ router }); - const resolvedParameters = workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters); + const resolvedParameters = + workflowHelpers.getNodeParametersWithResolvedExpressions(nodeParameters); expect(resolvedParameters).toHaveProperty('assignments'); const assignments = resolvedParameters.assignments as AssignmentCollectionValue; expect(assignments).toHaveProperty('assignments'); From 39a455ef5d249d5965f64b388b71cce89aa284e8 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 13 Sep 2024 14:50:59 +0200 Subject: [PATCH 15/19] =?UTF-8?q?=E2=9C=85=20Adding=20more=20test=20cases,?= =?UTF-8?q?=20handling=20missed=20edge=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/__tests__/nodeTypesUtils.spec.ts | 115 ++++++++++++++++++ .../editor-ui/src/utils/nodeTypesUtils.ts | 16 ++- 2 files changed, 125 insertions(+), 6 deletions(-) diff --git a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts index 3ca6e3e87573f..7f3cac9447f36 100644 --- a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts +++ b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts @@ -158,6 +158,121 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: }, expected: ['Edit Fields'], }, + { + caseName: 'Should handle multiple node references in one expression', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: "={{ $('Edit Fields').item.json.one }} {{ $('Edit Fields 2').item.json.two }} {{ $('Edit Fields').item.json.three }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit Fields', 'Edit Fields 2'], + }, + { + caseName: 'Should ignore spaces around node references', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: "={{ $(' Edit Fields ').item.json.one }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit Fields'], + }, + { + caseName: 'Should ignore non-existing nodes', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: "={{ $(' Edit Fields ').item.json.one }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit Fields'], + }, + { + caseName: 'Should ignore special characters in node references', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: "={{ $( 'Ignore ' this' ).item.json.document }", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: [], + }, + { + caseName: 'Should handle node names with quotes', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: "={{ $('Edit 'Fields' 2').item.json.name }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ["Edit 'Fields' 2"], + }, ]; describe.each(referencedNodesTestCases)('getReferencedNodes', (testCase) => { diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index 777d4c5c9b2c9..af4fbcf1076e4 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -532,15 +532,19 @@ export function getReferencedNodes(node: INode): string[] { } // Go through all parameters and check if they contain expressions on any level for (const key in node.parameters) { + let names: string[] = []; if (node.parameters[key] && typeof node.parameters[key] === 'object') { - const names = extractNodeNames(JSON.stringify(node.parameters[key])); - if (names.length) { - names.forEach((name) => { - referencedNodes.add(name); - }); - } + names = extractNodeNames(JSON.stringify(node.parameters[key])); + } else if (typeof node.parameters[key] === 'string') { + names = extractNodeNames(node.parameters[key]); + } + if (names.length) { + names.forEach((name) => { + referencedNodes.add(name.trim()); + }); } } + console.log(referencedNodes); return referencedNodes.size ? Array.from(referencedNodes) : []; } From 1fa12b393d3ec06e11c419b423e767ea3ab30c39 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 13 Sep 2024 14:53:26 +0200 Subject: [PATCH 16/19] =?UTF-8?q?=F0=9F=94=A5=20Removing=20leftover=20cons?= =?UTF-8?q?ole.log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/utils/nodeTypesUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index af4fbcf1076e4..b0a8cffc87035 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -544,7 +544,6 @@ export function getReferencedNodes(node: INode): string[] { }); } } - console.log(referencedNodes); return referencedNodes.size ? Array.from(referencedNodes) : []; } From 60db64157a0192eb84b7db1a0bae7763493985ff Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Tue, 17 Sep 2024 10:24:16 +0200 Subject: [PATCH 17/19] =?UTF-8?q?=E2=9A=A1=20Adding=20support=20for=20back?= =?UTF-8?q?ticks,=20skipping=20empty=20values,=20updating=20test=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/__tests__/nodeTypesUtils.spec.ts | 117 ++++++++++++++++-- .../editor-ui/src/utils/nodeTypesUtils.ts | 28 +++-- 2 files changed, 129 insertions(+), 16 deletions(-) diff --git a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts index 7f3cac9447f36..18b4061330003 100644 --- a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts +++ b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts @@ -89,7 +89,7 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: expected: ['Edit Fields 2'], }, { - caseName: 'Should handle expressions with single and double quotes', + caseName: 'Should handle expressions with single quotes, double quotes and backticks', node: { parameters: { authentication: 'oAuth2', @@ -105,6 +105,11 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: value: '={{ $("Edit Fields 2").item.json.sheet }}', mode: 'id', }, + rowName: { + __rl: true, + value: '={{ $(`Edit Fields 3`).item.json.row }}', + mode: 'id', + }, filtersUI: {}, combineFilters: 'AND', options: {}, @@ -121,7 +126,7 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: }, }, }, - expected: ['Edit Fields', 'Edit Fields 2'], + expected: ['Edit Fields', 'Edit Fields 2', 'Edit Fields 3'], }, { caseName: 'Should only add one reference for each referenced node', @@ -182,7 +187,7 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: expected: ['Edit Fields', 'Edit Fields 2'], }, { - caseName: 'Should ignore spaces around node references', + caseName: 'Should respect whitespace around node references', node: { parameters: { curlImport: '', @@ -202,15 +207,15 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', name: 'HTTP Request', }, - expected: ['Edit Fields'], + expected: [' Edit Fields '], }, { - caseName: 'Should ignore non-existing nodes', + caseName: 'Should ignore whitespace inside expressions', node: { parameters: { curlImport: '', method: 'GET', - url: "={{ $(' Edit Fields ').item.json.one }}", + url: "={{ $( 'Edit Fields' ).item.json.one }}", authentication: 'none', provideSslCertificates: false, sendQuery: false, @@ -251,12 +256,13 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: expected: [], }, { - caseName: 'Should handle node names with quotes', + caseName: 'Should correctly detect node names that contain single quotes', node: { parameters: { curlImport: '', method: 'GET', - url: "={{ $('Edit 'Fields' 2').item.json.name }}", + // In order to carry over backslashes to test function, the string needs to be double escaped + url: "={{ $('Edit \\'Fields\\' 2').item.json.name }}", authentication: 'none', provideSslCertificates: false, sendQuery: false, @@ -273,6 +279,101 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: }, expected: ["Edit 'Fields' 2"], }, + { + caseName: 'Should correctly detect node names with inner backticks', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: "={{ $('Edit `Fields` 2').item.json.name }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit `Fields` 2'], + }, + { + caseName: 'Should correctly detect node names with inner escaped backticks', + node: { + parameters: { + curlImport: '', + method: 'GET', + // eslint-disable-next-line prettier/prettier + url: "={{ $(`Edit \\`Fields\\` 2`).item.json.name }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit `Fields` 2'], + }, + { + caseName: 'Should correctly detect node names with inner escaped double quotes', + node: { + parameters: { + curlImport: '', + method: 'GET', + // In order to carry over backslashes to test function, the string needs to be double escaped + url: '={{ $("Edit \\"Fields\\" 2").item.json.name }}', + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit "Fields" 2'], + }, + { + caseName: 'Should not detect invalid expressions', + node: { + parameters: { + curlImport: '', + method: 'GET', + // String not closed properly + url: "={{ $('Edit ' fields').item.json.document }", + // Mixed quotes + url2: '{{ $("Edit \'Fields" 2").item.json.name }}', + url3: '{{ $("Edit `Fields" 2").item.json.name }}', + // Quotes not escaped + url4: '{{ $("Edit "Fields" 2").item.json.name }}', + url5: "{{ $('Edit 'Fields' 2').item.json.name }}", + url6: '{{ $(`Edit `Fields` 2`).item.json.name }}', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: [], + }, ]; describe.each(referencedNodesTestCases)('getReferencedNodes', (testCase) => { diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index b0a8cffc87035..03f930c740bea 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -505,10 +505,9 @@ export const getNodeIconColor = ( /** Regular expression to extract the node names from the expressions in the template. - Example: $('expression') => expression - Or: $("expression") => expression + Supports single quotes, double quotes, and backticks. */ -const entityRegex = /\$\((\\?['"])(.*?)\1\)/g; +const entityRegex = /\$\(\s*(\\?["'`])((?:\\.|(?!\1)[^\\])*)\1\s*\)/g; /** * Extract the node names from the expressions in the template. @@ -522,6 +521,13 @@ function extractNodeNames(template: string): string[] { return nodeNames; } +/** + * Unescape quotes in the string. Supports single quotes, double quotes, and backticks. + */ +export function unescapeQuotes(str: string): string { + return str.replace(/\\(['"`])/g, '$1'); +} + /** * Extract the node names from the expressions in the node parameters. */ @@ -533,15 +539,21 @@ export function getReferencedNodes(node: INode): string[] { // Go through all parameters and check if they contain expressions on any level for (const key in node.parameters) { let names: string[] = []; - if (node.parameters[key] && typeof node.parameters[key] === 'object') { + if ( + node.parameters[key] && + typeof node.parameters[key] === 'object' && + Object.keys(node.parameters[key]).length + ) { names = extractNodeNames(JSON.stringify(node.parameters[key])); - } else if (typeof node.parameters[key] === 'string') { + } else if (typeof node.parameters[key] === 'string' && node.parameters[key]) { names = extractNodeNames(node.parameters[key]); } if (names.length) { - names.forEach((name) => { - referencedNodes.add(name.trim()); - }); + names + .map((name) => unescapeQuotes(name)) + .forEach((name) => { + referencedNodes.add(name); + }); } } return referencedNodes.size ? Array.from(referencedNodes) : []; From 8aa94fc21f4c702a639ad76ec9f0a83434f58385 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Tue, 17 Sep 2024 11:04:20 +0200 Subject: [PATCH 18/19] =?UTF-8?q?=E2=9A=A1=20Updating=20expression=20resol?= =?UTF-8?q?ving=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useWorkflowHelpers.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 21b2056d570b0..10892ae53e4d2 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -712,7 +712,20 @@ export function useWorkflowHelpers(options: { router: ReturnType[2] = { + isForCredential: false, + }; + const ndvStore = useNDVStore(); + if (ndvStore.isInputParentOfActiveNode) { + opts = { + ...opts, + targetItem: ndvStore.expressionTargetItem ?? undefined, + inputNodeName: ndvStore.ndvInputNodeName, + inputRunIndex: ndvStore.ndvInputRunIndex, + inputBranchIndex: ndvStore.ndvInputBranchIndex, + }; + } + resolved = resolveExpression(value, undefined, opts); } catch (error) { resolved = `Error in expression: "${error.message}"`; } From acbc00fd053b47129927469d9463c630ddd18dc9 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Tue, 17 Sep 2024 15:51:22 +0200 Subject: [PATCH 19/19] =?UTF-8?q?=E2=9A=A1=20Resolving=20assistant=20expre?= =?UTF-8?q?ssions=20using=20default=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useWorkflowHelpers.ts | 15 +-------------- .../src/utils/__tests__/nodeTypesUtils.spec.ts | 3 +-- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 10892ae53e4d2..2c79cb2639e50 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -712,20 +712,7 @@ export function useWorkflowHelpers(options: { router: ReturnType[2] = { - isForCredential: false, - }; - const ndvStore = useNDVStore(); - if (ndvStore.isInputParentOfActiveNode) { - opts = { - ...opts, - targetItem: ndvStore.expressionTargetItem ?? undefined, - inputNodeName: ndvStore.ndvInputNodeName, - inputRunIndex: ndvStore.ndvInputRunIndex, - inputBranchIndex: ndvStore.ndvInputBranchIndex, - }; - } - resolved = resolveExpression(value, undefined, opts); + resolved = resolveExpression(value, undefined, { isForCredential: false }); } catch (error) { resolved = `Error in expression: "${error.message}"`; } diff --git a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts index 18b4061330003..118095ed2422b 100644 --- a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts +++ b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts @@ -308,8 +308,7 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: parameters: { curlImport: '', method: 'GET', - // eslint-disable-next-line prettier/prettier - url: "={{ $(`Edit \\`Fields\\` 2`).item.json.name }}", + url: '={{ $(`Edit \\`Fields\\` 2`).item.json.name }}', authentication: 'none', provideSslCertificates: false, sendQuery: false,