diff --git a/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts b/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts index 392ef09825178..3e50fb3d78cc4 100644 --- a/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useRunWorkflow.spec.ts @@ -58,7 +58,6 @@ vi.mock('@/composables/useToast', () => ({ vi.mock('@/composables/useWorkflowHelpers', () => ({ useWorkflowHelpers: vi.fn().mockReturnValue({ getCurrentWorkflow: vi.fn(), - checkReadyForExecution: vi.fn(), saveCurrentWorkflow: vi.fn(), getWorkflowDataToSave: vi.fn(), }), diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 7338578a90c36..40bd6a0c2eeaa 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -4,7 +4,6 @@ import { PLACEHOLDER_EMPTY_WORKFLOW_ID, PLACEHOLDER_FILLED_AT_EXECUTION_TIME, VIEWS, - WEBHOOK_NODE_TYPE, } from '@/constants'; import type { @@ -15,15 +14,12 @@ import type { INodeConnection, INodeCredentials, INodeExecutionData, - INodeIssues, INodeParameters, INodeProperties, - INodeType, INodeTypes, IRunExecutionData, IWebhookDescription, IWorkflowDataProxyAdditionalKeys, - IWorkflowIssues, IWorkflowSettings, NodeParameterValue, Workflow, @@ -531,81 +527,6 @@ export function useWorkflowHelpers(options: { router: ReturnType(arr: T[]): T[] { return [...new Set(arr)]; } +function hasDotNotationBannedChar(nodeName: string) { + const DOT_NOTATION_BANNED_CHARS = /^(\d)|[\\ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>?~]/g; + return DOT_NOTATION_BANNED_CHARS.test(nodeName); +} + +function backslashEscape(nodeName: string) { + const BACKSLASH_ESCAPABLE_CHARS = /[.*+?^${}()|[\]\\]/g; + return nodeName.replace(BACKSLASH_ESCAPABLE_CHARS, (char) => `\\${char}`); +} + +function dollarEscape(nodeName: string) { + return nodeName.replace(new RegExp('\\$', 'g'), '$$$$'); +} + +export interface WorkflowParameters { + id?: string; + name?: string; + nodes: INode[]; + connections: IConnections; + active: boolean; + nodeTypes: INodeTypes; + staticData?: IDataObject; + settings?: IWorkflowSettings; + pinData?: IPinData; +} + export class Workflow { id: string; @@ -70,9 +86,9 @@ export class Workflow { nodes: INodes = {}; - connectionsBySourceNode: IConnections; + connectionsBySourceNode: IConnections = {}; - connectionsByDestinationNode: IConnections; + connectionsByDestinationNode: IConnections = {}; nodeTypes: INodeTypes; @@ -84,37 +100,34 @@ export class Workflow { // To save workflow specific static data like for example // ids of registered webhooks of nodes - staticData: IDataObject; + staticData: IDataObject = {}; testStaticData: IDataObject | undefined; pinData?: IPinData; - // constructor(id: string | undefined, nodes: INode[], connections: IConnections, active: boolean, nodeTypes: INodeTypes, staticData?: IDataObject, settings?: IWorkflowSettings) { - constructor(parameters: { - id?: string; - name?: string; - nodes: INode[]; - connections: IConnections; - active: boolean; - nodeTypes: INodeTypes; - staticData?: IDataObject; - settings?: IWorkflowSettings; - pinData?: IPinData; - }) { + constructor(parameters: WorkflowParameters) { this.id = parameters.id as string; // @tech_debt Ensure this is not optional this.name = parameters.name; this.nodeTypes = parameters.nodeTypes; - this.pinData = parameters.pinData; + this.active = parameters.active || false; + this.settings = parameters.settings || {}; - // Save nodes in workflow as object to be able to get the - // nodes easily by its name. - // Also directly add the default values of the node type. - let nodeType: INodeType | undefined; - for (const node of parameters.nodes) { + this.updateNodes(parameters.nodes); + this.updateConnections(parameters.connections); + this.updatePinData(parameters.pinData); + this.updateStaticData(parameters.staticData); + + this.expression = new Expression(this); + } + + updateNodes(nodes: INode[]) { + this.nodes = {}; + + for (const node of nodes) { this.nodes[node.name] = node; - nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); + const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); if (nodeType === undefined) { // Go on to next node when its type is not known. @@ -122,10 +135,6 @@ export class Workflow { // expression resolution also then when the unknown node // does not get used. continue; - // throw new ApplicationError(`Node with unknown node type`, { - // tags: { nodeType: node.type }, - // extra: { node }, - // }); } // Add default values @@ -138,61 +147,34 @@ export class Workflow { ); node.parameters = nodeParameters !== null ? nodeParameters : {}; } - this.connectionsBySourceNode = parameters.connections; - - // Save also the connections by the destination nodes - this.connectionsByDestinationNode = this.__getConnectionsByDestination(parameters.connections); - - this.active = parameters.active || false; - - this.staticData = ObservableObject.create(parameters.staticData || {}, undefined, { - ignoreEmptyOnFirstChild: true, - }); - - this.settings = parameters.settings || {}; - - this.expression = new Expression(this); } /** - * The default connections are by source node. This function rewrites them by destination nodes - * to easily find parent nodes. - * + * The default connections are by source node. + * This function rewrites them by destination nodes to easily find parent nodes. */ - __getConnectionsByDestination(connections: IConnections): IConnections { - const returnConnection: IConnections = {}; - - let connectionInfo; - let maxIndex: number; - for (const sourceNode in connections) { - if (!connections.hasOwnProperty(sourceNode)) { - continue; - } - - for (const type of Object.keys(connections[sourceNode]) as NodeConnectionType[]) { - if (!connections[sourceNode].hasOwnProperty(type)) { - continue; - } - for (const inputIndex in connections[sourceNode][type]) { - if (!connections[sourceNode][type].hasOwnProperty(inputIndex)) { - continue; - } - for (connectionInfo of connections[sourceNode][type][inputIndex]) { - if (!returnConnection.hasOwnProperty(connectionInfo.node)) { - returnConnection[connectionInfo.node] = {}; - } - if (!returnConnection[connectionInfo.node].hasOwnProperty(connectionInfo.type)) { - returnConnection[connectionInfo.node][connectionInfo.type] = []; - } - - maxIndex = returnConnection[connectionInfo.node][connectionInfo.type].length - 1; - for (let j = maxIndex; j < connectionInfo.index; j++) { - returnConnection[connectionInfo.node][connectionInfo.type].push([]); + updateConnections(sourceConnections: IConnections) { + this.connectionsBySourceNode = sourceConnections; + + const returnConnections: IConnections = {}; + + for (const sourceNode in sourceConnections) { + const nodeConnections = sourceConnections[sourceNode]; + for (const connectionType in nodeConnections) { + const inputConnections = nodeConnections[connectionType]; + for (const [inputIndex, connectionInfos] of Object.entries(inputConnections)) { + for (const { type, node, index } of connectionInfos) { + const returnConnection = (returnConnections[node] = returnConnections[node] || {}); + const returnInputConnections = (returnConnection[type] = returnConnection[type] || []); + + const maxIndex = returnInputConnections.length - 1; + for (let j = maxIndex; j < index; j++) { + returnInputConnections.push([]); } - returnConnection[connectionInfo.node][connectionInfo.type][connectionInfo.index].push({ + returnInputConnections[index].push({ node: sourceNode, - type, + type: type as NodeConnectionType, index: parseInt(inputIndex, 10), }); } @@ -200,7 +182,17 @@ export class Workflow { } } - return returnConnection; + this.connectionsByDestinationNode = returnConnections; + } + + updatePinData(pinData?: IPinData) { + this.pinData = pinData; + } + + updateStaticData(staticData?: IDataObject) { + this.staticData = ObservableObject.create(staticData || {}, undefined, { + ignoreEmptyOnFirstChild: true, + }); } /** @@ -353,7 +345,6 @@ export class Workflow { /** * Returns all the trigger nodes in the workflow. - * */ getTriggerNodes(): INode[] { return this.queryNodes((nodeType: INodeType) => !!nodeType.trigger); @@ -361,7 +352,6 @@ export class Workflow { /** * Returns all the poll nodes in the workflow - * */ getPollNodes(): INode[] { return this.queryNodes((nodeType: INodeType) => !!nodeType.poll); @@ -487,11 +477,12 @@ export class Workflow { ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return returnArray; } // eslint-disable-next-line @typescript-eslint/no-explicit-any - const returnData: any = {}; + const returnData: Record = {}; for (const parameterName of Object.keys(parameterValue || {})) { returnData[parameterName] = this.renameNodeInParameterValue( @@ -539,27 +530,21 @@ export class Workflow { } // Change all source connections - if (this.connectionsBySourceNode.hasOwnProperty(currentName)) { - this.connectionsBySourceNode[newName] = this.connectionsBySourceNode[currentName]; - delete this.connectionsBySourceNode[currentName]; + const sourceConnections = this.connectionsBySourceNode; + if (currentName in sourceConnections) { + sourceConnections[newName] = sourceConnections[currentName]; + delete sourceConnections[currentName]; } // Change all destination connections - let sourceNode: string; - let type: string; - let sourceIndex: string; - let connectionIndex: string; - let connectionData: IConnection; - for (sourceNode of Object.keys(this.connectionsBySourceNode)) { - for (type of Object.keys(this.connectionsBySourceNode[sourceNode])) { - for (sourceIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type])) { - for (connectionIndex of Object.keys( - this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)], - )) { - connectionData = - this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)][ - parseInt(connectionIndex, 10) - ]; + for (const sourceNode of Object.keys(sourceConnections)) { + const sourceNodeConnections = sourceConnections[sourceNode]; + for (const type of Object.keys(sourceNodeConnections)) { + const inputConnections = sourceNodeConnections[type]; + const sourceIndices = Object.keys(inputConnections).map((index) => parseInt(index, 10)); + for (const sourceIndex of sourceIndices) { + for (const connectionIndex of Object.keys(inputConnections[sourceIndex])) { + const connectionData = inputConnections[sourceIndex][parseInt(connectionIndex, 10)]; if (connectionData.node === currentName) { connectionData.node = newName; } @@ -569,9 +554,7 @@ export class Workflow { } // Use the updated connections to create updated connections by destination nodes - this.connectionsByDestinationNode = this.__getConnectionsByDestination( - this.connectionsBySourceNode, - ); + this.updateConnections(sourceConnections); } /** @@ -682,11 +665,7 @@ export class Workflow { } /** - * Gets all the nodes which are connected nodes starting from - * the given one - * - * @param {ConnectionTypes} [type='main'] - * @param {*} [depth=-1] + * Gets all the nodes which are connected nodes starting from the given one */ getConnectedNodes( connections: IConnections, @@ -780,21 +759,20 @@ export class Workflow { /** * Returns all the nodes before the given one - * - * @param {*} [maxDepth=-1] */ getParentNodesByDepth(nodeName: string, maxDepth = -1): IConnectedNode[] { return this.searchNodesBFS(this.connectionsByDestinationNode, nodeName, maxDepth); } /** - * Gets all the nodes which are connected nodes starting from - * the given one + * Gets all the nodes which are connected nodes starting from the given one * Uses BFS traversal - * - * @param {*} [maxDepth=-1] */ - searchNodesBFS(connections: IConnections, sourceNode: string, maxDepth = -1): IConnectedNode[] { + private searchNodesBFS( + connections: IConnections, + sourceNode: string, + maxDepth = -1, + ): IConnectedNode[] { const returnConns: IConnectedNode[] = []; const type: ConnectionTypes = 'main'; @@ -981,7 +959,7 @@ export class Workflow { * * @param {string[]} nodeNames The potential start nodes */ - __getStartNode(nodeNames: string[]): INode | undefined { + private __getStartNode(nodeNames: string[]): INode | undefined { // Check if there are any trigger or poll nodes and then return the first one let node: INode; let nodeType: INodeType; @@ -1110,9 +1088,7 @@ export class Workflow { } /** - * Runs the given trigger node so that it can trigger the workflow - * when the node has data. - * + * Runs the given trigger node so that it can trigger the workflow when the node has data. */ async runTrigger( node: INode, @@ -1195,11 +1171,8 @@ export class Workflow { } /** - * Runs the given trigger node so that it can trigger the workflow - * when the node has data. - * + * Runs the given trigger node so that it can trigger the workflow when the node has data. */ - async runPoll( node: INode, pollFunctions: IPollFunctions, @@ -1224,9 +1197,7 @@ export class Workflow { } /** - * Executes the webhook data to see what it should return and if the - * workflow should be started or not - * + * Executes the webhook data to see what it should return and if the workflow should be started or not */ async runWebhook( webhookData: IWebhookData, @@ -1263,7 +1234,6 @@ export class Workflow { /** * Executes the given node. - * */ // eslint-disable-next-line complexity async runNode( @@ -1384,6 +1354,7 @@ export class Workflow { const closingErrors = closeFunctionsResults .filter((result): result is PromiseRejectedResult => result.status === 'rejected') + // eslint-disable-next-line @typescript-eslint/no-unsafe-return .map((result) => result.reason); if (closingErrors.length > 0) { @@ -1479,19 +1450,3 @@ export class Workflow { } } } - -function hasDotNotationBannedChar(nodeName: string) { - const DOT_NOTATION_BANNED_CHARS = /^(\d)|[\\ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>?~]/g; - - return DOT_NOTATION_BANNED_CHARS.test(nodeName); -} - -function backslashEscape(nodeName: string) { - const BACKSLASH_ESCAPABLE_CHARS = /[.*+?^${}()|[\]\\]/g; - - return nodeName.replace(BACKSLASH_ESCAPABLE_CHARS, (char) => `\\${char}`); -} - -function dollarEscape(nodeName: string) { - return nodeName.replace(new RegExp('\\$', 'g'), '$$$$'); -} diff --git a/packages/workflow/test/Workflow.test.ts b/packages/workflow/test/Workflow.test.ts index 28bac2c13bb2a..a9e0f5915088b 100644 --- a/packages/workflow/test/Workflow.test.ts +++ b/packages/workflow/test/Workflow.test.ts @@ -1,3 +1,5 @@ +import { mock } from 'jest-mock-extended'; +import { NodeConnectionType } from '@/Interfaces'; import type { IBinaryKeyData, IConnections, @@ -5,10 +7,13 @@ import type { INode, INodeExecutionData, INodeParameters, + INodeType, + INodeTypeDescription, + INodeTypes, IRunExecutionData, NodeParameterValueType, } from '@/Interfaces'; -import { Workflow } from '@/Workflow'; +import { Workflow, type WorkflowParameters } from '@/Workflow'; process.env.TEST_VARIABLE_1 = 'valueEnvVariable1'; @@ -20,303 +25,349 @@ interface StubNode { } describe('Workflow', () => { - describe('renameNodeInParameterValue for expressions', () => { - const tests = [ - { - description: 'do nothing if there is no expression', - input: { - currentName: 'Node1', - newName: 'Node1New', - parameters: { + describe('checkIfWorkflowCanBeActivated', () => { + const disabledNode = mock({ disabled: true }); + const ignoredNode = mock({ type: 'ignoredNode' }); + const unknownNode = mock({ type: 'unknownNode' }); + const noTriggersNode = mock({ type: 'noTriggersNode' }); + const pollNode = mock({ type: 'pollNode' }); + const triggerNode = mock({ type: 'triggerNode' }); + const webhookNode = mock({ type: 'webhookNode' }); + + const nodeTypes = mock(); + nodeTypes.getByNameAndVersion.mockImplementation((type) => { + // getByNameAndVersion signature needs to be updated to allow returning undefined + if (type === 'unknownNode') return undefined as unknown as INodeType; + const partial: Partial = { + poll: undefined, + trigger: undefined, + webhook: undefined, + description: mock({ + properties: [], + }), + }; + if (type === 'pollNode') partial.poll = jest.fn(); + if (type === 'triggerNode') partial.trigger = jest.fn(); + if (type === 'webhookNode') partial.webhook = jest.fn(); + return mock(partial); + }); + + test.each([ + ['should skip disabled nodes', disabledNode, false], + ['should skip nodes marked as ignored', ignoredNode, false], + ['should skip unknown nodes', unknownNode, false], + ['should skip nodes with no trigger method', noTriggersNode, false], + ['should activate if poll method exists', pollNode, true], + ['should activate if trigger method exists', triggerNode, true], + ['should activate if webhook method exists', webhookNode, true], + ])('%s', async (_, node, expected) => { + const params = mock({ nodeTypes }); + params.nodes = [node]; + const workflow = new Workflow(params); + expect(workflow.checkIfWorkflowCanBeActivated(['ignoredNode'])).toBe(expected); + }); + }); + + describe('renameNodeInParameterValue', () => { + describe('for expressions', () => { + const tests = [ + { + description: 'do nothing if there is no expression', + input: { + currentName: 'Node1', + newName: 'Node1New', + parameters: { + value1: 'value1Node1', + value2: 'value2Node1', + }, + }, + output: { value1: 'value1Node1', value2: 'value2Node1', }, }, - output: { - value1: 'value1Node1', - value2: 'value2Node1', - }, - }, - { - description: 'should work with dot notation', - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - value1: "={{$node.Node1.data.value1 + 'Node1'}}", - value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + { + description: 'should work with dot notation', + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + value1: "={{$node.Node1.data.value1 + 'Node1'}}", + value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + }, + }, + output: { + value1: "={{$node.NewName.data.value1 + 'Node1'}}", + value2: "={{$node.NewName.data.value2 + ' - ' + $node.NewName.data.value2}}", }, }, - output: { - value1: "={{$node.NewName.data.value1 + 'Node1'}}", - value2: "={{$node.NewName.data.value2 + ' - ' + $node.NewName.data.value2}}", - }, - }, - { - description: 'should work with ["nodeName"]', - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - value1: '={{$node["Node1"]["data"]["value1"] + \'Node1\'}}', + { + description: 'should work with ["nodeName"]', + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + value1: '={{$node["Node1"]["data"]["value1"] + \'Node1\'}}', + value2: + '={{$node["Node1"]["data"]["value2"] + \' - \' + $node["Node1"]["data"]["value2"]}}', + }, + }, + output: { + value1: '={{$node["NewName"]["data"]["value1"] + \'Node1\'}}', value2: - '={{$node["Node1"]["data"]["value2"] + \' - \' + $node["Node1"]["data"]["value2"]}}', + '={{$node["NewName"]["data"]["value2"] + \' - \' + $node["NewName"]["data"]["value2"]}}', }, }, - output: { - value1: '={{$node["NewName"]["data"]["value1"] + \'Node1\'}}', - value2: - '={{$node["NewName"]["data"]["value2"] + \' - \' + $node["NewName"]["data"]["value2"]}}', - }, - }, - { - description: 'should work with $("Node1")', - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - value1: '={{$("Node1")["data"]["value1"] + \'Node1\'}}', - value2: '={{$("Node1")["data"]["value2"] + \' - \' + $("Node1")["data"]["value2"]}}', + { + description: 'should work with $("Node1")', + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + value1: '={{$("Node1")["data"]["value1"] + \'Node1\'}}', + value2: '={{$("Node1")["data"]["value2"] + \' - \' + $("Node1")["data"]["value2"]}}', + }, }, - }, - output: { - value1: '={{$("NewName")["data"]["value1"] + \'Node1\'}}', - value2: '={{$("NewName")["data"]["value2"] + \' - \' + $("NewName")["data"]["value2"]}}', - }, - }, - { - description: 'should work with $items("Node1")', - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - value1: '={{$items("Node1")["data"]["value1"] + \'Node1\'}}', + output: { + value1: '={{$("NewName")["data"]["value1"] + \'Node1\'}}', value2: - '={{$items("Node1")["data"]["value2"] + \' - \' + $items("Node1")["data"]["value2"]}}', + '={{$("NewName")["data"]["value2"] + \' - \' + $("NewName")["data"]["value2"]}}', }, }, - output: { - value1: '={{$items("NewName")["data"]["value1"] + \'Node1\'}}', - value2: - '={{$items("NewName")["data"]["value2"] + \' - \' + $items("NewName")["data"]["value2"]}}', - }, - }, - { - description: 'should work with $items("Node1", 0, 1)', - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - value1: '={{$items("Node1", 0, 1)["data"]["value1"] + \'Node1\'}}', + { + description: 'should work with $items("Node1")', + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + value1: '={{$items("Node1")["data"]["value1"] + \'Node1\'}}', + value2: + '={{$items("Node1")["data"]["value2"] + \' - \' + $items("Node1")["data"]["value2"]}}', + }, + }, + output: { + value1: '={{$items("NewName")["data"]["value1"] + \'Node1\'}}', value2: - '={{$items("Node1", 0, 1)["data"]["value2"] + \' - \' + $items("Node1", 0, 1)["data"]["value2"]}}', + '={{$items("NewName")["data"]["value2"] + \' - \' + $items("NewName")["data"]["value2"]}}', }, }, - output: { - value1: '={{$items("NewName", 0, 1)["data"]["value1"] + \'Node1\'}}', - value2: - '={{$items("NewName", 0, 1)["data"]["value2"] + \' - \' + $items("NewName", 0, 1)["data"]["value2"]}}', - }, - }, - { - description: 'should work with dot notation that contains space and special character', - input: { - currentName: 'Node1', - newName: 'New $ Name', - parameters: { - value1: "={{$node.Node1.data.value1 + 'Node1'}}", - value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + { + description: 'should work with $items("Node1", 0, 1)', + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + value1: '={{$items("Node1", 0, 1)["data"]["value1"] + \'Node1\'}}', + value2: + '={{$items("Node1", 0, 1)["data"]["value2"] + \' - \' + $items("Node1", 0, 1)["data"]["value2"]}}', + }, }, - }, - output: { - value1: '={{$node["New $ Name"].data.value1 + \'Node1\'}}', - value2: - '={{$node["New $ Name"].data.value2 + \' - \' + $node["New $ Name"].data.value2}}', - }, - }, - { - description: 'should work with dot notation that contains space and trailing $', - input: { - currentName: 'Node1', - newName: 'NewName$', - parameters: { - value1: "={{$node.Node1.data.value1 + 'Node1'}}", - value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + output: { + value1: '={{$items("NewName", 0, 1)["data"]["value1"] + \'Node1\'}}', + value2: + '={{$items("NewName", 0, 1)["data"]["value2"] + \' - \' + $items("NewName", 0, 1)["data"]["value2"]}}', }, }, - output: { - value1: '={{$node["NewName$"].data.value1 + \'Node1\'}}', - value2: '={{$node["NewName$"].data.value2 + \' - \' + $node["NewName$"].data.value2}}', - }, - }, - { - description: 'should work with dot notation that contains space and special character', - input: { - currentName: 'Node1', - newName: 'NewName $ $& $` $$$', - parameters: { - value1: "={{$node.Node1.data.value1 + 'Node1'}}", - value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + { + description: 'should work with dot notation that contains space and special character', + input: { + currentName: 'Node1', + newName: 'New $ Name', + parameters: { + value1: "={{$node.Node1.data.value1 + 'Node1'}}", + value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + }, + }, + output: { + value1: '={{$node["New $ Name"].data.value1 + \'Node1\'}}', + value2: + '={{$node["New $ Name"].data.value2 + \' - \' + $node["New $ Name"].data.value2}}', }, }, - output: { - value1: '={{$node["NewName $ $& $` $$$"].data.value1 + \'Node1\'}}', - value2: - '={{$node["NewName $ $& $` $$$"].data.value2 + \' - \' + $node["NewName $ $& $` $$$"].data.value2}}', + { + description: 'should work with dot notation that contains space and trailing $', + input: { + currentName: 'Node1', + newName: 'NewName$', + parameters: { + value1: "={{$node.Node1.data.value1 + 'Node1'}}", + value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + }, + }, + output: { + value1: '={{$node["NewName$"].data.value1 + \'Node1\'}}', + value2: '={{$node["NewName$"].data.value2 + \' - \' + $node["NewName$"].data.value2}}', + }, }, - }, - { - description: 'should work with dot notation without trailing dot', - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - value1: "={{$node.Node1 + 'Node1'}}", - value2: "={{$node.Node1 + ' - ' + $node.Node1}}", + { + description: 'should work with dot notation that contains space and special character', + input: { + currentName: 'Node1', + newName: 'NewName $ $& $` $$$', + parameters: { + value1: "={{$node.Node1.data.value1 + 'Node1'}}", + value2: "={{$node.Node1.data.value2 + ' - ' + $node.Node1.data.value2}}", + }, + }, + output: { + value1: '={{$node["NewName $ $& $` $$$"].data.value1 + \'Node1\'}}', + value2: + '={{$node["NewName $ $& $` $$$"].data.value2 + \' - \' + $node["NewName $ $& $` $$$"].data.value2}}', }, }, - output: { - value1: "={{$node.NewName + 'Node1'}}", - value2: "={{$node.NewName + ' - ' + $node.NewName}}", + { + description: 'should work with dot notation without trailing dot', + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + value1: "={{$node.Node1 + 'Node1'}}", + value2: "={{$node.Node1 + ' - ' + $node.Node1}}", + }, + }, + output: { + value1: "={{$node.NewName + 'Node1'}}", + value2: "={{$node.NewName + ' - ' + $node.NewName}}", + }, }, - }, - { - description: "should work with ['nodeName']", - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - value1: "={{$node['Node1']['data']['value1'] + 'Node1'}}", + { + description: "should work with ['nodeName']", + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + value1: "={{$node['Node1']['data']['value1'] + 'Node1'}}", + value2: + "={{$node['Node1']['data']['value2'] + ' - ' + $node['Node1']['data']['value2']}}", + }, + }, + output: { + value1: "={{$node['NewName']['data']['value1'] + 'Node1'}}", value2: - "={{$node['Node1']['data']['value2'] + ' - ' + $node['Node1']['data']['value2']}}", + "={{$node['NewName']['data']['value2'] + ' - ' + $node['NewName']['data']['value2']}}", }, }, - output: { - value1: "={{$node['NewName']['data']['value1'] + 'Node1'}}", - value2: - "={{$node['NewName']['data']['value2'] + ' - ' + $node['NewName']['data']['value2']}}", - }, - }, - { - description: 'should work on lower levels', - input: { - currentName: 'Node1', - newName: 'NewName', - parameters: { - level1a: "={{$node.Node1.data.value1 + 'Node1'}}", + { + description: 'should work on lower levels', + input: { + currentName: 'Node1', + newName: 'NewName', + parameters: { + level1a: "={{$node.Node1.data.value1 + 'Node1'}}", + level1b: [ + { + value2a: "={{$node.Node1.data.value1 + 'Node1'}}", + value2b: "={{$node.Node1.data.value1 + 'Node1'}}", + }, + ], + level1c: { + value2a: { + value3a: "={{$node.Node1.data.value1 + 'Node1'}}", + value3b: [ + { + value4a: "={{$node.Node1.data.value1 + 'Node1'}}", + value4b: { + value5a: "={{$node.Node1.data.value1 + 'Node1'}}", + value5b: "={{$node.Node1.data.value1 + 'Node1'}}", + }, + }, + ], + }, + }, + } as INodeParameters, + }, + output: { + level1a: "={{$node.NewName.data.value1 + 'Node1'}}", level1b: [ { - value2a: "={{$node.Node1.data.value1 + 'Node1'}}", - value2b: "={{$node.Node1.data.value1 + 'Node1'}}", + value2a: "={{$node.NewName.data.value1 + 'Node1'}}", + value2b: "={{$node.NewName.data.value1 + 'Node1'}}", }, ], level1c: { value2a: { - value3a: "={{$node.Node1.data.value1 + 'Node1'}}", + value3a: "={{$node.NewName.data.value1 + 'Node1'}}", value3b: [ { - value4a: "={{$node.Node1.data.value1 + 'Node1'}}", + value4a: "={{$node.NewName.data.value1 + 'Node1'}}", value4b: { - value5a: "={{$node.Node1.data.value1 + 'Node1'}}", - value5b: "={{$node.Node1.data.value1 + 'Node1'}}", + value5a: "={{$node.NewName.data.value1 + 'Node1'}}", + value5b: "={{$node.NewName.data.value1 + 'Node1'}}", }, }, ], }, }, - } as INodeParameters, - }, - output: { - level1a: "={{$node.NewName.data.value1 + 'Node1'}}", - level1b: [ - { - value2a: "={{$node.NewName.data.value1 + 'Node1'}}", - value2b: "={{$node.NewName.data.value1 + 'Node1'}}", - }, - ], - level1c: { - value2a: { - value3a: "={{$node.NewName.data.value1 + 'Node1'}}", - value3b: [ - { - value4a: "={{$node.NewName.data.value1 + 'Node1'}}", - value4b: { - value5a: "={{$node.NewName.data.value1 + 'Node1'}}", - value5b: "={{$node.NewName.data.value1 + 'Node1'}}", - }, - }, - ], - }, }, }, - }, - ]; + ]; - const nodeTypes = Helpers.NodeTypes(); - const workflow = new Workflow({ nodes: [], connections: {}, active: false, nodeTypes }); + const nodeTypes = Helpers.NodeTypes(); + const workflow = new Workflow({ nodes: [], connections: {}, active: false, nodeTypes }); - for (const testData of tests) { - test(testData.description, () => { - const result = workflow.renameNodeInParameterValue( - testData.input.parameters, - testData.input.currentName, - testData.input.newName, - ); - expect(result).toEqual(testData.output); - }); - } - }); + for (const testData of tests) { + test(testData.description, () => { + const result = workflow.renameNodeInParameterValue( + testData.input.parameters, + testData.input.currentName, + testData.input.newName, + ); + expect(result).toEqual(testData.output); + }); + } + }); - describe('renameNodeInParameterValue for node with renamable content', () => { - const tests = [ - { - description: "should work with $('name')", - input: { - currentName: 'Old', - newName: 'New', - parameters: { jsCode: "$('Old').first();" }, + describe('for node with renamable content', () => { + const tests = [ + { + description: "should work with $('name')", + input: { + currentName: 'Old', + newName: 'New', + parameters: { jsCode: "$('Old').first();" }, + }, + output: { jsCode: "$('New').first();" }, }, - output: { jsCode: "$('New').first();" }, - }, - { - description: "should work with $node['name'] and $node.name", - input: { - currentName: 'Old', - newName: 'New', - parameters: { jsCode: "$node['Old'].first(); $node.Old.first();" }, + { + description: "should work with $node['name'] and $node.name", + input: { + currentName: 'Old', + newName: 'New', + parameters: { jsCode: "$node['Old'].first(); $node.Old.first();" }, + }, + output: { jsCode: "$node['New'].first(); $node.New.first();" }, }, - output: { jsCode: "$node['New'].first(); $node.New.first();" }, - }, - { - description: 'should work with $items()', - input: { - currentName: 'Old', - newName: 'New', - parameters: { jsCode: "$items('Old').first();" }, + { + description: 'should work with $items()', + input: { + currentName: 'Old', + newName: 'New', + parameters: { jsCode: "$items('Old').first();" }, + }, + output: { jsCode: "$items('New').first();" }, }, - output: { jsCode: "$items('New').first();" }, - }, - ]; - - const workflow = new Workflow({ - nodes: [], - connections: {}, - active: false, - nodeTypes: Helpers.NodeTypes(), - }); + ]; - for (const t of tests) { - test(t.description, () => { - expect( - workflow.renameNodeInParameterValue( - t.input.parameters, - t.input.currentName, - t.input.newName, - { hasRenamableContent: true }, - ), - ).toEqual(t.output); + const workflow = new Workflow({ + nodes: [], + connections: {}, + active: false, + nodeTypes: Helpers.NodeTypes(), }); - } + + for (const t of tests) { + test(t.description, () => { + expect( + workflow.renameNodeInParameterValue( + t.input.parameters, + t.input.currentName, + t.input.newName, + { hasRenamableContent: true }, + ), + ).toEqual(t.output); + }); + } + }); }); describe('renameNode', () => { @@ -377,7 +428,7 @@ describe('Workflow', () => { [ { node: 'Node2', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -408,7 +459,7 @@ describe('Workflow', () => { [ { node: 'Node2', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -444,7 +495,7 @@ describe('Workflow', () => { [ { node: 'Node2', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -475,7 +526,7 @@ describe('Workflow', () => { [ { node: 'Node2New', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -532,7 +583,7 @@ describe('Workflow', () => { [ { node: 'Node3', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -543,12 +594,12 @@ describe('Workflow', () => { [ { node: 'Node3', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, { node: 'Node5', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -559,12 +610,12 @@ describe('Workflow', () => { [ { node: 'Node4', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, { node: 'Node5', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -616,7 +667,7 @@ describe('Workflow', () => { [ { node: 'Node3New', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -627,12 +678,12 @@ describe('Workflow', () => { [ { node: 'Node3New', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, { node: 'Node5', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -643,12 +694,12 @@ describe('Workflow', () => { [ { node: 'Node4', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, { node: 'Node5', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1229,7 +1280,7 @@ describe('Workflow', () => { [ { node: 'Node2', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1240,7 +1291,7 @@ describe('Workflow', () => { [ { node: 'Node3', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1251,7 +1302,7 @@ describe('Workflow', () => { [ { node: 'Node2', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1521,7 +1572,7 @@ describe('Workflow', () => { [ { node: 'Set', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1532,7 +1583,7 @@ describe('Workflow', () => { [ { node: 'Set1', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1591,21 +1642,21 @@ describe('Workflow', () => { [ { node: 'Set1', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], [ { node: 'Set', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], [ { node: 'Set', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1616,7 +1667,7 @@ describe('Workflow', () => { [ { node: 'Set2', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1627,7 +1678,7 @@ describe('Workflow', () => { [ { node: 'Set2', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1691,7 +1742,7 @@ describe('Workflow', () => { [ { node: 'Set', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1699,7 +1750,7 @@ describe('Workflow', () => { [ { node: 'Switch', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1710,7 +1761,7 @@ describe('Workflow', () => { [ { node: 'Set1', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1721,12 +1772,12 @@ describe('Workflow', () => { [ { node: 'Set1', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, { node: 'Switch', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], @@ -1737,7 +1788,7 @@ describe('Workflow', () => { [ { node: 'Set1', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ],