diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 98d3702d9c..9a150b04b1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,8 +22,10 @@ A clear and concise description of what you expected to happen. **Environment (please complete the following information):** - OS: [e.g. Ubuntu Linux 18.04] - - n8n Version [e.g. 0.26.0] - - Node.js Version [e.g. 10.16.0] + - n8n Version [e.g. 0.119.0] + - Node.js Version [e.g. 14.16.0] + - Database system [e.g. SQLite; n8n uses SQLite as default otherwise changed] + - Operation mode [e.g. own; operation modes are `own`, `main` and `queue`. Default is `own`] **Additional context** Add any other context about the problem here. diff --git a/.gitignore b/.gitignore index 0441d445b4..d457b50091 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ _START_PACKAGE .vscode .idea .prettierrc.js +vetur.config.js diff --git a/README.md b/README.md index 6e1e7e1766..1fd4f1128c 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,7 @@ If you have problems or questions go to our forum, we will then try to help you ## Jobs If you are interested in working for n8n and so shape the future of the project -check out our job posts: - -[https://n8n.join.com](https://n8n.join.com) +check out our [job posts](https://apply.workable.com/n8n/) diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index ef0d7123e7..cb484addb0 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -49,6 +49,7 @@ Additional information and example workflows on the n8n.io website: [https://n8n docker run -it --rm \ --name n8n \ -p 5678:5678 \ + -v ~/.n8n:/home/node/.n8n \ n8nio/n8n ``` @@ -262,9 +263,7 @@ If you have problems or questions go to our forum, we will then try to help you ## Jobs If you are interested in working for n8n and so shape the future of the project -check out our job posts: - -[https://n8n.join.com](https://n8n.join.com) +check out our [job posts](https://apply.workable.com/n8n/) diff --git a/packages/cli/README.md b/packages/cli/README.md index e7f951bbbe..d7aad52434 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -2,7 +2,7 @@ ![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png) -n8n is a free and open [fair-code](http://faircode.io) distributed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools. +n8n is a free and open [fair-code](http://faircode.io) distributed node-based Workflow Automation Tool. You can self-host n8n, easily extend it, and even use it with internal tools. n8n.io - Screenshot @@ -11,100 +11,145 @@ n8n is a free and open [fair-code](http://faircode.io) distributed node based Wo - [Demo](#demo) +- [Getting Started](#getting-started) + - [Use npx](#use-npx) + - [Run with Docker](#run-with-docker) + - [Install with npm](#install-with-npm) + - [Sign-up on n8n.cloud](#sign-up-on-n8n.cloud) - [Available integrations](#available-integrations) - [Documentation](#documentation) - [Create Custom Nodes](#create-custom-nodes) -- [Hosted n8n](#hosted-n8n) +- [Contributing](#contributing) - [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it) - [Support](#support) - [Jobs](#jobs) - [Upgrading](#upgrading) - [License](#license) -- [Development](#development) ## Demo -[:tv: A short demo (< 3 min)](https://www.youtube.com/watch?v=3w7xIMKLVAg) -which shows how to create a simple workflow which automatically sends a new -Slack notification every time a Github repository received or lost a star. +πŸ“Ί Here's a [short demo (<3 min)](https://www.youtube.com/watch?v=3w7xIMKLVAg) that shows how to create a simple workflow to automatically sends a notification on Slack every time a GitHub repository gets starred or un-starred. +## Getting Started + +There are a couple of ways to get started with n8n. + +### Use npx + +To spin up n8n using npx, you can run: + +```bash +npx n8n +``` + +It will download everything that is needed to start n8n. + +You can then access n8n by opening: +[http://localhost:5678](http://localhost:5678) + +**Note:** The minimum required version for Node.js is v14.15. Make sure to update Node.js to v14.15 or above. + +### Run with Docker + +To play around with n8n, you can also start it using Docker: + +```bash +docker run -it --rm \ + --name n8n \ + -p 5678:5678 \ + n8nio/n8n +``` + +Be aware that all the data will be lost once the Docker container gets removed. To persist the data mount the `~/.n8n` folder: + +```bash +docker run -it --rm \ + --name n8n \ + -p 5678:5678 \ + -v ~/.n8n:/home/node/.n8n \ + n8nio/n8n +``` + +n8n also offers a Docker image for Raspberry Pi: `n8nio/n8n:latest-rpi`. + +Refer to the [documentation](https://github.com/n8n-io/n8n/blob/master/docker/images/n8n/README.md) for more information on the Docker setup. + +### Install with npm + +To install n8n globally using npm: + +```bash +npm install n8n -g +``` + +After the installation, start n8n running the following command: + +```bash +n8n +# or +n8n start +``` + +### Sign-up on n8n.cloud + +Sign-up for an [n8n.cloud](https://www.n8n.cloud/) account. + +While n8n.cloud and n8n are the same in terms of features, n8n.cloud provides certain conveniences such as: +- Not having to set up and maintain your n8n instance +- Managed OAuth for authentication +- Easily upgrading to the newer n8n versions ## Available integrations -n8n has 200+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) - +n8n has 280+ different nodes that allow you to connect various services and build your automation workflows. You can find the list of all the integrations at [https://n8n.io/integrations](https://n8n.io/integrations) ## Documentation -The official n8n documentation can be found under: [https://docs.n8n.io](https://docs.n8n.io) +To learn more about n8n, refer to the official documentation here: [https://docs.n8n.io](https://docs.n8n.io) -Additional information and example workflows on the n8n.io website: [https://n8n.io](https://n8n.io) +You can find additional information and example workflows on the [n8n.io](https://n8n.io) website. ## Create Custom Nodes -It is very easy to create own nodes for n8n. More information about that can -be found in the documentation of "n8n-node-dev" which is a small CLI which -helps with n8n-node-development. +You can create custom nodes for n8n. Follow the instructions mentioned in the documentation to create your node: [Creating nodes](https://docs.n8n.io/nodes/creating-nodes/create-node.html) -[To n8n-node-dev](https://github.com/n8n-io/n8n/tree/master/packages/node-dev) +## Contributing -Additional information can be found on the [ documentation page](https://docs.n8n.io/#/create-node). +πŸ› Did you find a bug? +✨ Do you want to contribute a feature? -## Hosted n8n +The [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you set up your development environment. -If you are interested in a hosted version of n8n on our infrastructure please contact us via: -[hosting@n8n.io](mailto:hosting@n8n.io) +You can find more information on how you can contribute to the project on our documentation: [How can I contribute?](https://docs.n8n.io/reference/contributing.html) +## What does n8n mean, and how do you pronounce it? +**Short answer:** n8n is an abbreviation for "nodemation", and it is pronounced as n-eight-n. -## What does n8n mean and how do you pronounce it? - -**Short answer:** It means "nodemation" and it is pronounced as n-eight-n. - -**Long answer:** I get that question quite often (more often than I expected) -so I decided it is probably best to answer it here. While looking for a -good name for the project with a free domain I realized very quickly that all the -good ones I could think of were already taken. So, in the end, I chose -nodemation. "node-" in the sense that it uses a Node-View and that it uses -Node.js and "-mation" for "automation" which is what the project is supposed to help with. -However, I did not like how long the name was and I could not imagine writing -something that long every time in the CLI. That is when I then ended up on -"n8n". Sure does not work perfectly but does neither for Kubernetes (k8s) and -did not hear anybody complain there. So I guess it should be ok. +**Long answer:** In n8n, you build your automation ("-mation") workflows by connecting different nodes in the Editor UI. The project is also built using Node.js. As a consequence, the project was named nodemation. +However, the name was long, and it wouldn't be a good idea to use such a long name in the CLI. Hence, nodemation got abbreviated as "n8n" (there are eight characters between the first and the last n!). ## Support -If you have problems or questions go to our forum, we will then try to help you asap: - -[https://community.n8n.io](https://community.n8n.io) - - +If you run into issues or have any questions reach out to us via our community forum: [https://community.n8n.io](https://community.n8n.io). ## Jobs -If you are interested in working for n8n and so shape the future of the project -check out our job posts: - -[https://n8n.join.com](https://n8n.join.com) +If you are interested in working at n8n and building the project, check out the [job openings](https://apply.workable.com/n8n/). ## Upgrading -Before you upgrade to the latest version make sure to check here if there are any breaking changes which concern you: -[Breaking Changes](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md) +Before you upgrade to the latest version, make sure to check the changelogs: [Changelog](https://docs.n8n.io/reference/changelog.html) +You can also find breaking changes here: [Breaking Changes](./BREAKING-CHANGES.md) ## License -n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license +n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license. -Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license) - - -## Development - -Have you found a bug :bug: ? Or maybe you have a nice feature :sparkles: to contribute ? The [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you get your development environment ready in minutes. +Additional information on the license can be found in the [FAQ](https://docs.n8n.io/reference/faq.html#license) diff --git a/packages/cli/package.json b/packages/cli/package.json index aa48f92da2..252994f485 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.118.0", + "version": "0.121.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -55,6 +55,7 @@ "devDependencies": { "@oclif/dev-cli": "^1.22.2", "@types/basic-auth": "^1.1.2", + "@types/bcryptjs": "^2.4.2", "@types/bull": "^3.3.10", "@types/compression": "1.0.1", "@types/connect-history-api-fallback": "^1.3.1", @@ -79,11 +80,11 @@ "typescript": "~3.9.7" }, "dependencies": { - "@node-rs/bcrypt": "^1.2.0", "@oclif/command": "^1.5.18", "@oclif/errors": "^1.2.2", "@types/jsonwebtoken": "^8.3.4", "basic-auth": "^2.0.1", + "bcryptjs": "^2.4.3", "body-parser": "^1.18.3", "body-parser-xml": "^1.1.0", "bull": "^3.19.0", @@ -104,10 +105,10 @@ "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", "mysql2": "~2.2.0", - "n8n-core": "~0.69.0", - "n8n-editor-ui": "~0.88.0", - "n8n-nodes-base": "~0.115.0", - "n8n-workflow": "~0.57.0", + "n8n-core": "~0.72.0", + "n8n-editor-ui": "~0.91.0", + "n8n-nodes-base": "~0.118.0", + "n8n-workflow": "~0.59.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^8.3.0", diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 9f9b9f3580..60b10b5b08 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -35,7 +35,7 @@ import { } from 'n8n-workflow'; import * as express from 'express'; -import { +import { LoggerProxy as Logger, } from 'n8n-workflow'; @@ -67,15 +67,15 @@ export class ActiveWorkflowRunner { for (const workflowData of workflowsData) { console.log(` - ${workflowData.name}`); - Logger.debug(`Initializing active workflow "${workflowData.name}" (startup)`, {workflowName: workflowData.name, workflowId: workflowData.id}); + Logger.debug(`Initializing active workflow "${workflowData.name}" (startup)`, { workflowName: workflowData.name, workflowId: workflowData.id }); try { await this.add(workflowData.id.toString(), 'init', workflowData); - Logger.verbose(`Successfully started workflow "${workflowData.name}"`, {workflowName: workflowData.name, workflowId: workflowData.id}); + Logger.verbose(`Successfully started workflow "${workflowData.name}"`, { workflowName: workflowData.name, workflowId: workflowData.id }); console.log(` => Started`); } catch (error) { console.log(` => ERROR: Workflow could not be activated:`); console.log(` ${error.message}`); - Logger.error(`Unable to initialize workflow "${workflowData.name}" (startup)`, {workflowName: workflowData.name, workflowId: workflowData.id}); + Logger.error(`Unable to initialize workflow "${workflowData.name}" (startup)`, { workflowName: workflowData.name, workflowId: workflowData.id }); } } Logger.verbose('Finished initializing active workflows (startup)'); @@ -188,7 +188,7 @@ export class ActiveWorkflowRunner { } const nodeTypes = NodeTypes(); - const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings}); + const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings }); const credentials = await WorkflowCredentials([workflow.getNode(webhook.node as string) as INode]); @@ -225,8 +225,8 @@ export class ActiveWorkflowRunner { * @returns {Promise} * @memberof ActiveWorkflowRunner */ - async getWebhookMethods(path: string) : Promise { - const webhooks = await Db.collections.Webhook?.find({ webhookPath: path}) as IWebhookDb[]; + async getWebhookMethods(path: string): Promise { + const webhooks = await Db.collections.Webhook?.find({ webhookPath: path }) as IWebhookDb[]; // Gather all request methods in string array const webhookMethods: string[] = webhooks.map(webhook => webhook.method); @@ -463,7 +463,7 @@ export class ActiveWorkflowRunner { * @returns {IGetExecuteTriggerFunctions} * @memberof ActiveWorkflowRunner */ - getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions{ + getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions { return ((workflow: Workflow, node: INode) => { const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation); returnFunctions.emit = (data: INodeExecutionData[][]): void => { @@ -517,8 +517,8 @@ export class ActiveWorkflowRunner { if (workflowInstance.getTriggerNodes().length !== 0 || workflowInstance.getPollNodes().length !== 0) { - await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions); - Logger.info(`Successfully activated workflow "${workflowData.name}"`); + await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions); + Logger.verbose(`Successfully activated workflow "${workflowData.name}"`, { workflowId, workflowName: workflowData.name }); } if (this.activationErrors[workflowId] !== undefined) { @@ -569,7 +569,8 @@ export class ActiveWorkflowRunner { // if it's active in memory then it's a trigger // so remove from list of actives workflows if (this.activeWorkflows.isActive(workflowId)) { - this.activeWorkflows.remove(workflowId); + await this.activeWorkflows.remove(workflowId); + Logger.verbose(`Successfully deactivated workflow "${workflowId}"`, { workflowId }); } return; diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index 8dba633baa..80b107f2be 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -11,22 +11,6 @@ import { IPackageVersions } from './'; let versionCache: IPackageVersions | undefined; -/** - * Displays a message to the user - * - * @export - * @param {string} message The message to display - * @param {string} [level='log'] - */ -export function logOutput(message: string, level = 'log'): void { - if (level === 'log') { - console.log(message); - } else if (level === 'error') { - console.error(message); - } -} - - /** * Returns the base URL n8n is reachable from * diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index a69c6be33f..2b1263e6d0 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -4,11 +4,18 @@ import { } from 'n8n-core'; import { ICredentialType, + ILogger, INodeType, INodeTypeData, + LoggerProxy, } from 'n8n-workflow'; import * as config from '../config'; + +import { + getLogger, +} from '../src/Logger'; + import { access as fsAccess, readdir as fsReaddir, @@ -31,7 +38,12 @@ class LoadNodesAndCredentialsClass { nodeModulesPath = ''; + logger: ILogger; + async init() { + this.logger = getLogger(); + LoggerProxy.init(this.logger); + // Get the path to the node-modules folder to be later able // to load the credentials and nodes const checkPaths = [ @@ -171,6 +183,10 @@ class LoadNodesAndCredentialsClass { tempNode.description.icon = 'file:' + path.join(path.dirname(filePath), tempNode.description.icon.substr(5)); } + if (tempNode.executeSingle) { + this.logger.warn(`"executeSingle" will get deprecated soon. Please update the code of node "${packageName}.${nodeName}" to use "execute" instead!`, { filePath }); + } + if (this.includeNodes !== undefined && !this.includeNodes.includes(fullNodeName)) { return; } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index cbe43697f3..ba4eb543d0 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -8,7 +8,6 @@ import { resolve as pathResolve, } from 'path'; import { - getConnection, getConnectionManager, In, } from 'typeorm'; @@ -22,7 +21,9 @@ import { RequestOptions } from 'oauth-1.0a'; import * as csrf from 'csrf'; import * as requestPromise from 'request-promise-native'; import { createHmac } from 'crypto'; -import { compare } from '@node-rs/bcrypt'; +// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and +// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ... +import { compare } from 'bcryptjs'; import * as promClient from 'prom-client'; import { @@ -572,6 +573,7 @@ class App { const newWorkflowData = req.body as IWorkflowBase; const id = req.params.id; + newWorkflowData.id = id; await this.externalHooks.run('workflow.update', [newWorkflowData]); @@ -716,6 +718,7 @@ class App { // get generated dynamically this.app.get(`/${this.restEndpoint}/node-parameter-options`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const nodeType = req.query.nodeType as string; + const path = req.query.path as string; let credentials: INodeCredentials | undefined = undefined; const currentNodeParameters = JSON.parse('' + req.query.currentNodeParameters) as INodeParameters; if (req.query.credentials !== undefined) { @@ -725,7 +728,7 @@ class App { const nodeTypes = NodeTypes(); - const loadDataInstance = new LoadNodeParameterOptions(nodeType, nodeTypes, JSON.parse('' + req.query.currentNodeParameters), credentials!); + const loadDataInstance = new LoadNodeParameterOptions(nodeType, nodeTypes, path, JSON.parse('' + req.query.currentNodeParameters), credentials!); const workflowData = loadDataInstance.getWorkflowData() as IWorkflowBase; const workflowCredentials = await WorkflowCredentials(workflowData.nodes); @@ -1732,6 +1735,7 @@ class App { } ); } + returnData.sort((a, b) => parseInt(b.id, 10) - parseInt(a.id, 10)); return returnData; } diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 14ae4a1abf..4a09c76a89 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -194,9 +194,9 @@ export class WorkflowRunnerProcess { * @param {any[]} parameters * @memberof WorkflowRunnerProcess */ - sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any + async sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any try { - sendToParentProcess('processHook', { + await sendToParentProcess('processHook', { hook, parameters, }); @@ -217,22 +217,22 @@ export class WorkflowRunnerProcess { const hookFunctions: IWorkflowExecuteHooks = { nodeExecuteBefore: [ async (nodeName: string): Promise => { - this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]); + await this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]); }, ], nodeExecuteAfter: [ async (nodeName: string, data: ITaskData): Promise => { - this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]); + await this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]); }, ], workflowExecuteBefore: [ async (): Promise => { - this.sendHookToParentProcess('workflowExecuteBefore', []); + await this.sendHookToParentProcess('workflowExecuteBefore', []); }, ], workflowExecuteAfter: [ async (fullRunData: IRun, newStaticData?: IDataObject): Promise => { - this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]); + await this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]); }, ], }; diff --git a/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts new file mode 100644 index 0000000000..ea1bd42049 --- /dev/null +++ b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import * as config from '../../../../config'; + +export class ChangeCredentialDataSize1620729500000 implements MigrationInterface { + name = 'ChangeCredentialDataSize1620729500000'; + + async up(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + + await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `type` varchar(128) NOT NULL'); + } + + async down(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + + await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `type` varchar(32) NOT NULL'); + } +} diff --git a/packages/cli/src/databases/mysqldb/migrations/index.ts b/packages/cli/src/databases/mysqldb/migrations/index.ts index 08ca45edbf..f5c110fec2 100644 --- a/packages/cli/src/databases/mysqldb/migrations/index.ts +++ b/packages/cli/src/databases/mysqldb/migrations/index.ts @@ -4,6 +4,7 @@ import { CreateIndexStoppedAt1594902918301 } from './1594902918301-CreateIndexSt import { AddWebhookId1611149998770 } from './1611149998770-AddWebhookId'; import { MakeStoppedAtNullable1607431743767 } from './1607431743767-MakeStoppedAtNullable'; import { ChangeDataSize1615306975123 } from './1615306975123-ChangeDataSize'; +import { ChangeCredentialDataSize1620729500000 } from './1620729500000-ChangeCredentialDataSize'; export const mysqlMigrations = [ InitialMigration1588157391238, @@ -12,4 +13,5 @@ export const mysqlMigrations = [ AddWebhookId1611149998770, MakeStoppedAtNullable1607431743767, ChangeDataSize1615306975123, + ChangeCredentialDataSize1620729500000, ]; diff --git a/packages/core/package.json b/packages/core/package.json index 69a76944f1..26f3b8b9d2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.69.0", + "version": "0.72.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -47,7 +47,7 @@ "file-type": "^14.6.2", "lodash.get": "^4.4.2", "mime-types": "^2.1.27", - "n8n-workflow": "~0.57.0", + "n8n-workflow": "~0.59.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "request": "^2.88.2", diff --git a/packages/core/src/LoadNodeParameterOptions.ts b/packages/core/src/LoadNodeParameterOptions.ts index 6a9b457503..d112f63bfd 100644 --- a/packages/core/src/LoadNodeParameterOptions.ts +++ b/packages/core/src/LoadNodeParameterOptions.ts @@ -18,10 +18,12 @@ const TEMP_WORKFLOW_NAME = 'Temp-Workflow'; export class LoadNodeParameterOptions { + path: string; workflow: Workflow; - constructor(nodeTypeName: string, nodeTypes: INodeTypes, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) { + constructor(nodeTypeName: string, nodeTypes: INodeTypes, path: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) { + this.path = path; const nodeType = nodeTypes.getByName(nodeTypeName); if (nodeType === undefined) { @@ -89,7 +91,7 @@ export class LoadNodeParameterOptions { throw new Error(`The node-type "${node!.type}" does not have the method "${methodName}" defined!`); } - const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(this.workflow, node!, additionalData); + const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(this.workflow, node!, this.path, additionalData); return nodeType!.methods.loadOptions[methodName].call(thisArgs); } diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index aeeb39c810..36004fd10c 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -691,7 +691,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx return continueOnFail(node); }, evaluateExpression: (expression: string, itemIndex: number) => { - return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); + return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); }, async executeWorkflow(workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[]): Promise { // tslint:disable-line:no-any return additionalData.executeWorkflow(workflowInfo, additionalData, inputData); @@ -742,7 +742,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx return getWorkflowMetadata(workflow); }, getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => { - const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); + const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode); return dataProxy.getDataProxy(); }, getWorkflowStaticData(type: string): IDataObject { @@ -789,7 +789,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData: }, evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => { evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex; - return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode); + return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode); }, getContext(type: string): IContextObject { return NodeHelpers.getContext(runExecutionData, type, node); @@ -841,7 +841,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData: return getWorkflowMetadata(workflow); }, getWorkflowDataProxy: (): IWorkflowDataProxyData => { - const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); + const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode); return dataProxy.getDataProxy(); }, getWorkflowStaticData(type: string): IDataObject { @@ -871,18 +871,20 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData: * @param {IWorkflowExecuteAdditionalData} additionalData * @returns {ILoadOptionsFunctions} */ -export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData): ILoadOptionsFunctions { - return ((workflow: Workflow, node: INode) => { +export function getLoadOptionsFunctions(workflow: Workflow, node: INode, path: string, additionalData: IWorkflowExecuteAdditionalData): ILoadOptionsFunctions { + return ((workflow: Workflow, node: INode, path: string) => { const that = { getCredentials(type: string): ICredentialDataDecryptedObject | undefined { return getCredentials(workflow, node, type, additionalData, 'internal'); }, - getCurrentNodeParameter: (parameterName: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined => { + getCurrentNodeParameter: (parameterPath: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined => { const nodeParameters = additionalData.currentNodeParameters; - if (nodeParameters && nodeParameters[parameterName]) { - return nodeParameters[parameterName]; + + if (parameterPath.charAt(0) === '&') { + parameterPath = `${path.split('.').slice(1, -1).join('.')}.${parameterPath.slice(1)}`; } - return undefined; + + return get(nodeParameters, parameterPath); }, getCurrentNodeParameters: (): INodeParameters | undefined => { return additionalData.currentNodeParameters; @@ -915,7 +917,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio }, }; return that; - })(workflow, node); + })(workflow, node, path); } diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index ccf591fe6b..b86a1f9a4e 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.88.0", + "version": "0.91.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -65,7 +65,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.57.0", + "n8n-workflow": "~0.59.0", "node-sass": "^4.12.0", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 1ac72fd90a..4fdbd770f8 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -131,7 +131,7 @@ export interface IRestApi { getSettings(): Promise; getNodeTypes(): Promise; getNodesInformation(nodeList: string[]): Promise; - getNodeParameterOptions(nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise; + getNodeParameterOptions(nodeType: string, path: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise; removeTestWebhook(workflowId: string): Promise; runWorkflow(runData: IStartRunData): Promise; createNewWorkflow(sendData: IWorkflowData): Promise; @@ -444,4 +444,4 @@ export interface ILinkMenuItemProperties { icon: string; href: string; newWindow?: boolean; -} \ No newline at end of file +} diff --git a/packages/editor-ui/src/components/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsList.vue index d576c6898b..e0e129a737 100644 --- a/packages/editor-ui/src/components/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsList.vue @@ -438,7 +438,8 @@ export default mixins( this.$store.commit('setActiveExecutions', results[1]); - const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => exec.id); + // execution IDs are typed as string, int conversion is necessary so we can order. + const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => parseInt(exec.id, 10)); let lastId = 0; const gaps = [] as number[]; for(let i = results[0].results.length - 1; i >= 0; i--) { @@ -459,7 +460,7 @@ export default mixins( // Check new results from end to start // Add new items accordingly. - const executionIndex = alreadyPresentExecutionIds.indexOf(currentItem.id); + const executionIndex = alreadyPresentExecutionIds.indexOf(currentId); if (executionIndex !== -1) { // Execution that we received is already present. @@ -477,7 +478,7 @@ export default mixins( // Find the correct position to place this newcomer let j; for (j = this.finishedExecutions.length - 1; j >= 0; j--) { - if (currentItem.id < this.finishedExecutions[j].id) { + if (currentId < parseInt(this.finishedExecutions[j].id, 10)) { this.finishedExecutions.splice(j + 1, 0, currentItem); break; } diff --git a/packages/editor-ui/src/components/ExpressionEdit.vue b/packages/editor-ui/src/components/ExpressionEdit.vue index ada71f6869..c073ce7793 100644 --- a/packages/editor-ui/src/components/ExpressionEdit.vue +++ b/packages/editor-ui/src/components/ExpressionEdit.vue @@ -30,7 +30,7 @@
Result
- + @@ -52,7 +52,11 @@ import { Workflow, } from 'n8n-workflow'; -export default Vue.extend({ +import { externalHooks } from '@/components/mixins/externalHooks'; + +import mixins from 'vue-typed-mixins'; + +export default mixins(externalHooks).extend({ name: 'ExpressionEdit', props: [ 'dialogVisible', @@ -81,7 +85,16 @@ export default Vue.extend({ }, itemSelected (eventData: IVariableItemSelected) { + // User inserted item from Expression Editor variable selector (this.$refs.inputFieldExpression as any).itemSelected(eventData); // tslint:disable-line:no-any + + this.$externalHooks().run('expressionEdit.itemSelected', { parameter: this.parameter, value: this.value, selectedItem: eventData }); + }, + }, + watch: { + dialogVisible (newValue) { + const resolvedExpressionValue = this.$refs.expressionResult && (this.$refs.expressionResult as any).getValue() || undefined; // tslint:disable-line:no-any + this.$externalHooks().run('expressionEdit.dialogVisibleChanged', { dialogVisible: newValue, parameter: this.parameter, value: this.value, resolvedExpressionValue }); }, }, }); diff --git a/packages/editor-ui/src/components/FixedCollectionParameter.vue b/packages/editor-ui/src/components/FixedCollectionParameter.vue index 9588e51a80..2d10f5b3ad 100644 --- a/packages/editor-ui/src/components/FixedCollectionParameter.vue +++ b/packages/editor-ui/src/components/FixedCollectionParameter.vue @@ -177,7 +177,11 @@ export default mixins(genericHelpers) } else if (optionParameter.typeOptions !== undefined && optionParameter.typeOptions.multipleValues === true) { // Multiple values are allowed so append option to array newParameterValue[optionParameter.name] = get(this.nodeValues, `${this.path}.${optionParameter.name}`, []); - (newParameterValue[optionParameter.name] as INodeParameters[]).push(JSON.parse(JSON.stringify(optionParameter.default))); + if (Array.isArray(optionParameter.default)) { + (newParameterValue[optionParameter.name] as INodeParameters[]).push(...JSON.parse(JSON.stringify(optionParameter.default))); + } else if (optionParameter.default !== '' && typeof optionParameter.default !== 'object') { + (newParameterValue[optionParameter.name] as INodeParameters[]).push(JSON.parse(JSON.stringify(optionParameter.default))); + } } else { // Add a new option newParameterValue[optionParameter.name] = JSON.parse(JSON.stringify(optionParameter.default)); diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 07f8ac7b2d..303b1fcb26 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -37,9 +37,6 @@