mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
🎨 Set up linting and formatting (#2120)
* ⬆️ Upgrade TS to 4.3.5
* 👕 Add ESLint configs
* 🎨 Add Prettier config
* 📦 Add deps and commands
* ⚡ Adjust global .editorconfig to new ruleset
* 🔥 Remove unneeded local .editorconfig
* 📦 Update deps in editor-ui
* 🔨 Limit Prettier to only TS files
* ⚡ Add recommended VSCode extensions
* 👕 Fix build
* 🔥 Remove Vue setting from global config
* ⚡ Disable prefer-default-export per feedback
* ✏️ Add forgotten divider
* 👕 Disable no-plusplus
* 👕 Disable class-methods-use-this
* ✏️ Alphabetize overrides
* 👕 Add one-var consecutive override
* ⏪ Revert one-var consecutive override
This reverts commit b9252cf935
.
* 🎨 👕 Lint and format workflow package (#2121)
* 🎨 Format /workflow package
* 👕 Lint /workflow package
* 🎨 Re-format /workflow package
* 👕 Re-lint /workflow package
* ✏️ Fix typo
* ⚡ Consolidate if-checks
* 🔥 Remove prefer-default-export exceptions
* 🔥 Remove no-plusplus exceptions
* 🔥 Remove class-methods-use-this exceptions
* 🎨 👕 Lint and format node-dev package (#2122)
* 🎨 Format /node-dev package
* ⚡ Exclude templates from ESLint config
This keeps the templates consistent with the codebase while preventing lint exceptions from being made part of the templates.
* 👕 Lint /node-dev package
* 🔥 Remove prefer-default-export exceptions
* 🔥 Remove no-plusplus exceptions
* 🎨 👕 Lint and format core package (#2123)
* 🎨 Format /core package
* 👕 Lint /core package
* 🎨 Re-format /core package
* 👕 Re-lint /core package
* 🔥 Remove prefer-default-export exceptions
* 🔥 Remove no-plusplus exceptions
* 🔥 Remove class-methods-use-this exceptions
* 🎨 👕 Lint and format cli package (#2124)
* 🎨 Format /cli package
* 👕 Exclude migrations from linting
* 👕 Lint /cli package
* 🎨 Re-format /cli package
* 👕 Re-lint /cli package
* 👕 Fix build
* 🔥 Remove prefer-default-export exceptions
* ⚡ Update exceptions in ActiveExecutions
* 🔥 Remove no-plusplus exceptions
* 🔥 Remove class-methods-use-this exceptions
* 👕 fix lint issues
* 🔧 use package specific linter, remove tslint command
* 🔨 resolve build issue, sync dependencies
* 🔧 change lint command
Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com>
This commit is contained in:
parent
223cd75685
commit
56c4c6991f
|
@ -12,9 +12,6 @@ trim_trailing_whitespace = true
|
|||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
|
354
.eslintrc.js
Normal file
354
.eslintrc.js
Normal file
|
@ -0,0 +1,354 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: ['./packages/*/tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
},
|
||||
ignorePatterns: [
|
||||
'.eslintrc.js',
|
||||
'**/*.js',
|
||||
'**/node_modules/**',
|
||||
'**/dist/**',
|
||||
'**/test/**',
|
||||
'**/templates/**',
|
||||
'**/ormconfig.ts',
|
||||
'**/migrations/**',
|
||||
],
|
||||
|
||||
extends: [
|
||||
/**
|
||||
* Config for typescript-eslint recommended ruleset (without type checking)
|
||||
*
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/1c1b572c3000d72cfe665b7afbada0ec415e7855/packages/eslint-plugin/src/configs/recommended.ts
|
||||
*/
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
|
||||
/**
|
||||
* Config for typescript-eslint recommended ruleset (with type checking)
|
||||
*
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/1c1b572c3000d72cfe665b7afbada0ec415e7855/packages/eslint-plugin/src/configs/recommended-requiring-type-checking.ts
|
||||
*/
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
|
||||
/**
|
||||
* Config for Airbnb style guide for TS, /base to remove React rules
|
||||
*
|
||||
* https://github.com/iamturns/eslint-config-airbnb-typescript
|
||||
* https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base/rules
|
||||
*/
|
||||
'eslint-config-airbnb-typescript/base',
|
||||
|
||||
/**
|
||||
* Config to disable ESLint rules covered by Prettier
|
||||
*
|
||||
* https://github.com/prettier/eslint-config-prettier
|
||||
*/
|
||||
'eslint-config-prettier',
|
||||
],
|
||||
|
||||
plugins: [
|
||||
/**
|
||||
* Plugin with lint rules for import/export syntax
|
||||
* https://github.com/import-js/eslint-plugin-import
|
||||
*/
|
||||
'eslint-plugin-import',
|
||||
|
||||
/**
|
||||
* @typescript-eslint/eslint-plugin is required by eslint-config-airbnb-typescript
|
||||
* See step 2: https://github.com/iamturns/eslint-config-airbnb-typescript#2-install-eslint-plugins
|
||||
*/
|
||||
'@typescript-eslint',
|
||||
|
||||
/**
|
||||
* Plugin to report formatting violations as lint violations
|
||||
* https://github.com/prettier/eslint-plugin-prettier
|
||||
*/
|
||||
'eslint-plugin-prettier',
|
||||
],
|
||||
|
||||
rules: {
|
||||
// ******************************************************************
|
||||
// required by prettier plugin
|
||||
// ******************************************************************
|
||||
|
||||
// The following rule enables eslint-plugin-prettier
|
||||
// See: https://github.com/prettier/eslint-plugin-prettier#recommended-configuration
|
||||
|
||||
'prettier/prettier': 'error',
|
||||
|
||||
// The following two rules must be disabled when using eslint-plugin-prettier:
|
||||
// See: https://github.com/prettier/eslint-plugin-prettier#arrow-body-style-and-prefer-arrow-callback-issue
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/arrow-body-style
|
||||
*/
|
||||
'arrow-body-style': 'off',
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/prefer-arrow-callback
|
||||
*/
|
||||
'prefer-arrow-callback': 'off',
|
||||
|
||||
// ******************************************************************
|
||||
// additions to base ruleset
|
||||
// ******************************************************************
|
||||
|
||||
// ----------------------------------
|
||||
// ESLint
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/id-denylist
|
||||
*/
|
||||
'id-denylist': [
|
||||
'error',
|
||||
'err',
|
||||
'cb',
|
||||
'callback',
|
||||
'any',
|
||||
'Number',
|
||||
'number',
|
||||
'String',
|
||||
'string',
|
||||
'Boolean',
|
||||
'boolean',
|
||||
'Undefined',
|
||||
'undefined',
|
||||
],
|
||||
|
||||
// ----------------------------------
|
||||
// @typescript-eslint
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md
|
||||
*/
|
||||
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-comment.md
|
||||
*/
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md
|
||||
*/
|
||||
'@typescript-eslint/ban-types': [
|
||||
'error',
|
||||
{
|
||||
types: {
|
||||
Object: {
|
||||
message: 'Use object instead',
|
||||
fixWith: 'object',
|
||||
},
|
||||
String: {
|
||||
message: 'Use string instead',
|
||||
fixWith: 'string',
|
||||
},
|
||||
Boolean: {
|
||||
message: 'Use boolean instead',
|
||||
fixWith: 'boolean',
|
||||
},
|
||||
Number: {
|
||||
message: 'Use number instead',
|
||||
fixWith: 'number',
|
||||
},
|
||||
Symbol: {
|
||||
message: 'Use symbol instead',
|
||||
fixWith: 'symbol',
|
||||
},
|
||||
Function: {
|
||||
message: [
|
||||
'The `Function` type accepts any function-like value.',
|
||||
'It provides no type safety when calling the function, which can be a common source of bugs.',
|
||||
'It also accepts things like class declarations, which will throw at runtime as they will not be called with `new`.',
|
||||
'If you are expecting the function to accept certain arguments, you should explicitly define the function shape.',
|
||||
].join('\n'),
|
||||
},
|
||||
},
|
||||
extendDefaults: false,
|
||||
},
|
||||
],
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md
|
||||
*/
|
||||
'@typescript-eslint/consistent-type-assertions': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md
|
||||
*/
|
||||
'@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md
|
||||
*/
|
||||
'@typescript-eslint/member-delimiter-style': [
|
||||
'error',
|
||||
{
|
||||
multiline: {
|
||||
delimiter: 'semi',
|
||||
requireLast: true,
|
||||
},
|
||||
singleline: {
|
||||
delimiter: 'semi',
|
||||
requireLast: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md
|
||||
*/
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
selector: 'default',
|
||||
format: ['camelCase'],
|
||||
},
|
||||
{
|
||||
selector: 'variable',
|
||||
format: ['camelCase', 'snake_case', 'UPPER_CASE'],
|
||||
leadingUnderscore: 'allowSingleOrDouble',
|
||||
trailingUnderscore: 'allowSingleOrDouble',
|
||||
},
|
||||
{
|
||||
selector: 'property',
|
||||
format: ['camelCase', 'snake_case'],
|
||||
leadingUnderscore: 'allowSingleOrDouble',
|
||||
trailingUnderscore: 'allowSingleOrDouble',
|
||||
},
|
||||
{
|
||||
selector: 'typeLike',
|
||||
format: ['PascalCase'],
|
||||
},
|
||||
{
|
||||
selector: ['method', 'function'],
|
||||
format: ['camelCase'],
|
||||
leadingUnderscore: 'allowSingleOrDouble',
|
||||
},
|
||||
],
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-duplicate-imports.md
|
||||
*/
|
||||
'@typescript-eslint/no-duplicate-imports': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-void-type.md
|
||||
*/
|
||||
'@typescript-eslint/no-invalid-void-type': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-promises.md
|
||||
*/
|
||||
'@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }],
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/1.0.0/rules/no-throw-literal
|
||||
*/
|
||||
'@typescript-eslint/no-throw-literal': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md
|
||||
*/
|
||||
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md
|
||||
*/
|
||||
'@typescript-eslint/no-unnecessary-qualifier': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md
|
||||
*/
|
||||
'@typescript-eslint/no-unused-expressions': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md
|
||||
*/
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '_' }],
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md
|
||||
*/
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-optional-chain.md
|
||||
*/
|
||||
'@typescript-eslint/prefer-optional-chain': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md
|
||||
*/
|
||||
'@typescript-eslint/promise-function-async': 'error',
|
||||
|
||||
// ----------------------------------
|
||||
// eslint-plugin-import
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/no-default-export.md
|
||||
*/
|
||||
'import/no-default-export': 'error',
|
||||
|
||||
/**
|
||||
* https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/order.md
|
||||
*/
|
||||
'import/order': 'error',
|
||||
|
||||
// ******************************************************************
|
||||
// overrides to base ruleset
|
||||
// ******************************************************************
|
||||
|
||||
// ----------------------------------
|
||||
// ESLint
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/class-methods-use-this
|
||||
*/
|
||||
'class-methods-use-this': 'off',
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/eqeqeq
|
||||
*/
|
||||
eqeqeq: 'error',
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/no-plusplus
|
||||
*/
|
||||
'no-plusplus': 'off',
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/object-shorthand
|
||||
*/
|
||||
'object-shorthand': 'error',
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/prefer-const
|
||||
*/
|
||||
'prefer-const': 'error',
|
||||
|
||||
/**
|
||||
* https://eslint.org/docs/rules/prefer-spread
|
||||
*/
|
||||
'prefer-spread': 'error',
|
||||
|
||||
// ----------------------------------
|
||||
// import
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/prefer-default-export.md
|
||||
*/
|
||||
'import/prefer-default-export': 'off',
|
||||
},
|
||||
};
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -23,6 +23,6 @@ jobs:
|
|||
npm run bootstrap
|
||||
npm run build --if-present
|
||||
npm test
|
||||
npm run tslint
|
||||
npm run lint
|
||||
env:
|
||||
CI: true
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -10,8 +10,8 @@ yarn.lock
|
|||
google-generated-credentials.json
|
||||
_START_PACKAGE
|
||||
.env
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.prettierrc.js
|
||||
vetur.config.js
|
||||
nodelinter.config.json
|
||||
|
|
51
.prettierrc.js
Normal file
51
.prettierrc.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
module.exports = {
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#semicolons
|
||||
*/
|
||||
semi: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#trailing-commas
|
||||
*/
|
||||
trailingComma: 'all',
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#bracket-spacing
|
||||
*/
|
||||
bracketSpacing: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#tabs
|
||||
*/
|
||||
useTabs: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#tab-width
|
||||
*/
|
||||
tabWidth: 2,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#arrow-function-parentheses
|
||||
*/
|
||||
arrowParens: 'always',
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#quotes
|
||||
*/
|
||||
singleQuote: true,
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#quote-props
|
||||
*/
|
||||
quoteProps: 'as-needed',
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#end-of-line
|
||||
*/
|
||||
endOfLine: 'lf',
|
||||
|
||||
/**
|
||||
* https://prettier.io/docs/en/options.html#print-width
|
||||
*/
|
||||
printWidth: 100,
|
||||
};
|
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode",
|
||||
"octref.vetur"
|
||||
]
|
||||
}
|
|
@ -7,12 +7,14 @@
|
|||
"build": "lerna exec npm run build",
|
||||
"dev": "lerna exec npm run dev --parallel",
|
||||
"clean:dist": "lerna exec -- rimraf ./dist",
|
||||
"format": "lerna exec npm run format",
|
||||
"lint": "lerna exec npm run lint",
|
||||
"lintfix": "lerna exec npm run lintfix",
|
||||
"optimize-svg": "find ./packages -name '*.svg' ! -name 'pipedrive.svg' -print0 | xargs -0 -P16 -L20 npx svgo",
|
||||
"start": "run-script-os",
|
||||
"start:default": "cd packages/cli/bin && ./n8n",
|
||||
"start:windows": "cd packages/cli/bin && n8n",
|
||||
"test": "lerna run test",
|
||||
"tslint": "lerna exec npm run tslint",
|
||||
"watch": "lerna run --parallel watch",
|
||||
"webhook": "./packages/cli/bin/n8n webhook",
|
||||
"worker": "./packages/cli/bin/n8n worker"
|
||||
|
|
14
packages/cli/commands/Interfaces.d.ts
vendored
14
packages/cli/commands/Interfaces.d.ts
vendored
|
@ -1,14 +1,14 @@
|
|||
interface IResult {
|
||||
totalWorkflows: number;
|
||||
summary: {
|
||||
failedExecutions: number,
|
||||
successfulExecutions: number,
|
||||
warningExecutions: number,
|
||||
errors: IExecutionError[],
|
||||
warnings: IExecutionError[],
|
||||
failedExecutions: number;
|
||||
successfulExecutions: number;
|
||||
warningExecutions: number;
|
||||
errors: IExecutionError[];
|
||||
warnings: IExecutionError[];
|
||||
};
|
||||
coveredNodes: {
|
||||
[nodeType: string]: number
|
||||
[nodeType: string]: number;
|
||||
};
|
||||
executions: IExecutionResult[];
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ interface IExecutionResult {
|
|||
error?: string;
|
||||
changes?: string;
|
||||
coveredNodes: {
|
||||
[nodeType: string]: number
|
||||
[nodeType: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable no-console */
|
||||
import { promises as fs } from 'fs';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import {
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
import {
|
||||
INode,
|
||||
} from 'n8n-workflow';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { INode, LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
|
@ -17,26 +15,18 @@ import {
|
|||
IWorkflowExecutionDataProcess,
|
||||
LoadNodesAndCredentials,
|
||||
NodeTypes,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
} from '../src';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { getLogger } from '../src/Logger';
|
||||
|
||||
export class Execute extends Command {
|
||||
static description = '\nExecutes a given workflow';
|
||||
|
||||
static examples = [
|
||||
`$ n8n execute --id=5`,
|
||||
`$ n8n execute --file=workflow.json`,
|
||||
];
|
||||
static examples = [`$ n8n execute --id=5`, `$ n8n execute --file=workflow.json`];
|
||||
|
||||
static flags = {
|
||||
help: flags.help({ char: 'h' }),
|
||||
|
@ -51,11 +41,12 @@ export class Execute extends Command {
|
|||
}),
|
||||
};
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(Execute);
|
||||
|
||||
// Start directly with the init of the database to improve startup time
|
||||
|
@ -76,12 +67,14 @@ export class Execute extends Command {
|
|||
}
|
||||
|
||||
let workflowId: string | undefined;
|
||||
let workflowData: IWorkflowBase | undefined = undefined;
|
||||
let workflowData: IWorkflowBase | undefined;
|
||||
if (flags.file) {
|
||||
// Path to workflow is given
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
workflowData = JSON.parse(await fs.readFile(flags.file, 'utf8'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (error.code === 'ENOENT') {
|
||||
console.info(`The file "${flags.file}" could not be found.`);
|
||||
return;
|
||||
|
@ -92,10 +85,15 @@ export class Execute extends Command {
|
|||
|
||||
// Do a basic check if the data in the file looks right
|
||||
// TODO: Later check with the help of TypeScript data if it is valid or not
|
||||
if (workflowData === undefined || workflowData.nodes === undefined || workflowData.connections === undefined) {
|
||||
if (
|
||||
workflowData === undefined ||
|
||||
workflowData.nodes === undefined ||
|
||||
workflowData.connections === undefined
|
||||
) {
|
||||
console.info(`The file "${flags.file}" does not contain valid workflow data.`);
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
workflowId = workflowData.id!.toString();
|
||||
}
|
||||
|
||||
|
@ -105,7 +103,8 @@ export class Execute extends Command {
|
|||
if (flags.id) {
|
||||
// Id of workflow is given
|
||||
workflowId = flags.id;
|
||||
workflowData = await Db.collections!.Workflow!.findOne(workflowId);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
workflowData = await Db.collections.Workflow!.findOne(workflowId);
|
||||
if (workflowData === undefined) {
|
||||
console.info(`The workflow with the id "${workflowId}" does not exist.`);
|
||||
process.exit(1);
|
||||
|
@ -139,7 +138,8 @@ export class Execute extends Command {
|
|||
// Check if the workflow contains the required "Start" node
|
||||
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
||||
let startNode: INode | undefined = undefined;
|
||||
let startNode: INode | undefined;
|
||||
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-non-null-assertion
|
||||
for (const node of workflowData!.nodes) {
|
||||
if (requiredNodeTypes.includes(node.type)) {
|
||||
startNode = node;
|
||||
|
@ -151,6 +151,7 @@ export class Execute extends Command {
|
|||
// If the workflow does not contain a start-node we can not know what
|
||||
// should be executed and with which data to start.
|
||||
console.info(`The workflow does not contain a "Start" node. So it can not be executed.`);
|
||||
// eslint-disable-next-line consistent-return
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -158,6 +159,7 @@ export class Execute extends Command {
|
|||
const runData: IWorkflowExecutionDataProcess = {
|
||||
executionMode: 'cli',
|
||||
startNodes: [startNode.name],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
workflowData: workflowData!,
|
||||
};
|
||||
|
||||
|
@ -178,6 +180,7 @@ export class Execute extends Command {
|
|||
logger.info(JSON.stringify(data, null, 2));
|
||||
|
||||
const { error } = data.data.resultData;
|
||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||
throw {
|
||||
...error,
|
||||
stack: error.stack,
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable array-callback-return */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable no-console */
|
||||
import * as fs from 'fs';
|
||||
import {
|
||||
Command,
|
||||
flags,
|
||||
} from '@oclif/command';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
|
||||
import {
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
ITaskData,
|
||||
} from 'n8n-workflow';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { INode, INodeExecutionData, ITaskData, LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import { sep } from 'path';
|
||||
|
||||
import { diff } from 'json-diff';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { pick } from 'lodash';
|
||||
import { getLogger } from '../src/Logger';
|
||||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
|
@ -20,35 +28,17 @@ import {
|
|||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
IExecutionsCurrentSummary,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
LoadNodesAndCredentials,
|
||||
NodeTypes,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowRunner,
|
||||
} from '../src';
|
||||
|
||||
import {
|
||||
sep,
|
||||
} from 'path';
|
||||
|
||||
import {
|
||||
diff,
|
||||
} from 'json-diff';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
pick,
|
||||
} from 'lodash';
|
||||
|
||||
export class ExecuteBatch extends Command {
|
||||
static description = '\nExecutes multiple workflows once';
|
||||
|
||||
|
@ -87,19 +77,24 @@ export class ExecuteBatch extends Command {
|
|||
}),
|
||||
concurrency: flags.integer({
|
||||
default: 1,
|
||||
description: 'How many workflows can run in parallel. Defaults to 1 which means no concurrency.',
|
||||
description:
|
||||
'How many workflows can run in parallel. Defaults to 1 which means no concurrency.',
|
||||
}),
|
||||
output: flags.string({
|
||||
description: 'Enable execution saving, You must inform an existing folder to save execution via this param',
|
||||
description:
|
||||
'Enable execution saving, You must inform an existing folder to save execution via this param',
|
||||
}),
|
||||
snapshot: flags.string({
|
||||
description: 'Enables snapshot saving. You must inform an existing folder to save snapshots via this param.',
|
||||
description:
|
||||
'Enables snapshot saving. You must inform an existing folder to save snapshots via this param.',
|
||||
}),
|
||||
compare: flags.string({
|
||||
description: 'Compares current execution with an existing snapshot. You must inform an existing folder where the snapshots are saved.',
|
||||
description:
|
||||
'Compares current execution with an existing snapshot. You must inform an existing folder where the snapshots are saved.',
|
||||
}),
|
||||
shallow: flags.boolean({
|
||||
description: 'Compares only if attributes output from node are the same, with no regards to neste JSON objects.',
|
||||
description:
|
||||
'Compares only if attributes output from node are the same, with no regards to neste JSON objects.',
|
||||
}),
|
||||
skipList: flags.string({
|
||||
description: 'File containing a comma separated list of workflow IDs to skip.',
|
||||
|
@ -117,15 +112,16 @@ export class ExecuteBatch extends Command {
|
|||
* Gracefully handles exit.
|
||||
* @param {boolean} skipExit Whether to skip exit or number according to received signal
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static async stopProcess(skipExit: boolean | number = false) {
|
||||
|
||||
if (ExecuteBatch.cancelled === true) {
|
||||
if (ExecuteBatch.cancelled) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
ExecuteBatch.cancelled = true;
|
||||
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||
const stopPromises = activeExecutionsInstance.getActiveExecutions().map(async execution => {
|
||||
const stopPromises = activeExecutionsInstance.getActiveExecutions().map(async (execution) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
activeExecutionsInstance.stopExecution(execution.id);
|
||||
});
|
||||
|
||||
|
@ -135,16 +131,17 @@ export class ExecuteBatch extends Command {
|
|||
process.exit(0);
|
||||
}, 30000);
|
||||
|
||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
|
||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||
|
||||
let count = 0;
|
||||
while (executingWorkflows.length !== 0) {
|
||||
if (count++ % 4 === 0) {
|
||||
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
||||
executingWorkflows.map(execution => {
|
||||
executingWorkflows.map((execution) => {
|
||||
console.log(` - Execution ID ${execution.id}, workflow ID: ${execution.workflowId}`);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
|
@ -157,12 +154,13 @@ export class ExecuteBatch extends Command {
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
formatJsonOutput(data: object) {
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
shouldBeConsideredAsWarning(errorMessage: string) {
|
||||
|
||||
const warningStrings = [
|
||||
'refresh token is invalid',
|
||||
'unable to connect to',
|
||||
|
@ -174,6 +172,7 @@ export class ExecuteBatch extends Command {
|
|||
'request timed out',
|
||||
];
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
errorMessage = errorMessage.toLowerCase();
|
||||
|
||||
for (let i = 0; i < warningStrings.length; i++) {
|
||||
|
@ -185,18 +184,18 @@ export class ExecuteBatch extends Command {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
|
||||
process.on('SIGTERM', ExecuteBatch.stopProcess);
|
||||
process.on('SIGINT', ExecuteBatch.stopProcess);
|
||||
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(ExecuteBatch);
|
||||
|
||||
ExecuteBatch.debug = flags.debug === true;
|
||||
ExecuteBatch.debug = flags.debug;
|
||||
ExecuteBatch.concurrency = flags.concurrency || 1;
|
||||
|
||||
const ids: number[] = [];
|
||||
|
@ -241,7 +240,7 @@ export class ExecuteBatch extends Command {
|
|||
if (flags.ids !== undefined) {
|
||||
const paramIds = flags.ids.split(',');
|
||||
const re = /\d+/;
|
||||
const matchedIds = paramIds.filter(id => id.match(re)).map(id => parseInt(id.trim(), 10));
|
||||
const matchedIds = paramIds.filter((id) => re.exec(id)).map((id) => parseInt(id.trim(), 10));
|
||||
|
||||
if (matchedIds.length === 0) {
|
||||
console.log(`The parameter --ids must be a list of numeric IDs separated by a comma.`);
|
||||
|
@ -254,18 +253,17 @@ export class ExecuteBatch extends Command {
|
|||
if (flags.skipList !== undefined) {
|
||||
if (fs.existsSync(flags.skipList)) {
|
||||
const contents = fs.readFileSync(flags.skipList, { encoding: 'utf-8' });
|
||||
skipIds.push(...contents.split(',').map(id => parseInt(id.trim(), 10)));
|
||||
skipIds.push(...contents.split(',').map((id) => parseInt(id.trim(), 10)));
|
||||
} else {
|
||||
console.log('Skip list file not found. Exiting.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.shallow === true) {
|
||||
if (flags.shallow) {
|
||||
ExecuteBatch.shallow = true;
|
||||
}
|
||||
|
||||
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init();
|
||||
|
||||
|
@ -281,7 +279,7 @@ export class ExecuteBatch extends Command {
|
|||
|
||||
let allWorkflows;
|
||||
|
||||
const query = Db.collections!.Workflow!.createQueryBuilder('workflows');
|
||||
const query = Db.collections.Workflow!.createQueryBuilder('workflows');
|
||||
|
||||
if (ids.length > 0) {
|
||||
query.andWhere(`workflows.id in (:...ids)`, { ids });
|
||||
|
@ -291,9 +289,10 @@ export class ExecuteBatch extends Command {
|
|||
query.andWhere(`workflows.id not in (:...skipIds)`, { skipIds });
|
||||
}
|
||||
|
||||
allWorkflows = await query.getMany() as IWorkflowDb[];
|
||||
// eslint-disable-next-line prefer-const
|
||||
allWorkflows = (await query.getMany()) as IWorkflowDb[];
|
||||
|
||||
if (ExecuteBatch.debug === true) {
|
||||
if (ExecuteBatch.debug) {
|
||||
process.stdout.write(`Found ${allWorkflows.length} workflows to execute.\n`);
|
||||
}
|
||||
|
||||
|
@ -318,12 +317,19 @@ export class ExecuteBatch extends Command {
|
|||
|
||||
let { retries } = flags;
|
||||
|
||||
while (retries > 0 && (results.summary.warningExecutions + results.summary.failedExecutions > 0) && ExecuteBatch.cancelled === false) {
|
||||
const failedWorkflowIds = results.summary.errors.map(execution => execution.workflowId);
|
||||
failedWorkflowIds.push(...results.summary.warnings.map(execution => execution.workflowId));
|
||||
while (
|
||||
retries > 0 &&
|
||||
results.summary.warningExecutions + results.summary.failedExecutions > 0 &&
|
||||
!ExecuteBatch.cancelled
|
||||
) {
|
||||
const failedWorkflowIds = results.summary.errors.map((execution) => execution.workflowId);
|
||||
failedWorkflowIds.push(...results.summary.warnings.map((execution) => execution.workflowId));
|
||||
|
||||
const newWorkflowList = allWorkflows.filter(workflow => failedWorkflowIds.includes(workflow.id));
|
||||
const newWorkflowList = allWorkflows.filter((workflow) =>
|
||||
failedWorkflowIds.includes(workflow.id),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const retryResults = await this.runTests(newWorkflowList);
|
||||
|
||||
this.mergeResults(results, retryResults);
|
||||
|
@ -343,13 +349,18 @@ export class ExecuteBatch extends Command {
|
|||
console.log(`\t${nodeName}: ${nodeCount}`);
|
||||
});
|
||||
console.log('\nCheck the JSON file for more details.');
|
||||
} else {
|
||||
if (flags.shortOutput === true) {
|
||||
console.log(this.formatJsonOutput({ ...results, executions: results.executions.filter(execution => execution.executionStatus !== 'success') }));
|
||||
} else if (flags.shortOutput) {
|
||||
console.log(
|
||||
this.formatJsonOutput({
|
||||
...results,
|
||||
executions: results.executions.filter(
|
||||
(execution) => execution.executionStatus !== 'success',
|
||||
),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
console.log(this.formatJsonOutput(results));
|
||||
}
|
||||
}
|
||||
|
||||
await ExecuteBatch.stopProcess(true);
|
||||
|
||||
|
@ -357,23 +368,26 @@ export class ExecuteBatch extends Command {
|
|||
this.exit(1);
|
||||
}
|
||||
this.exit(0);
|
||||
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
mergeResults(results: IResult, retryResults: IResult) {
|
||||
|
||||
if (retryResults.summary.successfulExecutions === 0) {
|
||||
// Nothing to replace.
|
||||
return;
|
||||
}
|
||||
|
||||
// Find successful executions and replace them on previous result.
|
||||
retryResults.executions.forEach(newExecution => {
|
||||
retryResults.executions.forEach((newExecution) => {
|
||||
if (newExecution.executionStatus === 'success') {
|
||||
// Remove previous execution from list.
|
||||
results.executions = results.executions.filter(previousExecutions => previousExecutions.workflowId !== newExecution.workflowId);
|
||||
results.executions = results.executions.filter(
|
||||
(previousExecutions) => previousExecutions.workflowId !== newExecution.workflowId,
|
||||
);
|
||||
|
||||
const errorIndex = results.summary.errors.findIndex(summaryInformation => summaryInformation.workflowId === newExecution.workflowId);
|
||||
const errorIndex = results.summary.errors.findIndex(
|
||||
(summaryInformation) => summaryInformation.workflowId === newExecution.workflowId,
|
||||
);
|
||||
if (errorIndex !== -1) {
|
||||
// This workflow errored previously. Decrement error count.
|
||||
results.summary.failedExecutions--;
|
||||
|
@ -381,7 +395,9 @@ export class ExecuteBatch extends Command {
|
|||
results.summary.errors.splice(errorIndex, 1);
|
||||
}
|
||||
|
||||
const warningIndex = results.summary.warnings.findIndex(summaryInformation => summaryInformation.workflowId === newExecution.workflowId);
|
||||
const warningIndex = results.summary.warnings.findIndex(
|
||||
(summaryInformation) => summaryInformation.workflowId === newExecution.workflowId,
|
||||
);
|
||||
if (warningIndex !== -1) {
|
||||
// This workflow errored previously. Decrement error count.
|
||||
results.summary.warningExecutions--;
|
||||
|
@ -420,7 +436,7 @@ export class ExecuteBatch extends Command {
|
|||
let workflow: IWorkflowDb | undefined;
|
||||
while (allWorkflows.length > 0) {
|
||||
workflow = allWorkflows.shift();
|
||||
if (ExecuteBatch.cancelled === true) {
|
||||
if (ExecuteBatch.cancelled) {
|
||||
process.stdout.write(`Thread ${i + 1} resolving and quitting.`);
|
||||
resolve(true);
|
||||
break;
|
||||
|
@ -440,6 +456,7 @@ export class ExecuteBatch extends Command {
|
|||
this.updateStatus();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
await this.startThread(workflow).then((executionResult) => {
|
||||
if (ExecuteBatch.debug) {
|
||||
ExecuteBatch.workflowExecutionsProgress[i].pop();
|
||||
|
@ -456,7 +473,7 @@ export class ExecuteBatch extends Command {
|
|||
result.summary.successfulExecutions++;
|
||||
const nodeNames = Object.keys(executionResult.coveredNodes);
|
||||
|
||||
nodeNames.map(nodeName => {
|
||||
nodeNames.map((nodeName) => {
|
||||
if (result.coveredNodes[nodeName] === undefined) {
|
||||
result.coveredNodes[nodeName] = 0;
|
||||
}
|
||||
|
@ -506,19 +523,18 @@ export class ExecuteBatch extends Command {
|
|||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
updateStatus() {
|
||||
|
||||
if (ExecuteBatch.cancelled === true) {
|
||||
if (ExecuteBatch.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.stdout.isTTY === true) {
|
||||
process.stdout.moveCursor(0, - (ExecuteBatch.concurrency));
|
||||
if (process.stdout.isTTY) {
|
||||
process.stdout.moveCursor(0, -ExecuteBatch.concurrency);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.clearLine(0);
|
||||
}
|
||||
|
||||
|
||||
ExecuteBatch.workflowExecutionsProgress.map((concurrentThread, index) => {
|
||||
let message = `${index + 1}: `;
|
||||
concurrentThread.map((executionItem, workflowIndex) => {
|
||||
|
@ -537,16 +553,19 @@ export class ExecuteBatch extends Command {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
message += (workflowIndex > 0 ? ', ' : '') + `${openColor}${executionItem.workflowId}${closeColor}`;
|
||||
message += `${workflowIndex > 0 ? ', ' : ''}${openColor}${
|
||||
executionItem.workflowId
|
||||
}${closeColor}`;
|
||||
});
|
||||
if (process.stdout.isTTY === true) {
|
||||
if (process.stdout.isTTY) {
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.clearLine(0);
|
||||
}
|
||||
process.stdout.write(message + '\n');
|
||||
process.stdout.write(`${message}\n`);
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
initializeLogs() {
|
||||
process.stdout.write('**********************************************\n');
|
||||
process.stdout.write(' n8n test workflows\n');
|
||||
|
@ -560,7 +579,7 @@ export class ExecuteBatch extends Command {
|
|||
}
|
||||
}
|
||||
|
||||
startThread(workflowData: IWorkflowDb): Promise<IExecutionResult> {
|
||||
async startThread(workflowData: IWorkflowDb): Promise<IExecutionResult> {
|
||||
// This will be the object returned by the promise.
|
||||
// It will be updated according to execution progress below.
|
||||
const executionResult: IExecutionResult = {
|
||||
|
@ -572,10 +591,9 @@ export class ExecuteBatch extends Command {
|
|||
coveredNodes: {},
|
||||
};
|
||||
|
||||
|
||||
|
||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
||||
let startNode: INode | undefined = undefined;
|
||||
let startNode: INode | undefined;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const node of workflowData.nodes) {
|
||||
if (requiredNodeTypes.includes(node.type)) {
|
||||
startNode = node;
|
||||
|
@ -593,10 +611,10 @@ export class ExecuteBatch extends Command {
|
|||
// properties from the JSON object (useful for optional properties that can
|
||||
// cause the comparison to detect changes when not true).
|
||||
const nodeEdgeCases = {} as INodeSpecialCases;
|
||||
workflowData.nodes.forEach(node => {
|
||||
workflowData.nodes.forEach((node) => {
|
||||
executionResult.coveredNodes[node.type] = (executionResult.coveredNodes[node.type] || 0) + 1;
|
||||
if (node.notes !== undefined && node.notes !== '') {
|
||||
node.notes.split('\n').forEach(note => {
|
||||
node.notes.split('\n').forEach((note) => {
|
||||
const parts = note.split('=');
|
||||
if (parts.length === 2) {
|
||||
if (nodeEdgeCases[node.name] === undefined) {
|
||||
|
@ -605,9 +623,13 @@ export class ExecuteBatch extends Command {
|
|||
if (parts[0] === 'CAP_RESULTS_LENGTH') {
|
||||
nodeEdgeCases[node.name].capResults = parseInt(parts[1], 10);
|
||||
} else if (parts[0] === 'IGNORED_PROPERTIES') {
|
||||
nodeEdgeCases[node.name].ignoredProperties = parts[1].split(',').map(property => property.trim());
|
||||
nodeEdgeCases[node.name].ignoredProperties = parts[1]
|
||||
.split(',')
|
||||
.map((property) => property.trim());
|
||||
} else if (parts[0] === 'KEEP_ONLY_PROPERTIES') {
|
||||
nodeEdgeCases[node.name].keepOnlyProperties = parts[1].split(',').map(property => property.trim());
|
||||
nodeEdgeCases[node.name].keepOnlyProperties = parts[1]
|
||||
.split(',')
|
||||
.map((property) => property.trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -633,13 +655,11 @@ export class ExecuteBatch extends Command {
|
|||
resolve(executionResult);
|
||||
}, ExecuteBatch.executionTimeout);
|
||||
|
||||
|
||||
try {
|
||||
|
||||
const runData: IWorkflowExecutionDataProcess = {
|
||||
executionMode: 'cli',
|
||||
startNodes: [startNode!.name],
|
||||
workflowData: workflowData!,
|
||||
workflowData,
|
||||
};
|
||||
|
||||
const workflowRunner = new WorkflowRunner();
|
||||
|
@ -647,7 +667,7 @@ export class ExecuteBatch extends Command {
|
|||
|
||||
const activeExecutions = ActiveExecutions.getInstance();
|
||||
const data = await activeExecutions.getPostExecutePromise(executionId);
|
||||
if (gotCancel || ExecuteBatch.cancelled === true) {
|
||||
if (gotCancel || ExecuteBatch.cancelled) {
|
||||
clearTimeout(timeoutTimer);
|
||||
// The promise was settled already so we simply ignore.
|
||||
return;
|
||||
|
@ -657,14 +677,18 @@ export class ExecuteBatch extends Command {
|
|||
executionResult.error = 'Workflow did not return any data.';
|
||||
executionResult.executionStatus = 'error';
|
||||
} else {
|
||||
executionResult.executionTime = (Date.parse(data.stoppedAt as unknown as string) - Date.parse(data.startedAt as unknown as string)) / 1000;
|
||||
executionResult.finished = (data?.finished !== undefined) as boolean;
|
||||
executionResult.executionTime =
|
||||
(Date.parse(data.stoppedAt as unknown as string) -
|
||||
Date.parse(data.startedAt as unknown as string)) /
|
||||
1000;
|
||||
executionResult.finished = data?.finished !== undefined;
|
||||
|
||||
if (data.data.resultData.error) {
|
||||
executionResult.error =
|
||||
data.data.resultData.error.hasOwnProperty('description') ?
|
||||
// @ts-ignore
|
||||
data.data.resultData.error.description : data.data.resultData.error.message;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-prototype-builtins
|
||||
executionResult.error = data.data.resultData.error.hasOwnProperty('description')
|
||||
? // @ts-ignore
|
||||
data.data.resultData.error.description
|
||||
: data.data.resultData.error.message;
|
||||
if (data.data.resultData.lastNodeExecuted !== undefined) {
|
||||
executionResult.error += ` on node ${data.data.resultData.lastNodeExecuted}`;
|
||||
}
|
||||
|
@ -674,7 +698,7 @@ export class ExecuteBatch extends Command {
|
|||
executionResult.executionStatus = 'warning';
|
||||
}
|
||||
} else {
|
||||
if (ExecuteBatch.shallow === true) {
|
||||
if (ExecuteBatch.shallow) {
|
||||
// What this does is guarantee that top-level attributes
|
||||
// from the JSON are kept and the are the same type.
|
||||
|
||||
|
@ -688,34 +712,48 @@ export class ExecuteBatch extends Command {
|
|||
if (taskData.data === undefined) {
|
||||
return;
|
||||
}
|
||||
Object.keys(taskData.data).map(connectionName => {
|
||||
const connection = taskData.data![connectionName] as Array<INodeExecutionData[] | null>;
|
||||
connection.map(executionDataArray => {
|
||||
Object.keys(taskData.data).map((connectionName) => {
|
||||
const connection = taskData.data![connectionName];
|
||||
connection.map((executionDataArray) => {
|
||||
if (executionDataArray === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].capResults !== undefined) {
|
||||
if (
|
||||
nodeEdgeCases[nodeName] !== undefined &&
|
||||
nodeEdgeCases[nodeName].capResults !== undefined
|
||||
) {
|
||||
executionDataArray.splice(nodeEdgeCases[nodeName].capResults!);
|
||||
}
|
||||
|
||||
executionDataArray.map(executionData => {
|
||||
executionDataArray.map((executionData) => {
|
||||
if (executionData.json === undefined) {
|
||||
return;
|
||||
}
|
||||
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].ignoredProperties !== undefined) {
|
||||
nodeEdgeCases[nodeName].ignoredProperties!.forEach(ignoredProperty => delete executionData.json[ignoredProperty]);
|
||||
if (
|
||||
nodeEdgeCases[nodeName] !== undefined &&
|
||||
nodeEdgeCases[nodeName].ignoredProperties !== undefined
|
||||
) {
|
||||
nodeEdgeCases[nodeName].ignoredProperties!.forEach(
|
||||
(ignoredProperty) => delete executionData.json[ignoredProperty],
|
||||
);
|
||||
}
|
||||
|
||||
let keepOnlyFields = [] as string[];
|
||||
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].keepOnlyProperties !== undefined) {
|
||||
if (
|
||||
nodeEdgeCases[nodeName] !== undefined &&
|
||||
nodeEdgeCases[nodeName].keepOnlyProperties !== undefined
|
||||
) {
|
||||
keepOnlyFields = nodeEdgeCases[nodeName].keepOnlyProperties!;
|
||||
}
|
||||
executionData.json = keepOnlyFields.length > 0 ? pick(executionData.json, keepOnlyFields) : executionData.json;
|
||||
executionData.json =
|
||||
keepOnlyFields.length > 0
|
||||
? pick(executionData.json, keepOnlyFields)
|
||||
: executionData.json;
|
||||
const jsonProperties = executionData.json;
|
||||
|
||||
const nodeOutputAttributes = Object.keys(jsonProperties);
|
||||
nodeOutputAttributes.map(attributeName => {
|
||||
nodeOutputAttributes.map((attributeName) => {
|
||||
if (Array.isArray(jsonProperties[attributeName])) {
|
||||
jsonProperties[attributeName] = ['json array'];
|
||||
} else if (typeof jsonProperties[attributeName] === 'object') {
|
||||
|
@ -724,7 +762,6 @@ export class ExecuteBatch extends Command {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -732,14 +769,14 @@ export class ExecuteBatch extends Command {
|
|||
// If not using shallow comparison then we only treat nodeEdgeCases.
|
||||
const specialCases = Object.keys(nodeEdgeCases);
|
||||
|
||||
specialCases.forEach(nodeName => {
|
||||
specialCases.forEach((nodeName) => {
|
||||
data.data.resultData.runData[nodeName].map((taskData: ITaskData) => {
|
||||
if (taskData.data === undefined) {
|
||||
return;
|
||||
}
|
||||
Object.keys(taskData.data).map(connectionName => {
|
||||
const connection = taskData.data![connectionName] as Array<INodeExecutionData[] | null>;
|
||||
connection.map(executionDataArray => {
|
||||
Object.keys(taskData.data).map((connectionName) => {
|
||||
const connection = taskData.data![connectionName];
|
||||
connection.map((executionDataArray) => {
|
||||
if (executionDataArray === null) {
|
||||
return;
|
||||
}
|
||||
|
@ -749,15 +786,16 @@ export class ExecuteBatch extends Command {
|
|||
}
|
||||
|
||||
if (nodeEdgeCases[nodeName].ignoredProperties !== undefined) {
|
||||
executionDataArray.map(executionData => {
|
||||
executionDataArray.map((executionData) => {
|
||||
if (executionData.json === undefined) {
|
||||
return;
|
||||
}
|
||||
nodeEdgeCases[nodeName].ignoredProperties!.forEach(ignoredProperty => delete executionData.json[ignoredProperty]);
|
||||
nodeEdgeCases[nodeName].ignoredProperties!.forEach(
|
||||
(ignoredProperty) => delete executionData.json[ignoredProperty],
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -767,9 +805,12 @@ export class ExecuteBatch extends Command {
|
|||
if (ExecuteBatch.compare === undefined) {
|
||||
executionResult.executionStatus = 'success';
|
||||
} else {
|
||||
const fileName = (ExecuteBatch.compare.endsWith(sep) ? ExecuteBatch.compare : ExecuteBatch.compare + sep) + `${workflowData.id}-snapshot.json`;
|
||||
if (fs.existsSync(fileName) === true) {
|
||||
|
||||
const fileName = `${
|
||||
ExecuteBatch.compare.endsWith(sep)
|
||||
? ExecuteBatch.compare
|
||||
: ExecuteBatch.compare + sep
|
||||
}${workflowData.id}-snapshot.json`;
|
||||
if (fs.existsSync(fileName)) {
|
||||
const contents = fs.readFileSync(fileName, { encoding: 'utf-8' });
|
||||
|
||||
const changes = diff(JSON.parse(contents), data, { keysOnly: true });
|
||||
|
@ -790,7 +831,11 @@ export class ExecuteBatch extends Command {
|
|||
// Save snapshots only after comparing - this is to make sure we're updating
|
||||
// After comparing to existing verion.
|
||||
if (ExecuteBatch.snapshot !== undefined) {
|
||||
const fileName = (ExecuteBatch.snapshot.endsWith(sep) ? ExecuteBatch.snapshot : ExecuteBatch.snapshot + sep) + `${workflowData.id}-snapshot.json`;
|
||||
const fileName = `${
|
||||
ExecuteBatch.snapshot.endsWith(sep)
|
||||
? ExecuteBatch.snapshot
|
||||
: ExecuteBatch.snapshot + sep
|
||||
}${workflowData.id}-snapshot.json`;
|
||||
fs.writeFileSync(fileName, serializedData);
|
||||
}
|
||||
}
|
||||
|
@ -803,5 +848,4 @@ export class ExecuteBatch extends Command {
|
|||
resolve(executionResult);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,32 +1,16 @@
|
|||
import {
|
||||
Command,
|
||||
flags,
|
||||
} from '@oclif/command';
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable no-console */
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
Credentials,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
Db,
|
||||
ICredentialsDecryptedDb,
|
||||
} from '../../src';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getLogger } from '../../src/Logger';
|
||||
import { Db, ICredentialsDecryptedDb } from '../../src';
|
||||
|
||||
export class ExportCredentialsCommand extends Command {
|
||||
static description = 'Export credentials';
|
||||
|
@ -45,7 +29,8 @@ export class ExportCredentialsCommand extends Command {
|
|||
description: 'Export all credentials',
|
||||
}),
|
||||
backup: flags.boolean({
|
||||
description: 'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
|
||||
description:
|
||||
'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
|
||||
}),
|
||||
id: flags.string({
|
||||
description: 'The ID of the credential to export',
|
||||
|
@ -58,17 +43,21 @@ export class ExportCredentialsCommand extends Command {
|
|||
description: 'Format the output in an easier to read fashion',
|
||||
}),
|
||||
separate: flags.boolean({
|
||||
description: 'Exports one file per credential (useful for versioning). Must inform a directory via --output.',
|
||||
description:
|
||||
'Exports one file per credential (useful for versioning). Must inform a directory via --output.',
|
||||
}),
|
||||
decrypted: flags.boolean({
|
||||
description: 'Exports data decrypted / in plain text. ALL SENSITIVE INFORMATION WILL BE VISIBLE IN THE FILES. Use to migrate from a installation to another that have a different secret key (in the config file).',
|
||||
description:
|
||||
'Exports data decrypted / in plain text. ALL SENSITIVE INFORMATION WILL BE VISIBLE IN THE FILES. Use to migrate from a installation to another that have a different secret key (in the config file).',
|
||||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(ExportCredentialsCommand);
|
||||
|
||||
if (flags.backup) {
|
||||
|
@ -103,7 +92,9 @@ export class ExportCredentialsCommand extends Command {
|
|||
fs.mkdirSync(flags.output, { recursive: true });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.');
|
||||
console.error(
|
||||
'Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.',
|
||||
);
|
||||
logger.error('\nFILESYSTEM ERROR');
|
||||
logger.info('====================================');
|
||||
logger.error(e.message);
|
||||
|
@ -127,6 +118,7 @@ export class ExportCredentialsCommand extends Command {
|
|||
findQuery.id = flags.id;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const credentials = await Db.collections.Credentials!.find(findQuery);
|
||||
|
||||
if (flags.decrypted) {
|
||||
|
@ -148,17 +140,22 @@ export class ExportCredentialsCommand extends Command {
|
|||
}
|
||||
|
||||
if (flags.separate) {
|
||||
let fileContents: string, i: number;
|
||||
let fileContents: string;
|
||||
let i: number;
|
||||
for (i = 0; i < credentials.length; i++) {
|
||||
fileContents = JSON.stringify(credentials[i], null, flags.pretty ? 2 : undefined);
|
||||
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + credentials[i].id + '.json';
|
||||
const filename = `${
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) +
|
||||
credentials[i].id
|
||||
}.json`;
|
||||
fs.writeFileSync(filename, fileContents);
|
||||
}
|
||||
console.info(`Successfully exported ${i} credentials.`);
|
||||
} else {
|
||||
const fileContents = JSON.stringify(credentials, null, flags.pretty ? 2 : undefined);
|
||||
if (flags.output) {
|
||||
fs.writeFileSync(flags.output!, fileContents);
|
||||
fs.writeFileSync(flags.output, fileContents);
|
||||
console.info(`Successfully exported ${credentials.length} credentials.`);
|
||||
} else {
|
||||
console.info(fileContents);
|
||||
|
|
|
@ -1,26 +1,13 @@
|
|||
import {
|
||||
Command,
|
||||
flags,
|
||||
} from '@oclif/command';
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable no-console */
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
IDataObject
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
Db,
|
||||
} from '../../src';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getLogger } from '../../src/Logger';
|
||||
import { Db } from '../../src';
|
||||
|
||||
export class ExportWorkflowsCommand extends Command {
|
||||
static description = 'Export workflows';
|
||||
|
@ -38,7 +25,8 @@ export class ExportWorkflowsCommand extends Command {
|
|||
description: 'Export all workflows',
|
||||
}),
|
||||
backup: flags.boolean({
|
||||
description: 'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
|
||||
description:
|
||||
'Sets --all --pretty --separate for simple backups. Only --output has to be set additionally.',
|
||||
}),
|
||||
id: flags.string({
|
||||
description: 'The ID of the workflow to export',
|
||||
|
@ -51,14 +39,17 @@ export class ExportWorkflowsCommand extends Command {
|
|||
description: 'Format the output in an easier to read fashion',
|
||||
}),
|
||||
separate: flags.boolean({
|
||||
description: 'Exports one file per workflow (useful for versioning). Must inform a directory via --output.',
|
||||
description:
|
||||
'Exports one file per workflow (useful for versioning). Must inform a directory via --output.',
|
||||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(ExportWorkflowsCommand);
|
||||
|
||||
if (flags.backup) {
|
||||
|
@ -93,7 +84,9 @@ export class ExportWorkflowsCommand extends Command {
|
|||
fs.mkdirSync(flags.output, { recursive: true });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.');
|
||||
console.error(
|
||||
'Aborting execution as a filesystem error has been encountered while creating the output directory. See log messages for details.',
|
||||
);
|
||||
logger.error('\nFILESYSTEM ERROR');
|
||||
logger.info('====================================');
|
||||
logger.error(e.message);
|
||||
|
@ -117,6 +110,7 @@ export class ExportWorkflowsCommand extends Command {
|
|||
findQuery.id = flags.id;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const workflows = await Db.collections.Workflow!.find(findQuery);
|
||||
|
||||
if (workflows.length === 0) {
|
||||
|
@ -124,18 +118,27 @@ export class ExportWorkflowsCommand extends Command {
|
|||
}
|
||||
|
||||
if (flags.separate) {
|
||||
let fileContents: string, i: number;
|
||||
let fileContents: string;
|
||||
let i: number;
|
||||
for (i = 0; i < workflows.length; i++) {
|
||||
fileContents = JSON.stringify(workflows[i], null, flags.pretty ? 2 : undefined);
|
||||
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + workflows[i].id + '.json';
|
||||
const filename = `${
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-non-null-assertion
|
||||
(flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) +
|
||||
workflows[i].id
|
||||
}.json`;
|
||||
fs.writeFileSync(filename, fileContents);
|
||||
}
|
||||
console.info(`Successfully exported ${i} workflows.`);
|
||||
} else {
|
||||
const fileContents = JSON.stringify(workflows, null, flags.pretty ? 2 : undefined);
|
||||
if (flags.output) {
|
||||
fs.writeFileSync(flags.output!, fileContents);
|
||||
console.info(`Successfully exported ${workflows.length} ${workflows.length === 1 ? 'workflow.' : 'workflows.'}`);
|
||||
fs.writeFileSync(flags.output, fileContents);
|
||||
console.info(
|
||||
`Successfully exported ${workflows.length} ${
|
||||
workflows.length === 1 ? 'workflow.' : 'workflows.'
|
||||
}`,
|
||||
);
|
||||
} else {
|
||||
console.info(fileContents);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,16 @@
|
|||
import {
|
||||
Command,
|
||||
flags,
|
||||
} from '@oclif/command';
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable no-console */
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
Credentials,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
|
||||
import {
|
||||
Db,
|
||||
} from '../../src';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'fast-glob';
|
||||
import * as path from 'path';
|
||||
import { getLogger } from '../../src/Logger';
|
||||
import { Db } from '../../src';
|
||||
|
||||
export class ImportCredentialsCommand extends Command {
|
||||
static description = 'Import credentials';
|
||||
|
@ -43,10 +31,12 @@ export class ImportCredentialsCommand extends Command {
|
|||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(ImportCredentialsCommand);
|
||||
|
||||
if (!flags.input) {
|
||||
|
@ -76,18 +66,25 @@ export class ImportCredentialsCommand extends Command {
|
|||
}
|
||||
|
||||
if (flags.separate) {
|
||||
const files = await glob((flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep) + '*.json');
|
||||
const files = await glob(
|
||||
`${flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep}*.json`,
|
||||
);
|
||||
for (i = 0; i < files.length; i++) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const credential = JSON.parse(fs.readFileSync(files[i], { encoding: 'utf8' }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (typeof credential.data === 'object') {
|
||||
// plain data / decrypted input. Should be encrypted first.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
Credentials.prototype.setData.call(credential, credential.data, encryptionKey);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Credentials!.save(credential);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const fileContents = JSON.parse(fs.readFileSync(flags.input, { encoding: 'utf8' }));
|
||||
|
||||
if (!Array.isArray(fileContents)) {
|
||||
|
@ -97,8 +94,13 @@ export class ImportCredentialsCommand extends Command {
|
|||
for (i = 0; i < fileContents.length; i++) {
|
||||
if (typeof fileContents[i].data === 'object') {
|
||||
// plain data / decrypted input. Should be encrypted first.
|
||||
Credentials.prototype.setData.call(fileContents[i], fileContents[i].data, encryptionKey);
|
||||
Credentials.prototype.setData.call(
|
||||
fileContents[i],
|
||||
fileContents[i].data,
|
||||
encryptionKey,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Credentials!.save(fileContents[i]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,15 @@
|
|||
import {
|
||||
Command,
|
||||
flags,
|
||||
} from '@oclif/command';
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
Db,
|
||||
} from '../../src';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'fast-glob';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { getLogger } from '../../src/Logger';
|
||||
import { Db } from '../../src';
|
||||
|
||||
export class ImportWorkflowsCommand extends Command {
|
||||
static description = 'Import workflows';
|
||||
|
@ -41,10 +30,12 @@ export class ImportWorkflowsCommand extends Command {
|
|||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(ImportWorkflowsCommand);
|
||||
|
||||
if (!flags.input) {
|
||||
|
@ -68,9 +59,12 @@ export class ImportWorkflowsCommand extends Command {
|
|||
await UserSettings.prepareUserSettings();
|
||||
let i;
|
||||
if (flags.separate) {
|
||||
const files = await glob((flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep) + '*.json');
|
||||
const files = await glob(
|
||||
`${flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep}*.json`,
|
||||
);
|
||||
for (i = 0; i < files.length; i++) {
|
||||
const workflow = JSON.parse(fs.readFileSync(files[i], { encoding: 'utf8' }));
|
||||
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Workflow!.save(workflow);
|
||||
}
|
||||
} else {
|
||||
|
@ -81,6 +75,7 @@ export class ImportWorkflowsCommand extends Command {
|
|||
}
|
||||
|
||||
for (i = 0; i < fileContents.length; i++) {
|
||||
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Workflow!.save(fileContents[i]);
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +84,7 @@ export class ImportWorkflowsCommand extends Command {
|
|||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('An error occurred while exporting workflows. See log messages for details.');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
logger.error(error.message);
|
||||
this.exit(1);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
import {
|
||||
Command,
|
||||
flags,
|
||||
} from '@oclif/command';
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable no-console */
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
IDataObject
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
Db,
|
||||
} from "../../src";
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import { Db } from '../../src';
|
||||
|
||||
export class ListWorkflowCommand extends Command {
|
||||
static description = '\nList workflows';
|
||||
|
@ -31,7 +25,9 @@ export class ListWorkflowCommand extends Command {
|
|||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(ListWorkflowCommand);
|
||||
|
||||
if (flags.active !== undefined && !['true', 'false'].includes(flags.active)) {
|
||||
|
@ -46,14 +42,13 @@ export class ListWorkflowCommand extends Command {
|
|||
findQuery.active = flags.active === 'true';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const workflows = await Db.collections.Workflow!.find(findQuery);
|
||||
if (flags.onlyId) {
|
||||
workflows.forEach(workflow => console.log(workflow.id));
|
||||
workflows.forEach((workflow) => console.log(workflow.id));
|
||||
} else {
|
||||
workflows.forEach(workflow => console.log(workflow.id + "|" + workflow.name));
|
||||
workflows.forEach((workflow) => console.log(`${workflow.id}|${workflow.name}`));
|
||||
}
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.error('\nGOT ERROR');
|
||||
console.log('====================================');
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
/* eslint-disable @typescript-eslint/await-thenable */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import * as localtunnel from 'localtunnel';
|
||||
import {
|
||||
TUNNEL_SUBDOMAIN_ENV,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
import { TUNNEL_SUBDOMAIN_ENV, UserSettings } from 'n8n-core';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
const open = require('open');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as Redis from 'ioredis';
|
||||
|
||||
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||
import * as config from '../config';
|
||||
import {
|
||||
ActiveExecutions,
|
||||
|
@ -17,6 +22,7 @@ import {
|
|||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
IExecutionsCurrentSummary,
|
||||
LoadNodesAndCredentials,
|
||||
NodeTypes,
|
||||
|
@ -24,15 +30,11 @@ import {
|
|||
TestWebhooks,
|
||||
WaitTracker,
|
||||
} from '../src';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
import { getLogger } from '../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
|
||||
const open = require('open');
|
||||
|
||||
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
|
||||
let processExistCode = 0;
|
||||
|
@ -54,29 +56,32 @@ export class Start extends Command {
|
|||
description: 'opens the UI automatically in browser',
|
||||
}),
|
||||
tunnel: flags.boolean({
|
||||
description: 'runs the webhooks via a hooks.n8n.cloud tunnel server. Use only for testing and development!',
|
||||
description:
|
||||
'runs the webhooks via a hooks.n8n.cloud tunnel server. Use only for testing and development!',
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens the UI in browser
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static openBrowser() {
|
||||
const editorUrl = GenericHelpers.getBaseUrl();
|
||||
|
||||
open(editorUrl, { wait: true })
|
||||
.catch((error: Error) => {
|
||||
console.log(`\nWas not able to open URL in browser. Please open manually by visiting:\n${editorUrl}\n`);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
open(editorUrl, { wait: true }).catch((error: Error) => {
|
||||
console.log(
|
||||
`\nWas not able to open URL in browser. Please open manually by visiting:\n${editorUrl}\n`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stoppes the n8n in a graceful way.
|
||||
* Make for example sure that all the webhooks from third party services
|
||||
* get removed.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static async stopProcess() {
|
||||
getLogger().info('\nStopping n8n...');
|
||||
|
||||
|
@ -90,10 +95,12 @@ export class Start extends Command {
|
|||
process.exit(processExistCode);
|
||||
}, 30000);
|
||||
|
||||
const skipWebhookDeregistration = config.get('endpoints.skipWebhoooksDeregistrationOnShutdown') as boolean;
|
||||
const skipWebhookDeregistration = config.get(
|
||||
'endpoints.skipWebhoooksDeregistrationOnShutdown',
|
||||
) as boolean;
|
||||
|
||||
const removePromises = [];
|
||||
if (activeWorkflowRunner !== undefined && skipWebhookDeregistration !== true) {
|
||||
if (activeWorkflowRunner !== undefined && !skipWebhookDeregistration) {
|
||||
removePromises.push(activeWorkflowRunner.removeAll());
|
||||
}
|
||||
|
||||
|
@ -105,22 +112,23 @@ export class Start extends Command {
|
|||
|
||||
// Wait for active workflow executions to finish
|
||||
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
|
||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||
|
||||
let count = 0;
|
||||
while (executingWorkflows.length !== 0) {
|
||||
if (count++ % 4 === 0) {
|
||||
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
||||
executingWorkflows.map(execution => {
|
||||
// eslint-disable-next-line array-callback-return
|
||||
executingWorkflows.map((execution) => {
|
||||
console.log(` - Execution ID ${execution.id}, workflow ID: ${execution.workflowId}`);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('There was an error shutting down n8n.', error);
|
||||
}
|
||||
|
@ -128,12 +136,12 @@ export class Start extends Command {
|
|||
process.exit(processExistCode);
|
||||
}
|
||||
|
||||
|
||||
async run() {
|
||||
// Make sure that n8n shuts down gracefully if possible
|
||||
process.on('SIGTERM', Start.stopProcess);
|
||||
process.on('SIGINT', Start.stopProcess);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(Start);
|
||||
|
||||
// Wrap that the process does not close but we can still use async
|
||||
|
@ -144,7 +152,9 @@ export class Start extends Command {
|
|||
logger.info('Initializing n8n process');
|
||||
|
||||
// todo remove a few versions after release
|
||||
logger.info('\nn8n now checks for new versions and security updates. You can turn this off using the environment variable N8N_VERSION_NOTIFICATIONS_ENABLED to "false"\nFor more information, please refer to https://docs.n8n.io/getting-started/installation/advanced/configuration.html\n');
|
||||
logger.info(
|
||||
'\nn8n now checks for new versions and security updates. You can turn this off using the environment variable N8N_VERSION_NOTIFICATIONS_ENABLED to "false"\nFor more information, please refer to https://docs.n8n.io/getting-started/installation/advanced/configuration.html\n',
|
||||
);
|
||||
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init().catch((error: Error) => {
|
||||
|
@ -186,9 +196,11 @@ export class Start extends Command {
|
|||
const redisPort = config.get('queue.bull.redis.port');
|
||||
const redisDB = config.get('queue.bull.redis.db');
|
||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||
let lastTimer = 0, cumulativeTimeout = 0;
|
||||
let lastTimer = 0;
|
||||
let cumulativeTimeout = 0;
|
||||
|
||||
const settings = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
retryStrategy: (times: number): number | null => {
|
||||
const now = Date.now();
|
||||
if (now - lastTimer > 30000) {
|
||||
|
@ -199,7 +211,10 @@ export class Start extends Command {
|
|||
cumulativeTimeout += now - lastTimer;
|
||||
lastTimer = now;
|
||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
||||
logger.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
|
||||
logger.error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Unable to connect to Redis after ${redisConnectionTimeoutLimit}. Exiting process.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -235,20 +250,24 @@ export class Start extends Command {
|
|||
});
|
||||
}
|
||||
|
||||
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
||||
const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
const shouldRunVacuum = config.get('database.sqlite.executeVacuumOnStartup') as number;
|
||||
if (shouldRunVacuum) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises, @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Execution!.query('VACUUM;');
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.tunnel === true) {
|
||||
if (flags.tunnel) {
|
||||
this.log('\nWaiting for tunnel ...');
|
||||
|
||||
let tunnelSubdomain;
|
||||
if (process.env[TUNNEL_SUBDOMAIN_ENV] !== undefined && process.env[TUNNEL_SUBDOMAIN_ENV] !== '') {
|
||||
if (
|
||||
process.env[TUNNEL_SUBDOMAIN_ENV] !== undefined &&
|
||||
process.env[TUNNEL_SUBDOMAIN_ENV] !== ''
|
||||
) {
|
||||
tunnelSubdomain = process.env[TUNNEL_SUBDOMAIN_ENV];
|
||||
} else if (userSettings.tunnelSubdomain !== undefined) {
|
||||
tunnelSubdomain = userSettings.tunnelSubdomain;
|
||||
|
@ -257,9 +276,13 @@ export class Start extends Command {
|
|||
if (tunnelSubdomain === undefined) {
|
||||
// When no tunnel subdomain did exist yet create a new random one
|
||||
const availableCharacters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
userSettings.tunnelSubdomain = Array.from({ length: 24 }).map(() => {
|
||||
return availableCharacters.charAt(Math.floor(Math.random() * availableCharacters.length));
|
||||
}).join('');
|
||||
userSettings.tunnelSubdomain = Array.from({ length: 24 })
|
||||
.map(() => {
|
||||
return availableCharacters.charAt(
|
||||
Math.floor(Math.random() * availableCharacters.length),
|
||||
);
|
||||
})
|
||||
.join('');
|
||||
|
||||
await UserSettings.writeUserSettings(userSettings);
|
||||
}
|
||||
|
@ -269,14 +292,16 @@ export class Start extends Command {
|
|||
subdomain: tunnelSubdomain,
|
||||
};
|
||||
|
||||
const port = config.get('port') as number;
|
||||
const port = config.get('port');
|
||||
|
||||
// @ts-ignore
|
||||
const webhookTunnel = await localtunnel(port, tunnelSettings);
|
||||
|
||||
process.env.WEBHOOK_URL = webhookTunnel.url + '/';
|
||||
process.env.WEBHOOK_URL = `${webhookTunnel.url}/`;
|
||||
this.log(`Tunnel URL: ${process.env.WEBHOOK_URL}\n`);
|
||||
this.log('IMPORTANT! Do not share with anybody as it would give people access to your n8n instance!');
|
||||
this.log(
|
||||
'IMPORTANT! Do not share with anybody as it would give people access to your n8n instance!',
|
||||
);
|
||||
}
|
||||
|
||||
await Server.start();
|
||||
|
@ -285,6 +310,7 @@ export class Start extends Command {
|
|||
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
||||
await activeWorkflowRunner.init();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const waitTracker = WaitTracker();
|
||||
|
||||
const editorUrl = GenericHelpers.getBaseUrl();
|
||||
|
@ -297,7 +323,7 @@ export class Start extends Command {
|
|||
process.stdin.setEncoding('utf8');
|
||||
let inputText = '';
|
||||
|
||||
if (flags.open === true) {
|
||||
if (flags.open) {
|
||||
Start.openBrowser();
|
||||
}
|
||||
this.log(`\nPress "o" to open in Browser.`);
|
||||
|
@ -307,15 +333,18 @@ export class Start extends Command {
|
|||
inputText = '';
|
||||
} else if (key.charCodeAt(0) === 3) {
|
||||
// Ctrl + c got pressed
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Start.stopProcess();
|
||||
} else {
|
||||
// When anything else got pressed, record it and send it on enter into the child process
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (key.charCodeAt(0) === 13) {
|
||||
// send to child process and print in terminal
|
||||
process.stdout.write('\n');
|
||||
inputText = '';
|
||||
} else {
|
||||
// record it and write into terminal
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
inputText += key;
|
||||
process.stdout.write(key);
|
||||
}
|
||||
|
@ -323,6 +352,7 @@ export class Start extends Command {
|
|||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
this.error(`There was an error: ${error.message}`);
|
||||
|
||||
processExistCode = 1;
|
||||
|
|
|
@ -1,26 +1,16 @@
|
|||
import {
|
||||
Command, flags,
|
||||
} from '@oclif/command';
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable no-console */
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
IDataObject
|
||||
} from 'n8n-workflow';
|
||||
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
Db,
|
||||
GenericHelpers,
|
||||
} from '../../src';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { Db, GenericHelpers } from '../../src';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { getLogger } from '../../src/Logger';
|
||||
|
||||
export class UpdateWorkflowCommand extends Command {
|
||||
static description = '\Update workflows';
|
||||
static description = 'Update workflows';
|
||||
|
||||
static examples = [
|
||||
`$ n8n update:workflow --all --active=false`,
|
||||
|
@ -40,10 +30,12 @@ export class UpdateWorkflowCommand extends Command {
|
|||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(UpdateWorkflowCommand);
|
||||
|
||||
if (!flags.all && !flags.id) {
|
||||
|
@ -52,7 +44,9 @@ export class UpdateWorkflowCommand extends Command {
|
|||
}
|
||||
|
||||
if (flags.all && flags.id) {
|
||||
console.info(`Either something else on top should be "--all" or "--id" can be set never both!`);
|
||||
console.info(
|
||||
`Either something else on top should be "--all" or "--id" can be set never both!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -60,13 +54,12 @@ export class UpdateWorkflowCommand extends Command {
|
|||
if (flags.active === undefined) {
|
||||
console.info(`No update flag like "--active=true" has been set!`);
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
if (!['false', 'true'].includes(flags.active)) {
|
||||
console.info(`Valid values for flag "--active" are only "false" or "true"!`);
|
||||
return;
|
||||
}
|
||||
updateQuery.active = flags.active === 'true';
|
||||
}
|
||||
|
||||
try {
|
||||
await Db.init();
|
||||
|
@ -80,6 +73,7 @@ export class UpdateWorkflowCommand extends Command {
|
|||
findQuery.active = true;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Workflow!.update(findQuery, updateQuery);
|
||||
console.info('Done');
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import {
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as Redis from 'ioredis';
|
||||
|
||||
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||
import * as config from '../config';
|
||||
import {
|
||||
ActiveExecutions,
|
||||
|
@ -15,29 +20,20 @@ import {
|
|||
GenericHelpers,
|
||||
LoadNodesAndCredentials,
|
||||
NodeTypes,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
TestWebhooks,
|
||||
WebhookServer,
|
||||
} from '../src';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { getLogger } from '../src/Logger';
|
||||
|
||||
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
|
||||
let processExistCode = 0;
|
||||
|
||||
|
||||
export class Webhook extends Command {
|
||||
static description = 'Starts n8n webhook process. Intercepts only production URLs.';
|
||||
|
||||
static examples = [
|
||||
`$ n8n webhook`,
|
||||
];
|
||||
static examples = [`$ n8n webhook`];
|
||||
|
||||
static flags = {
|
||||
help: flags.help({ char: 'h' }),
|
||||
|
@ -48,6 +44,7 @@ export class Webhook extends Command {
|
|||
* Make for example sure that all the webhooks from third party services
|
||||
* get removed.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static async stopProcess() {
|
||||
LoggerProxy.info(`\nStopping n8n...`);
|
||||
|
||||
|
@ -68,14 +65,16 @@ export class Webhook extends Command {
|
|||
let count = 0;
|
||||
while (executingWorkflows.length !== 0) {
|
||||
if (count++ % 4 === 0) {
|
||||
LoggerProxy.info(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
||||
LoggerProxy.info(
|
||||
`Waiting for ${executingWorkflows.length} active executions to finish...`,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
LoggerProxy.error('There was an error shutting down n8n.', error);
|
||||
}
|
||||
|
@ -83,7 +82,7 @@ export class Webhook extends Command {
|
|||
process.exit(processExistCode);
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
@ -92,6 +91,7 @@ export class Webhook extends Command {
|
|||
process.on('SIGTERM', Webhook.stopProcess);
|
||||
process.on('SIGINT', Webhook.stopProcess);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(Webhook);
|
||||
|
||||
// Wrap that the process does not close but we can still use async
|
||||
|
@ -114,7 +114,8 @@ export class Webhook extends Command {
|
|||
|
||||
try {
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init().catch(error => {
|
||||
const startDbInitPromise = Db.init().catch((error) => {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
logger.error(`There was an error initializing DB: "${error.message}"`);
|
||||
|
||||
processExistCode = 1;
|
||||
|
@ -124,6 +125,7 @@ export class Webhook extends Command {
|
|||
});
|
||||
|
||||
// Make sure the settings exist
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const userSettings = await UserSettings.prepareUserSettings();
|
||||
|
||||
// Load all node and credential types
|
||||
|
@ -153,9 +155,11 @@ export class Webhook extends Command {
|
|||
const redisPort = config.get('queue.bull.redis.port');
|
||||
const redisDB = config.get('queue.bull.redis.db');
|
||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||
let lastTimer = 0, cumulativeTimeout = 0;
|
||||
let lastTimer = 0;
|
||||
let cumulativeTimeout = 0;
|
||||
|
||||
const settings = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
retryStrategy: (times: number): number | null => {
|
||||
const now = Date.now();
|
||||
if (now - lastTimer > 30000) {
|
||||
|
@ -166,7 +170,10 @@ export class Webhook extends Command {
|
|||
cumulativeTimeout += now - lastTimer;
|
||||
lastTimer = now;
|
||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
||||
logger.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
|
||||
logger.error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Unable to connect to Redis after ${redisConnectionTimeoutLimit}. Exiting process.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -208,11 +215,12 @@ export class Webhook extends Command {
|
|||
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
||||
await activeWorkflowRunner.initWebhooks();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const editorUrl = GenericHelpers.getBaseUrl();
|
||||
console.info('Webhook listener waiting for requests.');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Exiting due to error. See log message for details.');
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
logger.error(`Webhook process cannot continue. "${error.message}"`);
|
||||
|
||||
processExistCode = 1;
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import {
|
||||
UserSettings,
|
||||
WorkflowExecute,
|
||||
} from 'n8n-core';
|
||||
import { UserSettings, WorkflowExecute } from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
|
@ -13,12 +19,12 @@ import {
|
|||
IWorkflowExecuteHooks,
|
||||
Workflow,
|
||||
WorkflowHooks,
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
FindOneOptions,
|
||||
} from 'typeorm';
|
||||
import { FindOneOptions } from 'typeorm';
|
||||
|
||||
import * as Bull from 'bull';
|
||||
import {
|
||||
ActiveExecutions,
|
||||
CredentialsOverwrites,
|
||||
|
@ -37,24 +43,15 @@ import {
|
|||
WorkflowExecuteAdditionalData,
|
||||
} from '../src';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
|
||||
import {
|
||||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
import { getLogger } from '../src/Logger';
|
||||
|
||||
import * as config from '../config';
|
||||
import * as Bull from 'bull';
|
||||
import * as Queue from '../src/Queue';
|
||||
|
||||
export class Worker extends Command {
|
||||
static description = '\nStarts a n8n worker';
|
||||
|
||||
static examples = [
|
||||
`$ n8n worker --concurrency=5`,
|
||||
];
|
||||
static examples = [`$ n8n worker --concurrency=5`];
|
||||
|
||||
static flags = {
|
||||
help: flags.help({ char: 'h' }),
|
||||
|
@ -82,6 +79,7 @@ export class Worker extends Command {
|
|||
LoggerProxy.info(`Stopping n8n...`);
|
||||
|
||||
// Stop accepting new jobs
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Worker.jobQueue.pause(true);
|
||||
|
||||
try {
|
||||
|
@ -103,13 +101,17 @@ export class Worker extends Command {
|
|||
while (Object.keys(Worker.runningJobs).length !== 0) {
|
||||
if (count++ % 4 === 0) {
|
||||
const waitLeft = Math.ceil((stopTime - new Date().getTime()) / 1000);
|
||||
LoggerProxy.info(`Waiting for ${Object.keys(Worker.runningJobs).length} active executions to finish... (wait ${waitLeft} more seconds)`);
|
||||
LoggerProxy.info(
|
||||
`Waiting for ${
|
||||
Object.keys(Worker.runningJobs).length
|
||||
} active executions to finish... (wait ${waitLeft} more seconds)`,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
LoggerProxy.error('There was an error shutting down n8n.', error);
|
||||
}
|
||||
|
@ -119,25 +121,38 @@ export class Worker extends Command {
|
|||
|
||||
async runJob(job: Bull.Job, nodeTypes: INodeTypes): Promise<IBullJobResponse> {
|
||||
const jobData = job.data as IBullJobData;
|
||||
const executionDb = await Db.collections.Execution!.findOne(jobData.executionId) as IExecutionFlattedDb;
|
||||
const currentExecutionDb = ResponseHelper.unflattenExecutionData(executionDb) as IExecutionResponse;
|
||||
LoggerProxy.info(`Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${jobData.executionId})`);
|
||||
const executionDb = (await Db.collections.Execution!.findOne(
|
||||
jobData.executionId,
|
||||
)) as IExecutionFlattedDb;
|
||||
const currentExecutionDb = ResponseHelper.unflattenExecutionData(executionDb);
|
||||
LoggerProxy.info(
|
||||
`Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${jobData.executionId})`,
|
||||
);
|
||||
|
||||
let staticData = currentExecutionDb.workflowData!.staticData;
|
||||
if (jobData.loadStaticData === true) {
|
||||
let { staticData } = currentExecutionDb.workflowData;
|
||||
if (jobData.loadStaticData) {
|
||||
const findOptions = {
|
||||
select: ['id', 'staticData'],
|
||||
} as FindOneOptions;
|
||||
const workflowData = await Db.collections!.Workflow!.findOne(currentExecutionDb.workflowData.id, findOptions);
|
||||
const workflowData = await Db.collections.Workflow!.findOne(
|
||||
currentExecutionDb.workflowData.id,
|
||||
findOptions,
|
||||
);
|
||||
if (workflowData === undefined) {
|
||||
throw new Error(`The workflow with the ID "${currentExecutionDb.workflowData.id}" could not be found`);
|
||||
throw new Error(
|
||||
`The workflow with the ID "${currentExecutionDb.workflowData.id}" could not be found`,
|
||||
);
|
||||
}
|
||||
staticData = workflowData.staticData;
|
||||
}
|
||||
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (currentExecutionDb.workflowData.settings && currentExecutionDb.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = currentExecutionDb.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
currentExecutionDb.workflowData.settings &&
|
||||
currentExecutionDb.workflowData.settings.executionTimeout
|
||||
) {
|
||||
workflowTimeout = currentExecutionDb.workflowData.settings.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
let executionTimeoutTimestamp: number | undefined;
|
||||
|
@ -146,16 +161,37 @@ export class Worker extends Command {
|
|||
executionTimeoutTimestamp = Date.now() + workflowTimeout * 1000;
|
||||
}
|
||||
|
||||
const workflow = new Workflow({ id: currentExecutionDb.workflowData.id as string, name: currentExecutionDb.workflowData.name, nodes: currentExecutionDb.workflowData!.nodes, connections: currentExecutionDb.workflowData!.connections, active: currentExecutionDb.workflowData!.active, nodeTypes, staticData, settings: currentExecutionDb.workflowData!.settings });
|
||||
const workflow = new Workflow({
|
||||
id: currentExecutionDb.workflowData.id as string,
|
||||
name: currentExecutionDb.workflowData.name,
|
||||
nodes: currentExecutionDb.workflowData.nodes,
|
||||
connections: currentExecutionDb.workflowData.connections,
|
||||
active: currentExecutionDb.workflowData.active,
|
||||
nodeTypes,
|
||||
staticData,
|
||||
settings: currentExecutionDb.workflowData.settings,
|
||||
});
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, executionTimeoutTimestamp);
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(currentExecutionDb.mode, job.data.executionId, currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(
|
||||
undefined,
|
||||
executionTimeoutTimestamp,
|
||||
);
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(
|
||||
currentExecutionDb.mode,
|
||||
job.data.executionId,
|
||||
currentExecutionDb.workflowData,
|
||||
{ retryOf: currentExecutionDb.retryOf as string },
|
||||
);
|
||||
additionalData.executionId = jobData.executionId;
|
||||
|
||||
let workflowExecute: WorkflowExecute;
|
||||
let workflowRun: PCancelable<IRun>;
|
||||
if (currentExecutionDb.data !== undefined) {
|
||||
workflowExecute = new WorkflowExecute(additionalData, currentExecutionDb.mode, currentExecutionDb.data);
|
||||
workflowExecute = new WorkflowExecute(
|
||||
additionalData,
|
||||
currentExecutionDb.mode,
|
||||
currentExecutionDb.data,
|
||||
);
|
||||
workflowRun = workflowExecute.processRunExecutionData(workflow);
|
||||
} else {
|
||||
// Execute all nodes
|
||||
|
@ -180,6 +216,7 @@ export class Worker extends Command {
|
|||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Starting n8n worker...');
|
||||
|
||||
// Make sure that n8n shuts down gracefully if possible
|
||||
|
@ -192,7 +229,7 @@ export class Worker extends Command {
|
|||
const { flags } = this.parse(Worker);
|
||||
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init().catch(error => {
|
||||
const startDbInitPromise = Db.init().catch((error) => {
|
||||
logger.error(`There was an error initializing DB: "${error.message}"`);
|
||||
|
||||
Worker.processExistCode = 1;
|
||||
|
@ -225,10 +262,12 @@ export class Worker extends Command {
|
|||
// Wait till the database is ready
|
||||
await startDbInitPromise;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||
|
||||
Worker.jobQueue = Queue.getInstance().getBullObjectInstance();
|
||||
Worker.jobQueue.process(flags.concurrency, (job) => this.runJob(job, nodeTypes));
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Worker.jobQueue.process(flags.concurrency, async (job) => this.runJob(job, nodeTypes));
|
||||
|
||||
const versions = await GenericHelpers.getVersions();
|
||||
|
||||
|
@ -251,9 +290,10 @@ export class Worker extends Command {
|
|||
}
|
||||
});
|
||||
|
||||
let lastTimer = 0, cumulativeTimeout = 0;
|
||||
let lastTimer = 0;
|
||||
let cumulativeTimeout = 0;
|
||||
Worker.jobQueue.on('error', (error: Error) => {
|
||||
if (error.toString().includes('ECONNREFUSED') === true) {
|
||||
if (error.toString().includes('ECONNREFUSED')) {
|
||||
const now = Date.now();
|
||||
if (now - lastTimer > 30000) {
|
||||
// Means we had no timeout at all or last timeout was temporary and we recovered
|
||||
|
@ -263,12 +303,14 @@ export class Worker extends Command {
|
|||
cumulativeTimeout += now - lastTimer;
|
||||
lastTimer = now;
|
||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
||||
logger.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
|
||||
logger.error(
|
||||
`Unable to connect to Redis after ${redisConnectionTimeoutLimit}. Exiting process.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
logger.warn('Redis unavailable - trying to reconnect...');
|
||||
} else if (error.toString().includes('Error initializing Lua scripts') === true) {
|
||||
} else if (error.toString().includes('Error initializing Lua scripts')) {
|
||||
// This is a non-recoverable error
|
||||
// Happens when worker starts and Redis is unavailable
|
||||
// Even if Redis comes back online, worker will be zombie
|
||||
|
@ -287,6 +329,5 @@ export class Worker extends Command {
|
|||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import * as convict from 'convict';
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as path from 'path';
|
||||
|
@ -6,7 +9,6 @@ import * as core from 'n8n-core';
|
|||
dotenv.config();
|
||||
|
||||
const config = convict({
|
||||
|
||||
database: {
|
||||
type: {
|
||||
doc: 'Type of database to use',
|
||||
|
@ -84,7 +86,6 @@ const config = convict({
|
|||
env: 'DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED',
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
mysqldb: {
|
||||
database: {
|
||||
|
@ -159,7 +160,6 @@ const config = convict({
|
|||
},
|
||||
|
||||
executions: {
|
||||
|
||||
// By default workflows get always executed in their own process.
|
||||
// If this option gets set to "main" it will run them in the
|
||||
// main-process instead.
|
||||
|
@ -573,7 +573,6 @@ const config = convict({
|
|||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw new TypeError(`The Nodes to exclude is not a valid Array of strings.`);
|
||||
}
|
||||
|
@ -644,7 +643,6 @@ const config = convict({
|
|||
env: 'N8N_VERSION_NOTIFICATIONS_INFO_URL',
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// Overwrite default configuration with settings which got defined in
|
||||
|
|
|
@ -4,88 +4,72 @@ import { entities } from '../src/databases/entities';
|
|||
|
||||
module.exports = [
|
||||
{
|
||||
"name": "sqlite",
|
||||
"type": "sqlite",
|
||||
"logging": true,
|
||||
"entities": Object.values(entities),
|
||||
"database": path.join(UserSettings.getUserN8nFolderPath(), 'database.sqlite'),
|
||||
"migrations": [
|
||||
"./src/databases/sqlite/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"./src/databases/sqlite/subscribers/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/entities",
|
||||
"migrationsDir": "./src/databases/sqlite/migrations",
|
||||
"subscribersDir": "./src/databases/sqlite/subscribers"
|
||||
}
|
||||
name: 'sqlite',
|
||||
type: 'sqlite',
|
||||
logging: true,
|
||||
entities: Object.values(entities),
|
||||
database: path.join(UserSettings.getUserN8nFolderPath(), 'database.sqlite'),
|
||||
migrations: ['./src/databases/sqlite/migrations/*.ts'],
|
||||
subscribers: ['./src/databases/sqlite/subscribers/*.ts'],
|
||||
cli: {
|
||||
entitiesDir: './src/databases/entities',
|
||||
migrationsDir: './src/databases/sqlite/migrations',
|
||||
subscribersDir: './src/databases/sqlite/subscribers',
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "postgres",
|
||||
"type": "postgres",
|
||||
"logging": false,
|
||||
"host": "localhost",
|
||||
"username": "postgres",
|
||||
"password": "",
|
||||
"port": 5432,
|
||||
"database": "n8n",
|
||||
"schema": "public",
|
||||
"entities": Object.values(entities),
|
||||
"migrations": [
|
||||
"./src/databases/postgresdb/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"src/subscriber/**/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/entities",
|
||||
"migrationsDir": "./src/databases/postgresdb/migrations",
|
||||
"subscribersDir": "./src/databases/postgresdb/subscribers"
|
||||
}
|
||||
name: 'postgres',
|
||||
type: 'postgres',
|
||||
logging: false,
|
||||
host: 'localhost',
|
||||
username: 'postgres',
|
||||
password: '',
|
||||
port: 5432,
|
||||
database: 'n8n',
|
||||
schema: 'public',
|
||||
entities: Object.values(entities),
|
||||
migrations: ['./src/databases/postgresdb/migrations/*.ts'],
|
||||
subscribers: ['src/subscriber/**/*.ts'],
|
||||
cli: {
|
||||
entitiesDir: './src/databases/entities',
|
||||
migrationsDir: './src/databases/postgresdb/migrations',
|
||||
subscribersDir: './src/databases/postgresdb/subscribers',
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "mysql",
|
||||
"type": "mysql",
|
||||
"database": "n8n",
|
||||
"username": "root",
|
||||
"password": "password",
|
||||
"host": "localhost",
|
||||
"port": "3306",
|
||||
"logging": false,
|
||||
"entities": Object.values(entities),
|
||||
"migrations": [
|
||||
"./src/databases/mysqldb/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"src/subscriber/**/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/entities",
|
||||
"migrationsDir": "./src/databases/mysqldb/migrations",
|
||||
"subscribersDir": "./src/databases/mysqldb/Subscribers"
|
||||
}
|
||||
name: 'mysql',
|
||||
type: 'mysql',
|
||||
database: 'n8n',
|
||||
username: 'root',
|
||||
password: 'password',
|
||||
host: 'localhost',
|
||||
port: '3306',
|
||||
logging: false,
|
||||
entities: Object.values(entities),
|
||||
migrations: ['./src/databases/mysqldb/migrations/*.ts'],
|
||||
subscribers: ['src/subscriber/**/*.ts'],
|
||||
cli: {
|
||||
entitiesDir: './src/databases/entities',
|
||||
migrationsDir: './src/databases/mysqldb/migrations',
|
||||
subscribersDir: './src/databases/mysqldb/Subscribers',
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "mariadb",
|
||||
"type": "mariadb",
|
||||
"database": "n8n",
|
||||
"username": "root",
|
||||
"password": "password",
|
||||
"host": "localhost",
|
||||
"port": "3306",
|
||||
"logging": false,
|
||||
"entities": Object.values(entities),
|
||||
"migrations": [
|
||||
"./src/databases/mysqldb/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"src/subscriber/**/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/entities",
|
||||
"migrationsDir": "./src/databases/mysqldb/migrations",
|
||||
"subscribersDir": "./src/databases/mysqldb/Subscribers"
|
||||
}
|
||||
name: 'mariadb',
|
||||
type: 'mariadb',
|
||||
database: 'n8n',
|
||||
username: 'root',
|
||||
password: 'password',
|
||||
host: 'localhost',
|
||||
port: '3306',
|
||||
logging: false,
|
||||
entities: Object.values(entities),
|
||||
migrations: ['./src/databases/mysqldb/migrations/*.ts'],
|
||||
subscribers: ['src/subscriber/**/*.ts'],
|
||||
cli: {
|
||||
entitiesDir: './src/databases/entities',
|
||||
migrationsDir: './src/databases/mysqldb/migrations',
|
||||
subscribersDir: './src/databases/mysqldb/Subscribers',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -21,14 +21,15 @@
|
|||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon\"",
|
||||
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/cli/**/**.ts --write",
|
||||
"lint": "cd ../.. && node_modules/eslint/bin/eslint.js packages/cli",
|
||||
"lintfix": "cd ../.. && node_modules/eslint/bin/eslint.js packages/cli --fix",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||
"start": "run-script-os",
|
||||
"start:default": "cd bin && ./n8n",
|
||||
"start:windows": "cd bin && n8n",
|
||||
"test": "jest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
||||
},
|
||||
|
@ -77,7 +78,7 @@
|
|||
"ts-jest": "^26.3.0",
|
||||
"ts-node": "^8.9.1",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
import {
|
||||
IRun,
|
||||
} from 'n8n-workflow';
|
||||
/* eslint-disable prefer-template */
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { IRun } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
createDeferredPromise,
|
||||
} from 'n8n-core';
|
||||
import { createDeferredPromise } from 'n8n-core';
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
Db,
|
||||
IExecutingWorkflowData,
|
||||
|
@ -17,16 +24,11 @@ import {
|
|||
WorkflowHelpers,
|
||||
} from '.';
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
|
||||
|
||||
export class ActiveExecutions {
|
||||
private activeExecutions: {
|
||||
[index: string]: IExecutingWorkflowData;
|
||||
} = {};
|
||||
|
||||
|
||||
/**
|
||||
* Add a new active execution
|
||||
*
|
||||
|
@ -35,8 +37,11 @@ export class ActiveExecutions {
|
|||
* @returns {string}
|
||||
* @memberof ActiveExecutions
|
||||
*/
|
||||
async add(executionData: IWorkflowExecutionDataProcess, process?: ChildProcess, executionId?: string): Promise<string> {
|
||||
|
||||
async add(
|
||||
executionData: IWorkflowExecutionDataProcess,
|
||||
process?: ChildProcess,
|
||||
executionId?: string,
|
||||
): Promise<string> {
|
||||
if (executionId === undefined) {
|
||||
// Is a new execution so save in DB
|
||||
|
||||
|
@ -52,14 +57,23 @@ export class ActiveExecutions {
|
|||
fullExecutionData.retryOf = executionData.retryOf.toString();
|
||||
}
|
||||
|
||||
if (executionData.workflowData.id !== undefined && WorkflowHelpers.isWorkflowIdValid(executionData.workflowData.id.toString()) === true) {
|
||||
if (
|
||||
executionData.workflowData.id !== undefined &&
|
||||
WorkflowHelpers.isWorkflowIdValid(executionData.workflowData.id.toString())
|
||||
) {
|
||||
fullExecutionData.workflowId = executionData.workflowData.id.toString();
|
||||
}
|
||||
|
||||
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
const executionResult = await Db.collections.Execution!.save(execution as IExecutionFlattedDb);
|
||||
executionId = typeof executionResult.id === "object" ? executionResult.id!.toString() : executionResult.id + "";
|
||||
const executionResult = await Db.collections.Execution!.save(
|
||||
execution as IExecutionFlattedDb,
|
||||
);
|
||||
executionId =
|
||||
typeof executionResult.id === 'object'
|
||||
? // @ts-ignore
|
||||
executionResult.id!.toString()
|
||||
: executionResult.id + '';
|
||||
} else {
|
||||
// Is an existing execution we want to finish so update in DB
|
||||
|
||||
|
@ -72,6 +86,7 @@ export class ActiveExecutions {
|
|||
await Db.collections.Execution!.update(executionId, execution);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this.activeExecutions[executionId] = {
|
||||
executionData,
|
||||
process,
|
||||
|
@ -79,10 +94,10 @@ export class ActiveExecutions {
|
|||
postExecutePromises: [],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
return executionId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attaches an execution
|
||||
*
|
||||
|
@ -90,15 +105,17 @@ export class ActiveExecutions {
|
|||
* @param {PCancelable<IRun>} workflowExecution
|
||||
* @memberof ActiveExecutions
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
attachWorkflowExecution(executionId: string, workflowExecution: PCancelable<IRun>) {
|
||||
if (this.activeExecutions[executionId] === undefined) {
|
||||
throw new Error(`No active execution with id "${executionId}" got found to attach to workflowExecution to!`);
|
||||
throw new Error(
|
||||
`No active execution with id "${executionId}" got found to attach to workflowExecution to!`,
|
||||
);
|
||||
}
|
||||
|
||||
this.activeExecutions[executionId].workflowExecution = workflowExecution;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove an active execution
|
||||
*
|
||||
|
@ -113,6 +130,7 @@ export class ActiveExecutions {
|
|||
}
|
||||
|
||||
// Resolve all the waiting promises
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const promise of this.activeExecutions[executionId].postExecutePromises) {
|
||||
promise.resolve(fullRunData);
|
||||
}
|
||||
|
@ -121,7 +139,6 @@ export class ActiveExecutions {
|
|||
delete this.activeExecutions[executionId];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forces an execution to stop
|
||||
*
|
||||
|
@ -144,7 +161,8 @@ export class ActiveExecutions {
|
|||
setTimeout(() => {
|
||||
// execute on next event loop tick;
|
||||
this.activeExecutions[executionId].process!.send({
|
||||
type: timeout ? timeout : 'stopExecution',
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
type: timeout || 'stopExecution',
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
|
@ -153,10 +171,10 @@ export class ActiveExecutions {
|
|||
this.activeExecutions[executionId].workflowExecution!.cancel();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return this.getPostExecutePromise(executionId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a promise which will resolve with the data of the execution
|
||||
* with the given id
|
||||
|
@ -178,7 +196,6 @@ export class ActiveExecutions {
|
|||
return waitPromise.promise();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the currently active executions
|
||||
*
|
||||
|
@ -189,25 +206,22 @@ export class ActiveExecutions {
|
|||
const returnData: IExecutionsCurrentSummary[] = [];
|
||||
|
||||
let data;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const id of Object.keys(this.activeExecutions)) {
|
||||
data = this.activeExecutions[id];
|
||||
returnData.push(
|
||||
{
|
||||
returnData.push({
|
||||
id,
|
||||
retryOf: data.executionData.retryOf as string | undefined,
|
||||
startedAt: data.startedAt,
|
||||
mode: data.executionData.executionMode,
|
||||
workflowId: data.executionData.workflowData.id! as string,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let activeExecutionsInstance: ActiveExecutions | undefined;
|
||||
|
||||
export function getInstance(): ActiveExecutions {
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
import {
|
||||
Db,
|
||||
IActivationError,
|
||||
IResponseCallbackData,
|
||||
IWebhookDb,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
ActiveWorkflows,
|
||||
NodeExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
/* eslint-disable prefer-spread */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ActiveWorkflows, NodeExecuteFunctions } from 'n8n-core';
|
||||
|
||||
import {
|
||||
IExecuteData,
|
||||
|
@ -32,12 +24,28 @@ import {
|
|||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
LoggerProxy as Logger,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as express from 'express';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
LoggerProxy as Logger,
|
||||
} from 'n8n-workflow';
|
||||
Db,
|
||||
IActivationError,
|
||||
IResponseCallbackData,
|
||||
IWebhookDb,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
} from '.';
|
||||
|
||||
const WEBHOOK_PROD_UNREGISTERED_HINT = `The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)`;
|
||||
|
||||
|
@ -48,14 +56,16 @@ export class ActiveWorkflowRunner {
|
|||
[key: string]: IActivationError;
|
||||
} = {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async init() {
|
||||
|
||||
// Get the active workflows from database
|
||||
|
||||
// NOTE
|
||||
// Here I guess we can have a flag on the workflow table like hasTrigger
|
||||
// so intead of pulling all the active wehhooks just pull the actives that have a trigger
|
||||
const workflowsData: IWorkflowDb[] = await Db.collections.Workflow!.find({ active: true }) as IWorkflowDb[];
|
||||
const workflowsData: IWorkflowDb[] = (await Db.collections.Workflow!.find({
|
||||
active: true,
|
||||
})) as IWorkflowDb[];
|
||||
|
||||
// Clear up active workflow table
|
||||
await Db.collections.Webhook?.clear();
|
||||
|
@ -69,21 +79,32 @@ 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:`);
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
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)');
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async initWebhooks() {
|
||||
this.activeWorkflows = new ActiveWorkflows();
|
||||
}
|
||||
|
@ -104,7 +125,10 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
|
||||
const activeWorkflows = await this.getActiveWorkflows();
|
||||
activeWorkflowId.push.apply(activeWorkflowId, activeWorkflows.map(workflow => workflow.id));
|
||||
activeWorkflowId.push.apply(
|
||||
activeWorkflowId,
|
||||
activeWorkflows.map((workflow) => workflow.id),
|
||||
);
|
||||
|
||||
const removePromises = [];
|
||||
for (const workflowId of activeWorkflowId) {
|
||||
|
@ -112,7 +136,6 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
|
||||
await Promise.all(removePromises);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,10 +148,19 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {Promise<object>}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async executeWebhook(httpMethod: WebhookHttpMethod, path: string, req: express.Request, res: express.Response): Promise<IResponseCallbackData> {
|
||||
async executeWebhook(
|
||||
httpMethod: WebhookHttpMethod,
|
||||
path: string,
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<IResponseCallbackData> {
|
||||
Logger.debug(`Received webhoook "${httpMethod}" for path "${path}"`);
|
||||
if (this.activeWorkflows === null) {
|
||||
throw new ResponseHelper.ResponseError('The "activeWorkflows" instance did not get initialized yet.', 404, 404);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
'The "activeWorkflows" instance did not get initialized yet.',
|
||||
404,
|
||||
404,
|
||||
);
|
||||
}
|
||||
|
||||
// Reset request parameters
|
||||
|
@ -139,7 +171,10 @@ export class ActiveWorkflowRunner {
|
|||
path = path.slice(0, -1);
|
||||
}
|
||||
|
||||
let webhook = await Db.collections.Webhook?.findOne({ webhookPath: path, method: httpMethod }) as IWebhookDb;
|
||||
let webhook = (await Db.collections.Webhook?.findOne({
|
||||
webhookPath: path,
|
||||
method: httpMethod,
|
||||
})) as IWebhookDb;
|
||||
let webhookId: string | undefined;
|
||||
|
||||
// check if path is dynamic
|
||||
|
@ -147,19 +182,30 @@ export class ActiveWorkflowRunner {
|
|||
// check if a dynamic webhook path exists
|
||||
const pathElements = path.split('/');
|
||||
webhookId = pathElements.shift();
|
||||
const dynamicWebhooks = await Db.collections.Webhook?.find({ webhookId, method: httpMethod, pathLength: pathElements.length });
|
||||
const dynamicWebhooks = await Db.collections.Webhook?.find({
|
||||
webhookId,
|
||||
method: httpMethod,
|
||||
pathLength: pathElements.length,
|
||||
});
|
||||
if (dynamicWebhooks === undefined || dynamicWebhooks.length === 0) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_PROD_UNREGISTERED_HINT);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`The requested webhook "${httpMethod} ${path}" is not registered.`,
|
||||
404,
|
||||
404,
|
||||
WEBHOOK_PROD_UNREGISTERED_HINT,
|
||||
);
|
||||
}
|
||||
|
||||
let maxMatches = 0;
|
||||
const pathElementsSet = new Set(pathElements);
|
||||
// check if static elements match in path
|
||||
// if more results have been returned choose the one with the most static-route matches
|
||||
dynamicWebhooks.forEach(dynamicWebhook => {
|
||||
const staticElements = dynamicWebhook.webhookPath.split('/').filter(ele => !ele.startsWith(':'));
|
||||
const allStaticExist = staticElements.every(staticEle => pathElementsSet.has(staticEle));
|
||||
dynamicWebhooks.forEach((dynamicWebhook) => {
|
||||
const staticElements = dynamicWebhook.webhookPath
|
||||
.split('/')
|
||||
.filter((ele) => !ele.startsWith(':'));
|
||||
const allStaticExist = staticElements.every((staticEle) => pathElementsSet.has(staticEle));
|
||||
|
||||
if (allStaticExist && staticElements.length > maxMatches) {
|
||||
maxMatches = staticElements.length;
|
||||
|
@ -171,12 +217,20 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
});
|
||||
if (webhook === undefined) {
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_PROD_UNREGISTERED_HINT);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`The requested webhook "${httpMethod} ${path}" is not registered.`,
|
||||
404,
|
||||
404,
|
||||
WEBHOOK_PROD_UNREGISTERED_HINT,
|
||||
);
|
||||
}
|
||||
|
||||
path = webhook!.webhookPath;
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
path = webhook.webhookPath;
|
||||
// extracting params from path
|
||||
webhook!.webhookPath.split('/').forEach((ele, index) => {
|
||||
// @ts-ignore
|
||||
webhook.webhookPath.split('/').forEach((ele, index) => {
|
||||
if (ele.startsWith(':')) {
|
||||
// write params to req.params
|
||||
req.params[ele.slice(1)] = pathElements[index];
|
||||
|
@ -186,16 +240,33 @@ export class ActiveWorkflowRunner {
|
|||
|
||||
const workflowData = await Db.collections.Workflow!.findOne(webhook.workflowId);
|
||||
if (workflowData === undefined) {
|
||||
throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhook.workflowId}"`, 404, 404);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`Could not find workflow with id "${webhook.workflowId}"`,
|
||||
404,
|
||||
404,
|
||||
);
|
||||
}
|
||||
|
||||
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 additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
|
||||
const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(webhook.node as string) as INode, additionalData).filter((webhook) => {
|
||||
return (webhook.httpMethod === httpMethod && webhook.path === path);
|
||||
const webhookData = NodeHelpers.getNodeWebhooks(
|
||||
workflow,
|
||||
workflow.getNode(webhook.node) as INode,
|
||||
additionalData,
|
||||
).filter((webhook) => {
|
||||
return webhook.httpMethod === httpMethod && webhook.path === path;
|
||||
})[0];
|
||||
|
||||
// Get the node which has the webhook defined to know where to start from and to
|
||||
|
@ -208,13 +279,26 @@ export class ActiveWorkflowRunner {
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
const executionMode = 'webhook';
|
||||
//@ts-ignore
|
||||
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, undefined, undefined, req, res, (error: Error | null, data: object) => {
|
||||
// @ts-ignore
|
||||
WebhookHelpers.executeWebhook(
|
||||
workflow,
|
||||
webhookData,
|
||||
workflowData,
|
||||
workflowStartNode,
|
||||
executionMode,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
req,
|
||||
res,
|
||||
// eslint-disable-next-line consistent-return
|
||||
(error: Error | null, data: object) => {
|
||||
if (error !== null) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -226,10 +310,10 @@ export class ActiveWorkflowRunner {
|
|||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async getWebhookMethods(path: string): Promise<string[]> {
|
||||
const webhooks = await Db.collections.Webhook?.find({ webhookPath: path }) as IWebhookDb[];
|
||||
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);
|
||||
const webhookMethods: string[] = webhooks.map((webhook) => webhook.method);
|
||||
return webhookMethods;
|
||||
}
|
||||
|
||||
|
@ -240,11 +324,15 @@ export class ActiveWorkflowRunner {
|
|||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async getActiveWorkflows(): Promise<IWorkflowDb[]> {
|
||||
const activeWorkflows = await Db.collections.Workflow?.find({ where: { active: true }, select: ['id'] }) as IWorkflowDb[];
|
||||
return activeWorkflows.filter(workflow => this.activationErrors[workflow.id.toString()] === undefined);
|
||||
const activeWorkflows = (await Db.collections.Workflow?.find({
|
||||
where: { active: true },
|
||||
select: ['id'],
|
||||
})) as IWorkflowDb[];
|
||||
return activeWorkflows.filter(
|
||||
(workflow) => this.activationErrors[workflow.id.toString()] === undefined,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the workflow is active
|
||||
*
|
||||
|
@ -253,8 +341,8 @@ export class ActiveWorkflowRunner {
|
|||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async isActive(id: string): Promise<boolean> {
|
||||
const workflow = await Db.collections.Workflow?.findOne({ id: Number(id) }) as IWorkflowDb;
|
||||
return workflow?.active as boolean;
|
||||
const workflow = (await Db.collections.Workflow?.findOne({ id: Number(id) })) as IWorkflowDb;
|
||||
return workflow?.active;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -281,12 +369,16 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<void> {
|
||||
async addWorkflowWebhooks(
|
||||
workflow: Workflow,
|
||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): Promise<void> {
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true);
|
||||
let path = '' as string | undefined;
|
||||
|
||||
for (const webhookData of webhooks) {
|
||||
|
||||
const node = workflow.getNode(webhookData.node) as INode;
|
||||
node.name = webhookData.node;
|
||||
|
||||
|
@ -312,18 +404,35 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await Db.collections.Webhook?.insert(webhook);
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, activation, false);
|
||||
const webhookExists = await workflow.runWebhookMethod(
|
||||
'checkExists',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
activation,
|
||||
false,
|
||||
);
|
||||
if (webhookExists !== true) {
|
||||
// If webhook does not exist yet create it
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, activation, false);
|
||||
await workflow.runWebhookMethod(
|
||||
'create',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
activation,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
try {
|
||||
await this.removeWorkflowWebhooks(workflow.id as string);
|
||||
} catch (error) {
|
||||
console.error(`Could not remove webhooks of workflow "${workflow.id}" because of error: "${error.message}"`);
|
||||
console.error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Could not remove webhooks of workflow "${workflow.id}" because of error: "${error.message}"`,
|
||||
);
|
||||
}
|
||||
|
||||
let errorMessage = '';
|
||||
|
@ -337,6 +446,7 @@ export class ActiveWorkflowRunner {
|
|||
// it's a error runnig the webhook methods (checkExists, create)
|
||||
errorMessage = error.detail;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
|
@ -347,7 +457,6 @@ export class ActiveWorkflowRunner {
|
|||
await WorkflowHelpers.saveStaticData(workflow);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove all the webhooks of the workflow
|
||||
*
|
||||
|
@ -362,7 +471,16 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
const workflow = new Workflow({ id: workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
||||
const workflow = new Workflow({
|
||||
id: workflowId,
|
||||
name: workflowData.name,
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: workflowData.active,
|
||||
nodeTypes,
|
||||
staticData: workflowData.staticData,
|
||||
settings: workflowData.settings,
|
||||
});
|
||||
|
||||
const mode = 'internal';
|
||||
|
||||
|
@ -371,7 +489,14 @@ export class ActiveWorkflowRunner {
|
|||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true);
|
||||
|
||||
for (const webhookData of webhooks) {
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, 'update', false);
|
||||
await workflow.runWebhookMethod(
|
||||
'delete',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
'update',
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
await WorkflowHelpers.saveStaticData(workflow);
|
||||
|
@ -394,7 +519,14 @@ export class ActiveWorkflowRunner {
|
|||
* @returns
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
runWorkflow(workflowData: IWorkflowDb, node: INode, data: INodeExecutionData[][], additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode) {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async runWorkflow(
|
||||
workflowData: IWorkflowDb,
|
||||
node: INode,
|
||||
data: INodeExecutionData[][],
|
||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
||||
mode: WorkflowExecuteMode,
|
||||
) {
|
||||
const nodeExecutionStack: IExecuteData[] = [
|
||||
{
|
||||
node,
|
||||
|
@ -427,7 +559,6 @@ export class ActiveWorkflowRunner {
|
|||
return workflowRunner.run(runData, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return poll function which gets the global functions from n8n-core
|
||||
* and overwrites the __emit to be able to start it in subprocess
|
||||
|
@ -438,18 +569,30 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {IGetExecutePollFunctions}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
getExecutePollFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecutePollFunctions {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
const returnFunctions = NodeExecuteFunctions.getExecutePollFunctions(workflow, node, additionalData, mode, activation);
|
||||
getExecutePollFunctions(
|
||||
workflowData: IWorkflowDb,
|
||||
additionalData: IWorkflowExecuteAdditionalDataWorkflow,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): IGetExecutePollFunctions {
|
||||
return (workflow: Workflow, node: INode) => {
|
||||
const returnFunctions = NodeExecuteFunctions.getExecutePollFunctions(
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
mode,
|
||||
activation,
|
||||
);
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
returnFunctions.__emit = (data: INodeExecutionData[][]): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`);
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
||||
};
|
||||
return returnFunctions;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return trigger function which gets the global functions from n8n-core
|
||||
* and overwrites the emit to be able to start it in subprocess
|
||||
|
@ -460,16 +603,31 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {IGetExecuteTriggerFunctions}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation);
|
||||
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 => {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.debug(`Received trigger for workflow "${workflow.name}"`);
|
||||
WorkflowHelpers.saveStaticData(workflow);
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) => console.error(err));
|
||||
// eslint-disable-next-line id-denylist
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) =>
|
||||
console.error(err),
|
||||
);
|
||||
};
|
||||
return returnFunctions;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -480,7 +638,11 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async add(workflowId: string, activation: WorkflowActivateMode, workflowData?: IWorkflowDb): Promise<void> {
|
||||
async add(
|
||||
workflowId: string,
|
||||
activation: WorkflowActivateMode,
|
||||
workflowData?: IWorkflowDb,
|
||||
): Promise<void> {
|
||||
if (this.activeWorkflows === null) {
|
||||
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
||||
}
|
||||
|
@ -488,33 +650,69 @@ export class ActiveWorkflowRunner {
|
|||
let workflowInstance: Workflow;
|
||||
try {
|
||||
if (workflowData === undefined) {
|
||||
workflowData = await Db.collections.Workflow!.findOne(workflowId) as IWorkflowDb;
|
||||
workflowData = (await Db.collections.Workflow!.findOne(workflowId)) as IWorkflowDb;
|
||||
}
|
||||
|
||||
if (!workflowData) {
|
||||
throw new Error(`Could not find workflow with id "${workflowId}".`);
|
||||
}
|
||||
const nodeTypes = NodeTypes();
|
||||
workflowInstance = new Workflow({ id: workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
||||
workflowInstance = new Workflow({
|
||||
id: workflowId,
|
||||
name: workflowData.name,
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: workflowData.active,
|
||||
nodeTypes,
|
||||
staticData: workflowData.staticData,
|
||||
settings: workflowData.settings,
|
||||
});
|
||||
|
||||
const canBeActivated = workflowInstance.checkIfWorkflowCanBeActivated(['n8n-nodes-base.start']);
|
||||
if (canBeActivated === false) {
|
||||
const canBeActivated = workflowInstance.checkIfWorkflowCanBeActivated([
|
||||
'n8n-nodes-base.start',
|
||||
]);
|
||||
if (!canBeActivated) {
|
||||
Logger.error(`Unable to activate workflow "${workflowData.name}"`);
|
||||
throw new Error(`The workflow can not be activated because it does not contain any nodes which could start the workflow. Only workflows which have trigger or webhook nodes can be activated.`);
|
||||
throw new Error(
|
||||
`The workflow can not be activated because it does not contain any nodes which could start the workflow. Only workflows which have trigger or webhook nodes can be activated.`,
|
||||
);
|
||||
}
|
||||
|
||||
const mode = 'trigger';
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode, activation);
|
||||
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode, activation);
|
||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(
|
||||
workflowData,
|
||||
additionalData,
|
||||
mode,
|
||||
activation,
|
||||
);
|
||||
const getPollFunctions = this.getExecutePollFunctions(
|
||||
workflowData,
|
||||
additionalData,
|
||||
mode,
|
||||
activation,
|
||||
);
|
||||
|
||||
// Add the workflows which have webhooks defined
|
||||
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode, activation);
|
||||
|
||||
if (workflowInstance.getTriggerNodes().length !== 0
|
||||
|| workflowInstance.getPollNodes().length !== 0) {
|
||||
await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions);
|
||||
Logger.verbose(`Successfully activated workflow "${workflowData.name}"`, { workflowId, workflowName: workflowData.name });
|
||||
if (
|
||||
workflowInstance.getTriggerNodes().length !== 0 ||
|
||||
workflowInstance.getPollNodes().length !== 0
|
||||
) {
|
||||
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) {
|
||||
|
@ -548,13 +746,15 @@ export class ActiveWorkflowRunner {
|
|||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async remove(workflowId: string): Promise<void> {
|
||||
|
||||
if (this.activeWorkflows !== null) {
|
||||
// Remove all the webhooks of the workflow
|
||||
try {
|
||||
await this.removeWorkflowWebhooks(workflowId);
|
||||
} catch (error) {
|
||||
console.error(`Could not remove webhooks of workflow "${workflowId}" because of error: "${error.message}"`);
|
||||
console.error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Could not remove webhooks of workflow "${workflowId}" because of error: "${error.message}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.activationErrors[workflowId] !== undefined) {
|
||||
|
@ -576,8 +776,6 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let workflowRunnerInstance: ActiveWorkflowRunner | undefined;
|
||||
|
||||
export function getInstance(): ActiveWorkflowRunner {
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
ICredentialTypes as ICredentialTypesInterface,
|
||||
} from 'n8n-workflow';
|
||||
import { ICredentialType, ICredentialTypes as ICredentialTypesInterface } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
CredentialsOverwrites,
|
||||
ICredentialsTypeData,
|
||||
} from './';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { CredentialsOverwrites, ICredentialsTypeData } from '.';
|
||||
|
||||
class CredentialTypesClass implements ICredentialTypesInterface {
|
||||
|
||||
credentialTypes: ICredentialsTypeData = {};
|
||||
|
||||
|
||||
async init(credentialTypes: ICredentialsTypeData): Promise<void> {
|
||||
this.credentialTypes = credentialTypes;
|
||||
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites().getAll();
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const credentialType of Object.keys(credentialsOverwrites)) {
|
||||
if (credentialTypes[credentialType] === undefined) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add which properties got overwritten that the Editor-UI knows
|
||||
// which properties it should hide
|
||||
credentialTypes[credentialType].__overwrittenProperties = Object.keys(credentialsOverwrites[credentialType]);
|
||||
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
|
||||
credentialTypes[credentialType].__overwrittenProperties = Object.keys(
|
||||
credentialsOverwrites[credentialType],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,10 +37,9 @@ class CredentialTypesClass implements ICredentialTypesInterface {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let credentialTypesInstance: CredentialTypesClass | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export function CredentialTypes(): CredentialTypesClass {
|
||||
if (credentialTypesInstance === undefined) {
|
||||
credentialTypesInstance = new CredentialTypesClass();
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import {
|
||||
Credentials,
|
||||
} from 'n8n-core';
|
||||
import { Credentials } from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
|
@ -17,29 +15,24 @@ import {
|
|||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ICredentialsDb,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { CredentialsOverwrites, CredentialTypes, Db, ICredentialsDb } from '.';
|
||||
|
||||
const mockNodeTypes: INodeTypes = {
|
||||
nodeTypes: {},
|
||||
init: async (nodeTypes?: INodeTypeData): Promise<void> => { },
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
init: async (nodeTypes?: INodeTypeData): Promise<void> => {},
|
||||
getAll: (): INodeType[] => {
|
||||
// Does not get used in Workflow so no need to return it
|
||||
return [];
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getByName: (nodeType: string): INodeType | undefined => {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
|
||||
/**
|
||||
* Returns the credentials instance
|
||||
*
|
||||
|
@ -49,23 +42,27 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
* @memberof CredentialsHelper
|
||||
*/
|
||||
async getCredentials(name: string, type: string): Promise<Credentials> {
|
||||
|
||||
const credentialsDb = await Db.collections.Credentials?.find({type});
|
||||
const credentialsDb = await Db.collections.Credentials?.find({ type });
|
||||
|
||||
if (credentialsDb === undefined || credentialsDb.length === 0) {
|
||||
throw new Error(`No credentials of type "${type}" exist.`);
|
||||
}
|
||||
|
||||
const credential = credentialsDb.find(credential => credential.name === name);
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const credential = credentialsDb.find((credential) => credential.name === name);
|
||||
|
||||
if (credential === undefined) {
|
||||
throw new Error(`No credentials with name "${name}" exist for type "${type}".`);
|
||||
}
|
||||
|
||||
return new Credentials(credential.name, credential.type, credential.nodesAccess, credential.data);
|
||||
return new Credentials(
|
||||
credential.name,
|
||||
credential.type,
|
||||
credential.nodesAccess,
|
||||
credential.data,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the properties of the credentials with the given name
|
||||
*
|
||||
|
@ -86,6 +83,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
}
|
||||
|
||||
const combineProperties = [] as INodeProperties[];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const credentialsTypeName of credentialTypeData.extends) {
|
||||
const mergeCredentialProperties = this.getCredentialsProperties(credentialsTypeName);
|
||||
NodeHelpers.mergeNodeProperties(combineProperties, mergeCredentialProperties);
|
||||
|
@ -97,7 +95,6 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
return combineProperties;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the decrypted credential data with applied overwrites
|
||||
*
|
||||
|
@ -107,7 +104,13 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
* @returns {ICredentialDataDecryptedObject}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
async getDecrypted(name: string, type: string, mode: WorkflowExecuteMode, raw?: boolean, expressionResolveValues?: ICredentialsExpressionResolveValues): Promise<ICredentialDataDecryptedObject> {
|
||||
async getDecrypted(
|
||||
name: string,
|
||||
type: string,
|
||||
mode: WorkflowExecuteMode,
|
||||
raw?: boolean,
|
||||
expressionResolveValues?: ICredentialsExpressionResolveValues,
|
||||
): Promise<ICredentialDataDecryptedObject> {
|
||||
const credentials = await this.getCredentials(name, type);
|
||||
|
||||
const decryptedDataOriginal = credentials.getData(this.encryptionKey);
|
||||
|
@ -116,10 +119,14 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
return decryptedDataOriginal;
|
||||
}
|
||||
|
||||
return this.applyDefaultsAndOverwrites(decryptedDataOriginal, type, mode, expressionResolveValues);
|
||||
return this.applyDefaultsAndOverwrites(
|
||||
decryptedDataOriginal,
|
||||
type,
|
||||
mode,
|
||||
expressionResolveValues,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies credential default data and overwrites
|
||||
*
|
||||
|
@ -128,11 +135,21 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
* @returns {ICredentialDataDecryptedObject}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
applyDefaultsAndOverwrites(decryptedDataOriginal: ICredentialDataDecryptedObject, type: string, mode: WorkflowExecuteMode, expressionResolveValues?: ICredentialsExpressionResolveValues): ICredentialDataDecryptedObject {
|
||||
applyDefaultsAndOverwrites(
|
||||
decryptedDataOriginal: ICredentialDataDecryptedObject,
|
||||
type: string,
|
||||
mode: WorkflowExecuteMode,
|
||||
expressionResolveValues?: ICredentialsExpressionResolveValues,
|
||||
): ICredentialDataDecryptedObject {
|
||||
const credentialsProperties = this.getCredentialsProperties(type);
|
||||
|
||||
// Add the default credential values
|
||||
let decryptedData = NodeHelpers.getNodeParameters(credentialsProperties, decryptedDataOriginal as INodeParameters, true, false) as ICredentialDataDecryptedObject;
|
||||
let decryptedData = NodeHelpers.getNodeParameters(
|
||||
credentialsProperties,
|
||||
decryptedDataOriginal as INodeParameters,
|
||||
true,
|
||||
false,
|
||||
) as ICredentialDataDecryptedObject;
|
||||
|
||||
if (decryptedDataOriginal.oauthTokenData !== undefined) {
|
||||
// The OAuth data gets removed as it is not defined specifically as a parameter
|
||||
|
@ -142,9 +159,26 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
|
||||
if (expressionResolveValues) {
|
||||
try {
|
||||
const workflow = new Workflow({ nodes: Object.values(expressionResolveValues.workflow.nodes), connections: expressionResolveValues.workflow.connectionsBySourceNode, active: false, nodeTypes: expressionResolveValues.workflow.nodeTypes });
|
||||
decryptedData = workflow.expression.getParameterValue(decryptedData as INodeParameters, expressionResolveValues.runExecutionData, expressionResolveValues.runIndex, expressionResolveValues.itemIndex, expressionResolveValues.node.name, expressionResolveValues.connectionInputData, mode, {}, false, decryptedData) as ICredentialDataDecryptedObject;
|
||||
const workflow = new Workflow({
|
||||
nodes: Object.values(expressionResolveValues.workflow.nodes),
|
||||
connections: expressionResolveValues.workflow.connectionsBySourceNode,
|
||||
active: false,
|
||||
nodeTypes: expressionResolveValues.workflow.nodeTypes,
|
||||
});
|
||||
decryptedData = workflow.expression.getParameterValue(
|
||||
decryptedData as INodeParameters,
|
||||
expressionResolveValues.runExecutionData,
|
||||
expressionResolveValues.runIndex,
|
||||
expressionResolveValues.itemIndex,
|
||||
expressionResolveValues.node.name,
|
||||
expressionResolveValues.connectionInputData,
|
||||
mode,
|
||||
{},
|
||||
false,
|
||||
decryptedData,
|
||||
) as ICredentialDataDecryptedObject;
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
e.message += ' [Error resolving credentials]';
|
||||
throw e;
|
||||
}
|
||||
|
@ -157,18 +191,30 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
parameters: {} as INodeParameters,
|
||||
} as INode;
|
||||
|
||||
const workflow = new Workflow({ nodes: [node!], connections: {}, active: false, nodeTypes: mockNodeTypes });
|
||||
const workflow = new Workflow({
|
||||
nodes: [node],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes: mockNodeTypes,
|
||||
});
|
||||
|
||||
// Resolve expressions if any are set
|
||||
decryptedData = workflow.expression.getComplexParameterValue(node!, decryptedData as INodeParameters, mode, {}, undefined, decryptedData) as ICredentialDataDecryptedObject;
|
||||
decryptedData = workflow.expression.getComplexParameterValue(
|
||||
node,
|
||||
decryptedData as INodeParameters,
|
||||
mode,
|
||||
{},
|
||||
undefined,
|
||||
decryptedData,
|
||||
) as ICredentialDataDecryptedObject;
|
||||
}
|
||||
|
||||
// Load and apply the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return credentialsOverwrites.applyOverwrite(type, decryptedData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates credentials in the database
|
||||
*
|
||||
|
@ -178,10 +224,15 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
async updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void> {
|
||||
async updateCredentials(
|
||||
name: string,
|
||||
type: string,
|
||||
data: ICredentialDataDecryptedObject,
|
||||
): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
const credentials = await this.getCredentials(name, type);
|
||||
|
||||
if (Db.collections!.Credentials === null) {
|
||||
if (Db.collections.Credentials === null) {
|
||||
// The first time executeWorkflow gets called the Database has
|
||||
// to get initialized first
|
||||
await Db.init();
|
||||
|
@ -201,7 +252,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
type,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Credentials!.update(findQuery, newCredentialsData);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
CredentialTypes,
|
||||
GenericHelpers,
|
||||
ICredentialsOverwrite,
|
||||
} from './';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { CredentialTypes, GenericHelpers, ICredentialsOverwrite } from '.';
|
||||
|
||||
class CredentialsOverwritesClass {
|
||||
|
||||
private credentialTypes = CredentialTypes();
|
||||
private overwriteData: ICredentialsOverwrite = {};
|
||||
private resolvedTypes: string[] = [];
|
||||
|
||||
private overwriteData: ICredentialsOverwrite = {};
|
||||
|
||||
private resolvedTypes: string[] = [];
|
||||
|
||||
async init(overwriteData?: ICredentialsOverwrite) {
|
||||
if (overwriteData !== undefined) {
|
||||
|
@ -24,9 +19,10 @@ class CredentialsOverwritesClass {
|
|||
return;
|
||||
}
|
||||
|
||||
const data = await GenericHelpers.getConfigValue('credentials.overwrite.data') as string;
|
||||
const data = (await GenericHelpers.getConfigValue('credentials.overwrite.data')) as string;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-shadow
|
||||
const overwriteData = JSON.parse(data);
|
||||
this.__setData(overwriteData);
|
||||
} catch (error) {
|
||||
|
@ -34,10 +30,10 @@ class CredentialsOverwritesClass {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
__setData(overwriteData: ICredentialsOverwrite) {
|
||||
this.overwriteData = overwriteData;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const credentialTypeData of this.credentialTypes.getAll()) {
|
||||
const type = credentialTypeData.name;
|
||||
|
||||
|
@ -49,29 +45,30 @@ class CredentialsOverwritesClass {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
applyOverwrite(type: string, data: ICredentialDataDecryptedObject) {
|
||||
|
||||
const overwrites = this.get(type);
|
||||
|
||||
if (overwrites === undefined) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const returnData = JSON.parse(JSON.stringify(data));
|
||||
// Overwrite only if there is currently no data set
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of Object.keys(overwrites)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if ([null, undefined, ''].includes(returnData[key])) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
returnData[key] = overwrites[key];
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
__getExtended(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
|
||||
if (this.resolvedTypes.includes(type)) {
|
||||
// Type got already resolved and can so returned directly
|
||||
return this.overwriteData[type];
|
||||
|
@ -89,6 +86,7 @@ class CredentialsOverwritesClass {
|
|||
}
|
||||
|
||||
const overwrites: ICredentialDataDecryptedObject = {};
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const credentialsTypeName of credentialTypeData.extends) {
|
||||
Object.assign(overwrites, this.__getExtended(credentialsTypeName));
|
||||
}
|
||||
|
@ -102,20 +100,18 @@ class CredentialsOverwritesClass {
|
|||
return overwrites;
|
||||
}
|
||||
|
||||
|
||||
get(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return this.overwriteData[type];
|
||||
}
|
||||
|
||||
|
||||
getAll(): ICredentialsOverwrite {
|
||||
return this.overwriteData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let credentialsOverwritesInstance: CredentialsOverwritesClass | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export function CredentialsOverwrites(): CredentialsOverwritesClass {
|
||||
if (credentialsOverwritesInstance === undefined) {
|
||||
credentialsOverwritesInstance = new CredentialsOverwritesClass();
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
import {
|
||||
DatabaseType,
|
||||
GenericHelpers,
|
||||
IDatabaseCollections,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ConnectionOptions,
|
||||
createConnection,
|
||||
getRepository,
|
||||
} from 'typeorm';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable no-case-declarations */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { ConnectionOptions, createConnection, getRepository } from 'typeorm';
|
||||
import { TlsOptions } from 'tls';
|
||||
import * as path from 'path';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { DatabaseType, GenericHelpers, IDatabaseCollections } from '.';
|
||||
|
||||
import * as config from '../config';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { entities } from './databases/entities';
|
||||
|
||||
export let collections: IDatabaseCollections = {
|
||||
import { postgresMigrations } from './databases/postgresdb/migrations';
|
||||
import { mysqlMigrations } from './databases/mysqldb/migrations';
|
||||
import { sqliteMigrations } from './databases/sqlite/migrations';
|
||||
|
||||
export const collections: IDatabaseCollections = {
|
||||
Credentials: null,
|
||||
Execution: null,
|
||||
Workflow: null,
|
||||
|
@ -28,14 +26,8 @@ export let collections: IDatabaseCollections = {
|
|||
Tag: null,
|
||||
};
|
||||
|
||||
import { postgresMigrations } from './databases/postgresdb/migrations';
|
||||
import { mysqlMigrations } from './databases/mysqldb/migrations';
|
||||
import { sqliteMigrations } from './databases/sqlite/migrations';
|
||||
|
||||
import * as path from 'path';
|
||||
|
||||
export async function init(): Promise<IDatabaseCollections> {
|
||||
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
||||
const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;
|
||||
const n8nFolder = UserSettings.getUserN8nFolderPath();
|
||||
|
||||
let connectionOptions: ConnectionOptions;
|
||||
|
@ -44,13 +36,17 @@ export async function init(): Promise<IDatabaseCollections> {
|
|||
|
||||
switch (dbType) {
|
||||
case 'postgresdb':
|
||||
const sslCa = await GenericHelpers.getConfigValue('database.postgresdb.ssl.ca') as string;
|
||||
const sslCert = await GenericHelpers.getConfigValue('database.postgresdb.ssl.cert') as string;
|
||||
const sslKey = await GenericHelpers.getConfigValue('database.postgresdb.ssl.key') as string;
|
||||
const sslRejectUnauthorized = await GenericHelpers.getConfigValue('database.postgresdb.ssl.rejectUnauthorized') as boolean;
|
||||
const sslCa = (await GenericHelpers.getConfigValue('database.postgresdb.ssl.ca')) as string;
|
||||
const sslCert = (await GenericHelpers.getConfigValue(
|
||||
'database.postgresdb.ssl.cert',
|
||||
)) as string;
|
||||
const sslKey = (await GenericHelpers.getConfigValue('database.postgresdb.ssl.key')) as string;
|
||||
const sslRejectUnauthorized = (await GenericHelpers.getConfigValue(
|
||||
'database.postgresdb.ssl.rejectUnauthorized',
|
||||
)) as boolean;
|
||||
|
||||
let ssl: TlsOptions | undefined = undefined;
|
||||
if (sslCa !== '' || sslCert !== '' || sslKey !== '' || sslRejectUnauthorized !== true) {
|
||||
let ssl: TlsOptions | undefined;
|
||||
if (sslCa !== '' || sslCert !== '' || sslKey !== '' || !sslRejectUnauthorized) {
|
||||
ssl = {
|
||||
ca: sslCa || undefined,
|
||||
cert: sslCert || undefined,
|
||||
|
@ -62,11 +58,11 @@ export async function init(): Promise<IDatabaseCollections> {
|
|||
connectionOptions = {
|
||||
type: 'postgres',
|
||||
entityPrefix,
|
||||
database: await GenericHelpers.getConfigValue('database.postgresdb.database') as string,
|
||||
host: await GenericHelpers.getConfigValue('database.postgresdb.host') as string,
|
||||
password: await GenericHelpers.getConfigValue('database.postgresdb.password') as string,
|
||||
port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number,
|
||||
username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string,
|
||||
database: (await GenericHelpers.getConfigValue('database.postgresdb.database')) as string,
|
||||
host: (await GenericHelpers.getConfigValue('database.postgresdb.host')) as string,
|
||||
password: (await GenericHelpers.getConfigValue('database.postgresdb.password')) as string,
|
||||
port: (await GenericHelpers.getConfigValue('database.postgresdb.port')) as number,
|
||||
username: (await GenericHelpers.getConfigValue('database.postgresdb.user')) as string,
|
||||
schema: config.get('database.postgresdb.schema'),
|
||||
migrations: postgresMigrations,
|
||||
migrationsRun: true,
|
||||
|
@ -80,12 +76,12 @@ export async function init(): Promise<IDatabaseCollections> {
|
|||
case 'mysqldb':
|
||||
connectionOptions = {
|
||||
type: dbType === 'mysqldb' ? 'mysql' : 'mariadb',
|
||||
database: await GenericHelpers.getConfigValue('database.mysqldb.database') as string,
|
||||
database: (await GenericHelpers.getConfigValue('database.mysqldb.database')) as string,
|
||||
entityPrefix,
|
||||
host: await GenericHelpers.getConfigValue('database.mysqldb.host') as string,
|
||||
password: await GenericHelpers.getConfigValue('database.mysqldb.password') as string,
|
||||
port: await GenericHelpers.getConfigValue('database.mysqldb.port') as number,
|
||||
username: await GenericHelpers.getConfigValue('database.mysqldb.user') as string,
|
||||
host: (await GenericHelpers.getConfigValue('database.mysqldb.host')) as string,
|
||||
password: (await GenericHelpers.getConfigValue('database.mysqldb.password')) as string,
|
||||
port: (await GenericHelpers.getConfigValue('database.mysqldb.port')) as number,
|
||||
username: (await GenericHelpers.getConfigValue('database.mysqldb.user')) as string,
|
||||
migrations: mysqlMigrations,
|
||||
migrationsRun: true,
|
||||
migrationsTableName: `${entityPrefix}migrations`,
|
||||
|
@ -122,8 +118,10 @@ export async function init(): Promise<IDatabaseCollections> {
|
|||
// n8n knows it has changed. Happens only on sqlite.
|
||||
let migrations = [];
|
||||
try {
|
||||
migrations = await connection.query(`SELECT id FROM ${entityPrefix}migrations where name = "MakeStoppedAtNullable1607431743769"`);
|
||||
} catch(error) {
|
||||
migrations = await connection.query(
|
||||
`SELECT id FROM ${entityPrefix}migrations where name = "MakeStoppedAtNullable1607431743769"`,
|
||||
);
|
||||
} catch (error) {
|
||||
// Migration table does not exist yet - it will be created after migrations run for the first time.
|
||||
}
|
||||
|
||||
|
@ -133,6 +131,7 @@ export async function init(): Promise<IDatabaseCollections> {
|
|||
transaction: 'none',
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (migrations.length === 0) {
|
||||
await connection.close();
|
||||
connection = await createConnection(connectionOptions);
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
import {
|
||||
Db,
|
||||
IExternalHooksClass,
|
||||
IExternalHooksFileData,
|
||||
IExternalHooksFunctions,
|
||||
} from './';
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { Db, IExternalHooksClass, IExternalHooksFileData, IExternalHooksFunctions } from '.';
|
||||
|
||||
import * as config from '../config';
|
||||
|
||||
|
||||
class ExternalHooksClass implements IExternalHooksClass {
|
||||
|
||||
externalHooks: {
|
||||
[key: string]: Array<() => {}>
|
||||
[key: string]: Array<() => {}>;
|
||||
} = {};
|
||||
|
||||
initDidRun = false;
|
||||
|
||||
|
||||
async init(): Promise<void> {
|
||||
if (this.initDidRun === true) {
|
||||
if (this.initDidRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -26,7 +23,6 @@ class ExternalHooksClass implements IExternalHooksClass {
|
|||
this.initDidRun = true;
|
||||
}
|
||||
|
||||
|
||||
async reload(externalHooks?: IExternalHooksFileData) {
|
||||
this.externalHooks = {};
|
||||
|
||||
|
@ -37,7 +33,6 @@ class ExternalHooksClass implements IExternalHooksClass {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
async loadHooksFiles(reload = false) {
|
||||
const externalHookFiles = config.get('externalHookFiles').split(':');
|
||||
|
||||
|
@ -46,21 +41,22 @@ class ExternalHooksClass implements IExternalHooksClass {
|
|||
hookFilePath = hookFilePath.trim();
|
||||
if (hookFilePath !== '') {
|
||||
try {
|
||||
|
||||
if (reload === true) {
|
||||
if (reload) {
|
||||
delete require.cache[require.resolve(hookFilePath)];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-dynamic-require
|
||||
// eslint-disable-next-line global-require
|
||||
const hookFile = require(hookFilePath) as IExternalHooksFileData;
|
||||
this.loadHooks(hookFile);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
throw new Error(`Problem loading external hook file "${hookFilePath}": ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
loadHooks(hookFileData: IExternalHooksFileData) {
|
||||
for (const resource of Object.keys(hookFileData)) {
|
||||
for (const operation of Object.keys(hookFileData[resource])) {
|
||||
|
@ -71,13 +67,17 @@ class ExternalHooksClass implements IExternalHooksClass {
|
|||
this.externalHooks[hookString] = [];
|
||||
}
|
||||
|
||||
this.externalHooks[hookString].push.apply(this.externalHooks[hookString], hookFileData[resource][operation]);
|
||||
// eslint-disable-next-line prefer-spread
|
||||
this.externalHooks[hookString].push.apply(
|
||||
this.externalHooks[hookString],
|
||||
hookFileData[resource][operation],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async run(hookName: string, hookParameters?: any[]): Promise<void> { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async run(hookName: string, hookParameters?: any[]): Promise<void> {
|
||||
const externalHookFunctions: IExternalHooksFunctions = {
|
||||
dbCollections: Db.collections,
|
||||
};
|
||||
|
@ -86,22 +86,20 @@ class ExternalHooksClass implements IExternalHooksClass {
|
|||
return;
|
||||
}
|
||||
|
||||
for(const externalHookFunction of this.externalHooks[hookName]) {
|
||||
for (const externalHookFunction of this.externalHooks[hookName]) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await externalHookFunction.apply(externalHookFunctions, hookParameters);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exists(hookName: string): boolean {
|
||||
return !!this.externalHooks[hookName];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
let externalHooksInstance: ExternalHooksClass | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export function ExternalHooks(): ExternalHooksClass {
|
||||
if (externalHooksInstance === undefined) {
|
||||
externalHooksInstance = new ExternalHooksClass();
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import * as config from '../config';
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import * as express from 'express';
|
||||
import { join as pathJoin } from 'path';
|
||||
import { readFile as fsReadFile } from 'fs/promises';
|
||||
import { readFileSync as fsReadFileSync } from 'fs';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
import * as config from '../config';
|
||||
|
||||
import { IPackageVersions } from './';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { IPackageVersions } from '.';
|
||||
|
||||
let versionCache: IPackageVersions | undefined;
|
||||
|
||||
|
@ -16,18 +22,17 @@ let versionCache: IPackageVersions | undefined;
|
|||
* @returns {string}
|
||||
*/
|
||||
export function getBaseUrl(): string {
|
||||
const protocol = config.get('protocol') as string;
|
||||
const host = config.get('host') as string;
|
||||
const port = config.get('port') as number;
|
||||
const path = config.get('path') as string;
|
||||
const protocol = config.get('protocol');
|
||||
const host = config.get('host');
|
||||
const port = config.get('port');
|
||||
const path = config.get('path');
|
||||
|
||||
if (protocol === 'http' && port === 80 || protocol === 'https' && port === 443) {
|
||||
if ((protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)) {
|
||||
return `${protocol}://${host}${path}`;
|
||||
}
|
||||
return `${protocol}://${host}:${port}${path}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the session id if one is set
|
||||
*
|
||||
|
@ -39,7 +44,6 @@ export function getSessionId(req: express.Request): string | undefined {
|
|||
return req.headers.sessionid as string | undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns information which version of the packages are installed
|
||||
*
|
||||
|
@ -51,10 +55,12 @@ export async function getVersions(): Promise<IPackageVersions> {
|
|||
return versionCache;
|
||||
}
|
||||
|
||||
const packageFile = await fsReadFile(pathJoin(__dirname, '../../package.json'), 'utf8') as string;
|
||||
const packageFile = await fsReadFile(pathJoin(__dirname, '../../package.json'), 'utf8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const packageData = JSON.parse(packageFile);
|
||||
|
||||
versionCache = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
cli: packageData.version,
|
||||
};
|
||||
|
||||
|
@ -71,9 +77,11 @@ export async function getVersions(): Promise<IPackageVersions> {
|
|||
function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDataObject {
|
||||
const configKeyParts = configKey.split('.');
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of configKeyParts) {
|
||||
if (configSchema[key] === undefined) {
|
||||
throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
} else if ((configSchema[key]! as IDataObject)._cvtProperties === undefined) {
|
||||
configSchema = configSchema[key] as IDataObject;
|
||||
} else {
|
||||
|
@ -90,7 +98,9 @@ function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDat
|
|||
* @param {string} configKey The key of the config data to get
|
||||
* @returns {(Promise<string | boolean | number | undefined>)}
|
||||
*/
|
||||
export async function getConfigValue(configKey: string): Promise<string | boolean | number | undefined> {
|
||||
export async function getConfigValue(
|
||||
configKey: string,
|
||||
): Promise<string | boolean | number | undefined> {
|
||||
// Get the environment variable
|
||||
const configSchema = config.getSchema();
|
||||
// @ts-ignore
|
||||
|
@ -102,7 +112,7 @@ export async function getConfigValue(configKey: string): Promise<string | boolea
|
|||
}
|
||||
|
||||
// Check if special file enviroment variable exists
|
||||
const fileEnvironmentVariable = process.env[currentSchema.env + '_FILE'];
|
||||
const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`];
|
||||
if (fileEnvironmentVariable === undefined) {
|
||||
// Does not exist, so return value from config
|
||||
return config.get(configKey);
|
||||
|
@ -110,7 +120,7 @@ export async function getConfigValue(configKey: string): Promise<string | boolea
|
|||
|
||||
let data;
|
||||
try {
|
||||
data = await fsReadFile(fileEnvironmentVariable, 'utf8') as string;
|
||||
data = await fsReadFile(fileEnvironmentVariable, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`);
|
||||
|
@ -141,7 +151,7 @@ export function getConfigValueSync(configKey: string): string | boolean | number
|
|||
}
|
||||
|
||||
// Check if special file enviroment variable exists
|
||||
const fileEnvironmentVariable = process.env[currentSchema.env + '_FILE'];
|
||||
const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`];
|
||||
if (fileEnvironmentVariable === undefined) {
|
||||
// Does not exist, so return value from config
|
||||
return config.get(configKey);
|
||||
|
@ -149,7 +159,7 @@ export function getConfigValueSync(configKey: string): string | boolean | number
|
|||
|
||||
let data;
|
||||
try {
|
||||
data = fsReadFileSync(fileEnvironmentVariable, 'utf8') as string;
|
||||
data = fsReadFileSync(fileEnvironmentVariable, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/* eslint-disable import/no-cycle */
|
||||
import {
|
||||
ExecutionError,
|
||||
ICredentialDataDecryptedObject,
|
||||
|
@ -10,15 +12,15 @@ import {
|
|||
IRunExecutionData,
|
||||
ITaskData,
|
||||
IWorkflowBase as IWorkflowBaseWorkflow,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
IWorkflowCredentials,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
IDeferredPromise, WorkflowExecute,
|
||||
} from 'n8n-core';
|
||||
import { IDeferredPromise, WorkflowExecute } from 'n8n-core';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
|
@ -85,7 +87,7 @@ export interface ITagDb {
|
|||
}
|
||||
|
||||
export type UsageCount = {
|
||||
usageCount: number
|
||||
usageCount: number;
|
||||
};
|
||||
|
||||
export type ITagWithCountDb = ITagDb & UsageCount;
|
||||
|
@ -214,7 +216,6 @@ export interface IExecutionsSummary {
|
|||
workflowName?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IExecutionsCurrentSummary {
|
||||
id: string;
|
||||
retryOf?: string;
|
||||
|
@ -223,7 +224,6 @@ export interface IExecutionsCurrentSummary {
|
|||
workflowId: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IExecutionDeleteFilter {
|
||||
deleteBefore?: Date;
|
||||
filters?: IDataObject;
|
||||
|
@ -240,22 +240,33 @@ export interface IExecutingWorkflowData {
|
|||
|
||||
export interface IExternalHooks {
|
||||
credentials?: {
|
||||
create?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsEncrypted): Promise<void>; }>
|
||||
delete?: Array<{ (this: IExternalHooksFunctions, credentialId: string): Promise<void>; }>
|
||||
update?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsDb): Promise<void>; }>
|
||||
create?: Array<{
|
||||
(this: IExternalHooksFunctions, credentialsData: ICredentialsEncrypted): Promise<void>;
|
||||
}>;
|
||||
delete?: Array<{ (this: IExternalHooksFunctions, credentialId: string): Promise<void> }>;
|
||||
update?: Array<{
|
||||
(this: IExternalHooksFunctions, credentialsData: ICredentialsDb): Promise<void>;
|
||||
}>;
|
||||
};
|
||||
workflow?: {
|
||||
activate?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void>; }>
|
||||
create?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowBase): Promise<void>; }>
|
||||
delete?: Array<{ (this: IExternalHooksFunctions, workflowId: string): Promise<void>; }>
|
||||
execute?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb, mode: WorkflowExecuteMode): Promise<void>; }>
|
||||
update?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void>; }>
|
||||
activate?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void> }>;
|
||||
create?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowBase): Promise<void> }>;
|
||||
delete?: Array<{ (this: IExternalHooksFunctions, workflowId: string): Promise<void> }>;
|
||||
execute?: Array<{
|
||||
(
|
||||
this: IExternalHooksFunctions,
|
||||
workflowData: IWorkflowDb,
|
||||
mode: WorkflowExecuteMode,
|
||||
): Promise<void>;
|
||||
}>;
|
||||
update?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void> }>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IExternalHooksFileData {
|
||||
[key: string]: {
|
||||
[key: string]: Array<(...args: any[]) => Promise<void>>; //tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: Array<(...args: any[]) => Promise<void>>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -265,7 +276,8 @@ export interface IExternalHooksFunctions {
|
|||
|
||||
export interface IExternalHooksClass {
|
||||
init(): Promise<void>;
|
||||
run(hookName: string, hookParameters?: any[]): Promise<void>; // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
run(hookName: string, hookParameters?: any[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IN8nConfig {
|
||||
|
@ -295,12 +307,14 @@ export interface IN8nConfigEndpoints {
|
|||
webhookTest: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/export
|
||||
export interface IN8nConfigExecutions {
|
||||
saveDataOnError: SaveExecutionDataType;
|
||||
saveDataOnSuccess: SaveExecutionDataType;
|
||||
saveDataManualExecutions: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/export
|
||||
export interface IN8nConfigExecutions {
|
||||
saveDataOnError: SaveExecutionDataType;
|
||||
saveDataOnSuccess: SaveExecutionDataType;
|
||||
|
@ -409,13 +423,11 @@ export interface IPushDataNodeExecuteAfter {
|
|||
nodeName: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IPushDataNodeExecuteBefore {
|
||||
executionId: string;
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IPushDataTestWebhook {
|
||||
executionId: string;
|
||||
workflowId: string;
|
||||
|
@ -432,7 +444,6 @@ export interface IResponseCallbackData {
|
|||
responseCode?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface ITransferNodeTypes {
|
||||
[key: string]: {
|
||||
className: string;
|
||||
|
@ -440,7 +451,6 @@ export interface ITransferNodeTypes {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IWorkflowErrorData {
|
||||
[key: string]: IDataObject | string | number | ExecutionError;
|
||||
execution: {
|
||||
|
@ -457,7 +467,8 @@ export interface IWorkflowErrorData {
|
|||
|
||||
export interface IProcessMessageDataHook {
|
||||
hook: string;
|
||||
parameters: any[]; // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parameters: any[];
|
||||
}
|
||||
|
||||
export interface IWorkflowExecutionDataProcess {
|
||||
|
@ -471,7 +482,6 @@ export interface IWorkflowExecutionDataProcess {
|
|||
workflowData: IWorkflowBase;
|
||||
}
|
||||
|
||||
|
||||
export interface IWorkflowExecutionDataProcessWithExecution extends IWorkflowExecutionDataProcess {
|
||||
credentialsOverwrite: ICredentialsOverwrite;
|
||||
credentialsTypeData: ICredentialsTypeData;
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import {
|
||||
CUSTOM_EXTENSION_ENV,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { CUSTOM_EXTENSION_ENV, UserSettings } from 'n8n-core';
|
||||
import {
|
||||
CodexData,
|
||||
ICredentialType,
|
||||
|
@ -11,12 +18,6 @@ import {
|
|||
LoggerProxy,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as config from '../config';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
|
||||
import {
|
||||
access as fsAccess,
|
||||
readdir as fsReaddir,
|
||||
|
@ -25,18 +26,20 @@ import {
|
|||
} from 'fs/promises';
|
||||
import * as glob from 'fast-glob';
|
||||
import * as path from 'path';
|
||||
import { getLogger } from './Logger';
|
||||
import * as config from '../config';
|
||||
|
||||
const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
|
||||
|
||||
|
||||
class LoadNodesAndCredentialsClass {
|
||||
nodeTypes: INodeTypeData = {};
|
||||
|
||||
credentialTypes: {
|
||||
[key: string]: ICredentialType
|
||||
[key: string]: ICredentialType;
|
||||
} = {};
|
||||
|
||||
excludeNodes: string[] | undefined = undefined;
|
||||
|
||||
includeNodes: string[] | undefined = undefined;
|
||||
|
||||
nodeModulesPath = '';
|
||||
|
@ -64,6 +67,7 @@ class LoadNodesAndCredentialsClass {
|
|||
break;
|
||||
} catch (error) {
|
||||
// Folder does not exist so get next one
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +94,9 @@ class LoadNodesAndCredentialsClass {
|
|||
|
||||
// Add folders from special environment variable
|
||||
if (process.env[CUSTOM_EXTENSION_ENV] !== undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const customExtensionFolders = process.env[CUSTOM_EXTENSION_ENV]!.split(';');
|
||||
// eslint-disable-next-line prefer-spread
|
||||
customDirectories.push.apply(customDirectories, customExtensionFolders);
|
||||
}
|
||||
|
||||
|
@ -99,7 +105,6 @@ class LoadNodesAndCredentialsClass {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the names of the packages which could
|
||||
* contain n8n nodes
|
||||
|
@ -120,9 +125,11 @@ class LoadNodesAndCredentialsClass {
|
|||
if (!(await fsStat(nodeModulesPath)).isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
if (isN8nNodesPackage) { results.push(`${relativePath}${file}`); }
|
||||
if (isN8nNodesPackage) {
|
||||
results.push(`${relativePath}${file}`);
|
||||
}
|
||||
if (isNpmScopedPackage) {
|
||||
results.push(...await getN8nNodePackagesRecursive(`${relativePath}${file}/`));
|
||||
results.push(...(await getN8nNodePackagesRecursive(`${relativePath}${file}/`)));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
|
@ -138,6 +145,7 @@ class LoadNodesAndCredentialsClass {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadCredentialsFromFile(credentialName: string, filePath: string): Promise<void> {
|
||||
// eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
|
||||
const tempModule = require(filePath);
|
||||
|
||||
let tempCredential: ICredentialType;
|
||||
|
@ -145,7 +153,9 @@ class LoadNodesAndCredentialsClass {
|
|||
tempCredential = new tempModule[credentialName]() as ICredentialType;
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
throw new Error(`Class with name "${credentialName}" could not be found. Please check if the class is named correctly!`);
|
||||
throw new Error(
|
||||
`Class with name "${credentialName}" could not be found. Please check if the class is named correctly!`,
|
||||
);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
@ -154,7 +164,6 @@ class LoadNodesAndCredentialsClass {
|
|||
this.credentialTypes[tempCredential.name] = tempCredential;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a node from a file
|
||||
*
|
||||
|
@ -167,26 +176,34 @@ class LoadNodesAndCredentialsClass {
|
|||
let tempNode: INodeType;
|
||||
let fullNodeName: string;
|
||||
|
||||
// eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
|
||||
const tempModule = require(filePath);
|
||||
try {
|
||||
tempNode = new tempModule[nodeName]() as INodeType;
|
||||
this.addCodex({ node: tempNode, filePath, isCustom: packageName === 'CUSTOM' });
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Error loading node "${nodeName}" from: "${filePath}"`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
fullNodeName = packageName + '.' + tempNode.description.name;
|
||||
// eslint-disable-next-line prefer-const
|
||||
fullNodeName = `${packageName}.${tempNode.description.name}`;
|
||||
tempNode.description.name = fullNodeName;
|
||||
|
||||
if (tempNode.description.icon !== undefined &&
|
||||
tempNode.description.icon.startsWith('file:')) {
|
||||
if (tempNode.description.icon !== undefined && tempNode.description.icon.startsWith('file:')) {
|
||||
// If a file icon gets used add the full path
|
||||
tempNode.description.icon = 'file:' + path.join(path.dirname(filePath), tempNode.description.icon.substr(5));
|
||||
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 });
|
||||
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)) {
|
||||
|
@ -212,7 +229,9 @@ class LoadNodesAndCredentialsClass {
|
|||
* @returns {CodexData}
|
||||
*/
|
||||
getCodex(filePath: string): CodexData {
|
||||
// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires
|
||||
const { categories, subcategories, alias } = require(`${filePath}on`); // .js to .json
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return {
|
||||
...(categories && { categories }),
|
||||
...(subcategories && { subcategories }),
|
||||
|
@ -230,11 +249,7 @@ class LoadNodesAndCredentialsClass {
|
|||
* @param obj.isCustom Whether the node is custom
|
||||
* @returns {void}
|
||||
*/
|
||||
addCodex({ node, filePath, isCustom }: {
|
||||
node: INodeType;
|
||||
filePath: string;
|
||||
isCustom: boolean;
|
||||
}) {
|
||||
addCodex({ node, filePath, isCustom }: { node: INodeType; filePath: string; isCustom: boolean }) {
|
||||
try {
|
||||
const codex = this.getCodex(filePath);
|
||||
|
||||
|
@ -246,6 +261,7 @@ class LoadNodesAndCredentialsClass {
|
|||
|
||||
node.description.codex = codex;
|
||||
} catch (_) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
this.logger.debug(`No codex available for: ${filePath.split('/').pop()}`);
|
||||
|
||||
if (isCustom) {
|
||||
|
@ -264,7 +280,7 @@ class LoadNodesAndCredentialsClass {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadDataFromDirectory(setPackageName: string, directory: string): Promise<void> {
|
||||
const files = await glob(path.join(directory, '**/*\.@(node|credentials)\.js'));
|
||||
const files = await glob(path.join(directory, '**/*.@(node|credentials).js'));
|
||||
|
||||
let fileName: string;
|
||||
let type: string;
|
||||
|
@ -283,7 +299,6 @@ class LoadNodesAndCredentialsClass {
|
|||
await Promise.all(loadPromises);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads nodes and credentials from the package with the given name
|
||||
*
|
||||
|
@ -301,10 +316,12 @@ class LoadNodesAndCredentialsClass {
|
|||
return;
|
||||
}
|
||||
|
||||
let tempPath: string, filePath: string;
|
||||
let tempPath: string;
|
||||
let filePath: string;
|
||||
|
||||
// Read all node types
|
||||
let fileName: string, type: string;
|
||||
let fileName: string;
|
||||
let type: string;
|
||||
if (packageFile.n8n.hasOwnProperty('nodes') && Array.isArray(packageFile.n8n.nodes)) {
|
||||
for (filePath of packageFile.n8n.nodes) {
|
||||
tempPath = path.join(packagePath, filePath);
|
||||
|
@ -314,18 +331,21 @@ class LoadNodesAndCredentialsClass {
|
|||
}
|
||||
|
||||
// Read all credential types
|
||||
if (packageFile.n8n.hasOwnProperty('credentials') && Array.isArray(packageFile.n8n.credentials)) {
|
||||
if (
|
||||
packageFile.n8n.hasOwnProperty('credentials') &&
|
||||
Array.isArray(packageFile.n8n.credentials)
|
||||
) {
|
||||
for (filePath of packageFile.n8n.credentials) {
|
||||
tempPath = path.join(packagePath, filePath);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
[fileName, type] = path.parse(filePath).name.split('.');
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.loadCredentialsFromFile(fileName, tempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let packagesInformationInstance: LoadNodesAndCredentialsClass | undefined;
|
||||
|
||||
export function LoadNodesAndCredentials(): LoadNodesAndCredentialsClass {
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import config = require('../config');
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import * as winston from 'winston';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILogger,
|
||||
LogTypes,
|
||||
} from 'n8n-workflow';
|
||||
import { IDataObject, ILogger, LogTypes } from 'n8n-workflow';
|
||||
|
||||
import * as callsites from 'callsites';
|
||||
import { basename } from 'path';
|
||||
import config = require('../config');
|
||||
|
||||
class Logger implements ILogger {
|
||||
private logger: winston.Logger;
|
||||
|
||||
constructor() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const level = config.get('logs.level');
|
||||
const output = (config.get('logs.output') as string).split(',').map(output => output.trim());
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const output = (config.get('logs.output') as string).split(',').map((output) => output.trim());
|
||||
|
||||
this.logger = winston.createLogger({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
level,
|
||||
});
|
||||
|
||||
|
@ -28,18 +28,22 @@ class Logger implements ILogger {
|
|||
winston.format.metadata(),
|
||||
winston.format.timestamp(),
|
||||
winston.format.colorize({ all: true }),
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
winston.format.printf(({ level, message, timestamp, metadata }) => {
|
||||
return `${timestamp} | ${level.padEnd(18)} | ${message}` + (Object.keys(metadata).length ? ` ${JSON.stringify(metadata)}` : '');
|
||||
}) as winston.Logform.Format
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
return `${timestamp} | ${level.padEnd(18)} | ${message}${
|
||||
Object.keys(metadata).length ? ` ${JSON.stringify(metadata)}` : ''
|
||||
}`;
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
format = winston.format.printf(({ message }) => message) as winston.Logform.Format;
|
||||
format = winston.format.printf(({ message }) => message);
|
||||
}
|
||||
|
||||
this.logger.add(
|
||||
new winston.transports.Console({
|
||||
format,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -47,15 +51,15 @@ class Logger implements ILogger {
|
|||
const fileLogFormat = winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.metadata(),
|
||||
winston.format.json()
|
||||
winston.format.json(),
|
||||
);
|
||||
this.logger.add(
|
||||
new winston.transports.File({
|
||||
filename: config.get('logs.file.location'),
|
||||
format: fileLogFormat,
|
||||
maxsize: config.get('logs.file.fileSizeMax') as number * 1048576, // config * 1mb
|
||||
maxsize: (config.get('logs.file.fileSizeMax') as number) * 1048576, // config * 1mb
|
||||
maxFiles: config.get('logs.file.fileCountMax'),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -70,13 +74,14 @@ class Logger implements ILogger {
|
|||
// We are in runtime, so it means we are looking at compiled js files
|
||||
const logDetails = {} as IDataObject;
|
||||
if (callsite[2] !== undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
logDetails.file = basename(callsite[2].getFileName() || '');
|
||||
const functionName = callsite[2].getFunctionName();
|
||||
if (functionName) {
|
||||
logDetails.function = functionName;
|
||||
}
|
||||
}
|
||||
this.logger.log(type, message, {...meta, ...logDetails});
|
||||
this.logger.log(type, message, { ...meta, ...logDetails });
|
||||
}
|
||||
|
||||
// Convenience methods below
|
||||
|
@ -100,11 +105,11 @@ class Logger implements ILogger {
|
|||
warn(message: string, meta: object = {}) {
|
||||
this.log('warn', message, meta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let activeLoggerInstance: Logger | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getLogger() {
|
||||
if (activeLoggerInstance === undefined) {
|
||||
activeLoggerInstance = new Logger();
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
import {
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
NodeHelpers,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { INodeType, INodeTypeData, INodeTypes, NodeHelpers } from 'n8n-workflow';
|
||||
|
||||
class NodeTypesClass implements INodeTypes {
|
||||
|
||||
nodeTypes: INodeTypeData = {};
|
||||
|
||||
|
||||
async init(nodeTypes: INodeTypeData): Promise<void> {
|
||||
// Some nodeTypes need to get special parameters applied like the
|
||||
// polling nodes the polling times
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const nodeTypeData of Object.values(nodeTypes)) {
|
||||
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type);
|
||||
|
||||
if (applyParameters.length) {
|
||||
nodeTypeData.type.description.properties.unshift.apply(nodeTypeData.type.description.properties, applyParameters);
|
||||
// eslint-disable-next-line prefer-spread
|
||||
nodeTypeData.type.description.properties.unshift.apply(
|
||||
nodeTypeData.type.description.properties,
|
||||
applyParameters,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.nodeTypes = nodeTypes;
|
||||
|
@ -36,10 +33,9 @@ class NodeTypesClass implements INodeTypes {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export function NodeTypes(): NodeTypesClass {
|
||||
if (nodeTypesInstance === undefined) {
|
||||
nodeTypesInstance = new NodeTypesClass();
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
// @ts-ignore
|
||||
import * as sseChannel from 'sse-channel';
|
||||
import * as express from 'express';
|
||||
|
||||
import {
|
||||
IPushData,
|
||||
IPushDataType,
|
||||
} from '.';
|
||||
|
||||
import {
|
||||
LoggerProxy as Logger,
|
||||
} from 'n8n-workflow';
|
||||
import { LoggerProxy as Logger } from 'n8n-workflow';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { IPushData, IPushDataType } from '.';
|
||||
|
||||
export class Push {
|
||||
private channel: sseChannel;
|
||||
|
||||
private connections: {
|
||||
[key: string]: express.Response;
|
||||
} = {};
|
||||
|
||||
|
||||
constructor() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, new-cap
|
||||
this.channel = new sseChannel({
|
||||
cors: {
|
||||
// Allow access also from frontend when developing
|
||||
|
@ -26,6 +24,7 @@ export class Push {
|
|||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
this.channel.on('disconnect', (channel: string, res: express.Response) => {
|
||||
if (res.req !== undefined) {
|
||||
Logger.debug(`Remove editor-UI session`, { sessionId: res.req.query.sessionId });
|
||||
|
@ -34,7 +33,6 @@ export class Push {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new push connection
|
||||
*
|
||||
|
@ -43,6 +41,7 @@ export class Push {
|
|||
* @param {express.Response} res The response
|
||||
* @memberof Push
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
add(sessionId: string, req: express.Request, res: express.Response) {
|
||||
Logger.debug(`Add editor-UI session`, { sessionId });
|
||||
|
||||
|
@ -57,7 +56,6 @@ export class Push {
|
|||
this.channel.addClient(req, res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends data to the client which is connected via a specific session
|
||||
*
|
||||
|
@ -67,9 +65,8 @@ export class Push {
|
|||
* @memberof Push
|
||||
*/
|
||||
|
||||
|
||||
|
||||
send(type: IPushDataType, data: any, sessionId?: string) { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
send(type: IPushDataType, data: any, sessionId?: string) {
|
||||
if (sessionId !== undefined && this.connections[sessionId] === undefined) {
|
||||
Logger.error(`The session "${sessionId}" is not registred.`, { sessionId });
|
||||
return;
|
||||
|
@ -79,6 +76,7 @@ export class Push {
|
|||
|
||||
const sendData: IPushData = {
|
||||
type,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data,
|
||||
};
|
||||
|
||||
|
@ -89,7 +87,6 @@ export class Push {
|
|||
// Send only to a specific client
|
||||
this.channel.send(JSON.stringify(sendData), [this.connections[sessionId]]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as Bull from 'bull';
|
||||
import * as config from '../config';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { IBullJobData } from './Interfaces';
|
||||
|
||||
export class Queue {
|
||||
|
@ -18,15 +19,15 @@ export class Queue {
|
|||
}
|
||||
|
||||
async add(jobData: IBullJobData, jobOptions: object): Promise<Bull.Job> {
|
||||
return await this.jobQueue.add(jobData,jobOptions);
|
||||
return this.jobQueue.add(jobData, jobOptions);
|
||||
}
|
||||
|
||||
async getJob(jobId: Bull.JobId): Promise<Bull.Job | null> {
|
||||
return await this.jobQueue.getJob(jobId);
|
||||
return this.jobQueue.getJob(jobId);
|
||||
}
|
||||
|
||||
async getJobs(jobTypes: Bull.JobStatus[]): Promise<Bull.Job[]> {
|
||||
return await this.jobQueue.getJobs(jobTypes);
|
||||
return this.jobQueue.getJobs(jobTypes);
|
||||
}
|
||||
|
||||
getBullObjectInstance(): Bull.Queue {
|
||||
|
@ -43,7 +44,7 @@ export class Queue {
|
|||
// Job is already running so tell it to stop
|
||||
await job.progress(-1);
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
// Job did not get started yet so remove from queue
|
||||
try {
|
||||
await job.remove();
|
||||
|
@ -51,7 +52,7 @@ export class Queue {
|
|||
} catch (e) {
|
||||
await job.progress(-1);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Request, Response } from 'express';
|
||||
import { parse, stringify } from 'flatted';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
IExecutionDb,
|
||||
IExecutionFlatted,
|
||||
IExecutionFlattedDb,
|
||||
IExecutionResponse,
|
||||
IWorkflowDb,
|
||||
} from './';
|
||||
} from '.';
|
||||
|
||||
/**
|
||||
* Special Error which allows to return also an error code and http status code
|
||||
|
@ -17,7 +23,6 @@ import {
|
|||
* @extends {Error}
|
||||
*/
|
||||
export class ResponseError extends Error {
|
||||
|
||||
// The HTTP status code of response
|
||||
httpStatusCode?: number;
|
||||
|
||||
|
@ -35,7 +40,7 @@ export class ResponseError extends Error {
|
|||
* @param {string} [hint] The error hint to provide a context (webhook related)
|
||||
* @memberof ResponseError
|
||||
*/
|
||||
constructor(message: string, errorCode?: number, httpStatusCode?: number, hint?:string) {
|
||||
constructor(message: string, errorCode?: number, httpStatusCode?: number, hint?: string) {
|
||||
super(message);
|
||||
this.name = 'ResponseError';
|
||||
|
||||
|
@ -51,21 +56,23 @@ export class ResponseError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function basicAuthAuthorizationError(resp: Response, realm: string, message?: string) {
|
||||
resp.statusCode = 401;
|
||||
resp.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
|
||||
resp.json({code: resp.statusCode, message});
|
||||
resp.json({ code: resp.statusCode, message });
|
||||
}
|
||||
|
||||
export function jwtAuthAuthorizationError(resp: Response, message?: string) {
|
||||
resp.statusCode = 403;
|
||||
resp.json({code: resp.statusCode, message});
|
||||
resp.json({ code: resp.statusCode, message });
|
||||
}
|
||||
|
||||
|
||||
export function sendSuccessResponse(res: Response, data: any, raw?: boolean, responseCode?: number) { // tslint:disable-line:no-any
|
||||
export function sendSuccessResponse(
|
||||
res: Response,
|
||||
data: any,
|
||||
raw?: boolean,
|
||||
responseCode?: number,
|
||||
) {
|
||||
if (responseCode !== undefined) {
|
||||
res.status(responseCode);
|
||||
}
|
||||
|
@ -83,7 +90,6 @@ export function sendSuccessResponse(res: Response, data: any, raw?: boolean, res
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
export function sendErrorResponse(res: Response, error: ResponseError) {
|
||||
let httpStatusCode = 500;
|
||||
if (error.httpStatusCode) {
|
||||
|
@ -122,7 +128,6 @@ export function sendErrorResponse(res: Response, error: ResponseError) {
|
|||
res.status(httpStatusCode).json(response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A helper function which does not just allow to return Promises it also makes sure that
|
||||
* all the responses have the same format
|
||||
|
@ -133,8 +138,7 @@ export function sendErrorResponse(res: Response, error: ResponseError) {
|
|||
* @returns
|
||||
*/
|
||||
|
||||
export function send(processFunction: (req: Request, res: Response) => Promise<any>) { // tslint:disable-line:no-any
|
||||
|
||||
export function send(processFunction: (req: Request, res: Response) => Promise<any>) {
|
||||
return async (req: Request, res: Response) => {
|
||||
try {
|
||||
const data = await processFunction(req, res);
|
||||
|
@ -148,7 +152,6 @@ export function send(processFunction: (req: Request, res: Response) => Promise<a
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flattens the Execution data.
|
||||
* As it contains a lot of references which normally would be saved as duplicate data
|
||||
|
@ -160,33 +163,34 @@ export function send(processFunction: (req: Request, res: Response) => Promise<a
|
|||
*/
|
||||
export function flattenExecutionData(fullExecutionData: IExecutionDb): IExecutionFlatted {
|
||||
// Flatten the data
|
||||
const returnData: IExecutionFlatted = Object.assign({}, {
|
||||
const returnData: IExecutionFlatted = {
|
||||
data: stringify(fullExecutionData.data),
|
||||
mode: fullExecutionData.mode,
|
||||
// @ts-ignore
|
||||
waitTill: fullExecutionData.waitTill,
|
||||
startedAt: fullExecutionData.startedAt,
|
||||
stoppedAt: fullExecutionData.stoppedAt,
|
||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
||||
workflowId: fullExecutionData.workflowId,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
workflowData: fullExecutionData.workflowData!,
|
||||
});
|
||||
};
|
||||
|
||||
if (fullExecutionData.id !== undefined) {
|
||||
returnData.id = fullExecutionData.id!.toString();
|
||||
returnData.id = fullExecutionData.id.toString();
|
||||
}
|
||||
|
||||
if (fullExecutionData.retryOf !== undefined) {
|
||||
returnData.retryOf = fullExecutionData.retryOf!.toString();
|
||||
returnData.retryOf = fullExecutionData.retryOf.toString();
|
||||
}
|
||||
|
||||
if (fullExecutionData.retrySuccessId !== undefined) {
|
||||
returnData.retrySuccessId = fullExecutionData.retrySuccessId!.toString();
|
||||
returnData.retrySuccessId = fullExecutionData.retrySuccessId.toString();
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unflattens the Execution data.
|
||||
*
|
||||
|
@ -195,8 +199,7 @@ export function flattenExecutionData(fullExecutionData: IExecutionDb): IExecutio
|
|||
* @returns {IExecutionResponse}
|
||||
*/
|
||||
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb): IExecutionResponse {
|
||||
|
||||
const returnData: IExecutionResponse = Object.assign({}, {
|
||||
const returnData: IExecutionResponse = {
|
||||
id: fullExecutionData.id.toString(),
|
||||
workflowData: fullExecutionData.workflowData as IWorkflowDb,
|
||||
data: parse(fullExecutionData.data),
|
||||
|
@ -206,7 +209,7 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb):
|
|||
stoppedAt: fullExecutionData.stoppedAt,
|
||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
||||
workflowId: fullExecutionData.workflowId,
|
||||
});
|
||||
};
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,14 @@
|
|||
import { getConnection } from "typeorm";
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { getConnection } from 'typeorm';
|
||||
import { validate } from 'class-validator';
|
||||
|
||||
import {
|
||||
ResponseHelper,
|
||||
} from ".";
|
||||
import { ResponseHelper } from '.';
|
||||
|
||||
import {
|
||||
TagEntity,
|
||||
} from "./databases/entities/TagEntity";
|
||||
|
||||
import {
|
||||
ITagWithCountDb,
|
||||
} from "./Interfaces";
|
||||
import { TagEntity } from './databases/entities/TagEntity';
|
||||
|
||||
import { ITagWithCountDb } from './Interfaces';
|
||||
|
||||
// ----------------------------------
|
||||
// utils
|
||||
|
@ -29,7 +25,7 @@ export function sortByRequestOrder(tagsDb: TagEntity[], tagIds: string[]) {
|
|||
return acc;
|
||||
}, {} as { [key: string]: TagEntity });
|
||||
|
||||
return tagIds.map(tagId => tagMap[tagId]);
|
||||
return tagIds.map((tagId) => tagMap[tagId]);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
@ -43,6 +39,7 @@ export async function validateTag(newTag: TagEntity) {
|
|||
const errors = await validate(newTag);
|
||||
|
||||
if (errors.length) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const validationErrorMessage = Object.values(errors[0].constraints!)[0];
|
||||
throw new ResponseHelper.ResponseError(validationErrorMessage, undefined, 400);
|
||||
}
|
||||
|
@ -64,21 +61,28 @@ export function throwDuplicateEntryError(error: Error) {
|
|||
/**
|
||||
* Retrieve all tags and the number of workflows each tag is related to.
|
||||
*/
|
||||
export function getTagsWithCountDb(tablePrefix: string): Promise<ITagWithCountDb[]> {
|
||||
export async function getTagsWithCountDb(tablePrefix: string): Promise<ITagWithCountDb[]> {
|
||||
return getConnection()
|
||||
.createQueryBuilder()
|
||||
.select(`${tablePrefix}tag_entity.id`, 'id')
|
||||
.addSelect(`${tablePrefix}tag_entity.name`, 'name')
|
||||
.addSelect(`COUNT(${tablePrefix}workflows_tags.workflowId)`, 'usageCount')
|
||||
.from(`${tablePrefix}tag_entity`, 'tag_entity')
|
||||
.leftJoin(`${tablePrefix}workflows_tags`, 'workflows_tags', `${tablePrefix}workflows_tags.tagId = tag_entity.id`)
|
||||
.leftJoin(
|
||||
`${tablePrefix}workflows_tags`,
|
||||
'workflows_tags',
|
||||
`${tablePrefix}workflows_tags.tagId = tag_entity.id`,
|
||||
)
|
||||
.groupBy(`${tablePrefix}tag_entity.id`)
|
||||
.getRawMany()
|
||||
.then(tagsWithCount => {
|
||||
tagsWithCount.forEach(tag => {
|
||||
.then((tagsWithCount) => {
|
||||
tagsWithCount.forEach((tag) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
tag.id = tag.id.toString();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
tag.usageCount = Number(tag.usageCount);
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return tagsWithCount;
|
||||
});
|
||||
}
|
||||
|
@ -90,19 +94,19 @@ export function getTagsWithCountDb(tablePrefix: string): Promise<ITagWithCountDb
|
|||
/**
|
||||
* Relate a workflow to one or more tags.
|
||||
*/
|
||||
export function createRelations(workflowId: string, tagIds: string[], tablePrefix: string) {
|
||||
export async function createRelations(workflowId: string, tagIds: string[], tablePrefix: string) {
|
||||
return getConnection()
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${tablePrefix}workflows_tags`)
|
||||
.values(tagIds.map(tagId => ({ workflowId, tagId })))
|
||||
.values(tagIds.map((tagId) => ({ workflowId, tagId })))
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all tags for a workflow during a tag update operation.
|
||||
*/
|
||||
export function removeRelations(workflowId: string, tablePrefix: string) {
|
||||
export async function removeRelations(workflowId: string, tablePrefix: string) {
|
||||
return getConnection()
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
/* eslint-disable consistent-return */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable no-param-reassign */
|
||||
import * as express from 'express';
|
||||
|
||||
import {
|
||||
IResponseCallbackData,
|
||||
IWorkflowDb,
|
||||
Push,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
ActiveWebhooks,
|
||||
} from 'n8n-core';
|
||||
import { ActiveWebhooks } from 'n8n-core';
|
||||
|
||||
import {
|
||||
IWebhookData,
|
||||
|
@ -20,28 +13,28 @@ import {
|
|||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { IResponseCallbackData, IWorkflowDb, Push, ResponseHelper, WebhookHelpers } from '.';
|
||||
|
||||
const WEBHOOK_TEST_UNREGISTERED_HINT = `Click the 'Execute workflow' button on the canvas, then try again. (In test mode, the webhook only works for one call after you click this button)`;
|
||||
|
||||
export class TestWebhooks {
|
||||
|
||||
private testWebhookData: {
|
||||
[key: string]: {
|
||||
sessionId?: string;
|
||||
timeout: NodeJS.Timeout,
|
||||
timeout: NodeJS.Timeout;
|
||||
workflowData: IWorkflowDb;
|
||||
workflow: Workflow;
|
||||
};
|
||||
} = {};
|
||||
private activeWebhooks: ActiveWebhooks | null = null;
|
||||
|
||||
private activeWebhooks: ActiveWebhooks | null = null;
|
||||
|
||||
constructor() {
|
||||
this.activeWebhooks = new ActiveWebhooks();
|
||||
this.activeWebhooks.testWebhooks = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes a test-webhook and returns the data. It also makes sure that the
|
||||
* data gets additionally send to the UI. After the request got handled it
|
||||
|
@ -54,7 +47,12 @@ export class TestWebhooks {
|
|||
* @returns {Promise<object>}
|
||||
* @memberof TestWebhooks
|
||||
*/
|
||||
async callTestWebhook(httpMethod: WebhookHttpMethod, path: string, request: express.Request, response: express.Response): Promise<IResponseCallbackData> {
|
||||
async callTestWebhook(
|
||||
httpMethod: WebhookHttpMethod,
|
||||
path: string,
|
||||
request: express.Request,
|
||||
response: express.Response,
|
||||
): Promise<IResponseCallbackData> {
|
||||
// Reset request parameters
|
||||
request.params = {};
|
||||
|
||||
|
@ -69,10 +67,16 @@ export class TestWebhooks {
|
|||
if (webhookData === undefined) {
|
||||
const pathElements = path.split('/');
|
||||
const webhookId = pathElements.shift();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
webhookData = this.activeWebhooks!.get(httpMethod, pathElements.join('/'), webhookId);
|
||||
if (webhookData === undefined) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_TEST_UNREGISTERED_HINT);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`The requested webhook "${httpMethod} ${path}" is not registered.`,
|
||||
404,
|
||||
404,
|
||||
WEBHOOK_TEST_UNREGISTERED_HINT,
|
||||
);
|
||||
}
|
||||
|
||||
path = webhookData.path;
|
||||
|
@ -85,15 +89,24 @@ export class TestWebhooks {
|
|||
});
|
||||
}
|
||||
|
||||
const webhookKey = this.activeWebhooks!.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId) + `|${webhookData.workflowId}`;
|
||||
const webhookKey = `${this.activeWebhooks!.getWebhookKey(
|
||||
webhookData.httpMethod,
|
||||
webhookData.path,
|
||||
webhookData.webhookId,
|
||||
)}|${webhookData.workflowId}`;
|
||||
|
||||
// TODO: Clean that duplication up one day and improve code generally
|
||||
if (this.testWebhookData[webhookKey] === undefined) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_TEST_UNREGISTERED_HINT);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`The requested webhook "${httpMethod} ${path}" is not registered.`,
|
||||
404,
|
||||
404,
|
||||
WEBHOOK_TEST_UNREGISTERED_HINT,
|
||||
);
|
||||
}
|
||||
|
||||
const workflow = this.testWebhookData[webhookKey].workflow;
|
||||
const { workflow } = this.testWebhookData[webhookKey];
|
||||
|
||||
// Get the node which has the webhook defined to know where to start from and to
|
||||
// get additional data
|
||||
|
@ -102,15 +115,28 @@ export class TestWebhooks {
|
|||
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const executionMode = 'manual';
|
||||
const executionId = await WebhookHelpers.executeWebhook(workflow, webhookData!, this.testWebhookData[webhookKey].workflowData, workflowStartNode, executionMode, this.testWebhookData[webhookKey].sessionId, undefined, undefined, request, response, (error: Error | null, data: IResponseCallbackData) => {
|
||||
const executionId = await WebhookHelpers.executeWebhook(
|
||||
workflow,
|
||||
webhookData!,
|
||||
this.testWebhookData[webhookKey].workflowData,
|
||||
workflowStartNode,
|
||||
executionMode,
|
||||
this.testWebhookData[webhookKey].sessionId,
|
||||
undefined,
|
||||
undefined,
|
||||
request,
|
||||
response,
|
||||
(error: Error | null, data: IResponseCallbackData) => {
|
||||
if (error !== null) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (executionId === undefined) {
|
||||
// The workflow did not run as the request was probably setup related
|
||||
|
@ -122,9 +148,12 @@ export class TestWebhooks {
|
|||
// Inform editor-ui that webhook got received
|
||||
if (this.testWebhookData[webhookKey].sessionId !== undefined) {
|
||||
const pushInstance = Push.getInstance();
|
||||
pushInstance.send('testWebhookReceived', { workflowId: webhookData!.workflowId, executionId }, this.testWebhookData[webhookKey].sessionId!);
|
||||
pushInstance.send(
|
||||
'testWebhookReceived',
|
||||
{ workflowId: webhookData!.workflowId, executionId },
|
||||
this.testWebhookData[webhookKey].sessionId,
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Delete webhook also if an error is thrown
|
||||
}
|
||||
|
@ -132,6 +161,7 @@ export class TestWebhooks {
|
|||
// Remove the webhook
|
||||
clearTimeout(this.testWebhookData[webhookKey].timeout);
|
||||
delete this.testWebhookData[webhookKey];
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.activeWebhooks!.removeWorkflow(workflow);
|
||||
});
|
||||
}
|
||||
|
@ -140,18 +170,22 @@ export class TestWebhooks {
|
|||
* Gets all request methods associated with a single test webhook
|
||||
* @param path webhook path
|
||||
*/
|
||||
async getWebhookMethods(path : string) : Promise<string[]> {
|
||||
async getWebhookMethods(path: string): Promise<string[]> {
|
||||
const webhookMethods: string[] = this.activeWebhooks!.getWebhookMethods(path);
|
||||
|
||||
if (webhookMethods === undefined) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${path}" is not registered.`, 404, 404, WEBHOOK_TEST_UNREGISTERED_HINT);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`The requested webhook "${path}" is not registered.`,
|
||||
404,
|
||||
404,
|
||||
WEBHOOK_TEST_UNREGISTERED_HINT,
|
||||
);
|
||||
}
|
||||
|
||||
return webhookMethods;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if it has to wait for webhook data to execute the workflow. If yes it waits
|
||||
* for it and resolves with the result of the workflow if not it simply resolves
|
||||
|
@ -162,9 +196,22 @@ export class TestWebhooks {
|
|||
* @returns {(Promise<IExecutionDb | undefined>)}
|
||||
* @memberof TestWebhooks
|
||||
*/
|
||||
async needsWebhookData(workflowData: IWorkflowDb, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, sessionId?: string, destinationNode?: string): Promise<boolean> {
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, destinationNode, true);
|
||||
if (!webhooks.find(webhook => webhook.webhookDescription.restartWebhook !== true)) {
|
||||
async needsWebhookData(
|
||||
workflowData: IWorkflowDb,
|
||||
workflow: Workflow,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
sessionId?: string,
|
||||
destinationNode?: string,
|
||||
): Promise<boolean> {
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(
|
||||
workflow,
|
||||
additionalData,
|
||||
destinationNode,
|
||||
true,
|
||||
);
|
||||
if (!webhooks.find((webhook) => webhook.webhookDescription.restartWebhook !== true)) {
|
||||
// No webhooks found to start a workflow
|
||||
return false;
|
||||
}
|
||||
|
@ -180,8 +227,13 @@ export class TestWebhooks {
|
|||
|
||||
let key: string;
|
||||
const activatedKey: string[] = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const webhookData of webhooks) {
|
||||
key = this.activeWebhooks!.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId) + `|${workflowData.id}`;
|
||||
key = `${this.activeWebhooks!.getWebhookKey(
|
||||
webhookData.httpMethod,
|
||||
webhookData.path,
|
||||
webhookData.webhookId,
|
||||
)}|${workflowData.id}`;
|
||||
|
||||
activatedKey.push(key);
|
||||
|
||||
|
@ -193,9 +245,11 @@ export class TestWebhooks {
|
|||
};
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.activeWebhooks!.add(workflow, webhookData, mode, activation);
|
||||
} catch (error) {
|
||||
activatedKey.forEach(deleteKey => delete this.testWebhookData[deleteKey] );
|
||||
activatedKey.forEach((deleteKey) => delete this.testWebhookData[deleteKey]);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.activeWebhooks!.removeWorkflow(workflow);
|
||||
throw error;
|
||||
}
|
||||
|
@ -204,7 +258,6 @@ export class TestWebhooks {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a test webhook of the workflow with the given id
|
||||
*
|
||||
|
@ -214,10 +267,12 @@ export class TestWebhooks {
|
|||
*/
|
||||
cancelTestWebhook(workflowId: string): boolean {
|
||||
let foundWebhook = false;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
||||
const webhookData = this.testWebhookData[webhookKey];
|
||||
|
||||
if (webhookData.workflowData.id.toString() !== workflowId) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -227,19 +282,24 @@ export class TestWebhooks {
|
|||
if (this.testWebhookData[webhookKey].sessionId !== undefined) {
|
||||
try {
|
||||
const pushInstance = Push.getInstance();
|
||||
pushInstance.send('testWebhookDeleted', { workflowId }, this.testWebhookData[webhookKey].sessionId!);
|
||||
pushInstance.send(
|
||||
'testWebhookDeleted',
|
||||
{ workflowId },
|
||||
this.testWebhookData[webhookKey].sessionId,
|
||||
);
|
||||
} catch (error) {
|
||||
// Could not inform editor, probably is not connected anymore. So sipmly go on.
|
||||
}
|
||||
}
|
||||
|
||||
const workflow = this.testWebhookData[webhookKey].workflow;
|
||||
const { workflow } = this.testWebhookData[webhookKey];
|
||||
|
||||
// Remove the webhook
|
||||
delete this.testWebhookData[webhookKey];
|
||||
|
||||
if (foundWebhook === false) {
|
||||
if (!foundWebhook) {
|
||||
// As it removes all webhooks of the workflow execute only once
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.activeWebhooks!.removeWorkflow(workflow);
|
||||
}
|
||||
|
||||
|
@ -249,7 +309,6 @@ export class TestWebhooks {
|
|||
return foundWebhook;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all the currently active test webhooks
|
||||
*/
|
||||
|
@ -260,6 +319,7 @@ export class TestWebhooks {
|
|||
|
||||
let workflow: Workflow;
|
||||
const workflows: Workflow[] = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
||||
workflow = this.testWebhookData[webhookKey].workflow;
|
||||
workflows.push(workflow);
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
/* eslint-disable import/no-cycle */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { IRun, LoggerProxy as Logger, WorkflowOperationError } from 'n8n-workflow';
|
||||
|
||||
import { FindManyOptions, LessThanOrEqual, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { DateUtils } from 'typeorm/util/DateUtils';
|
||||
import {
|
||||
ActiveExecutions,
|
||||
DatabaseType,
|
||||
|
@ -7,38 +20,23 @@ import {
|
|||
IExecutionsStopData,
|
||||
IWorkflowExecutionDataProcess,
|
||||
ResponseHelper,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowRunner,
|
||||
} from '.';
|
||||
|
||||
import {
|
||||
IRun,
|
||||
LoggerProxy as Logger,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
FindManyOptions,
|
||||
LessThanOrEqual,
|
||||
ObjectLiteral,
|
||||
} from 'typeorm';
|
||||
|
||||
import { DateUtils } from 'typeorm/util/DateUtils';
|
||||
|
||||
|
||||
export class WaitTrackerClass {
|
||||
activeExecutionsInstance: ActiveExecutions.ActiveExecutions;
|
||||
|
||||
private waitingExecutions: {
|
||||
[key: string]: {
|
||||
executionId: string,
|
||||
timer: NodeJS.Timeout,
|
||||
executionId: string;
|
||||
timer: NodeJS.Timeout;
|
||||
};
|
||||
} = {};
|
||||
|
||||
mainTimer: NodeJS.Timeout;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||
|
||||
|
@ -50,7 +48,7 @@ export class WaitTrackerClass {
|
|||
this.getwaitingExecutions();
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async getwaitingExecutions() {
|
||||
Logger.debug('Wait tracker querying database for waiting executions');
|
||||
// Find all the executions which should be triggered in the next 70 seconds
|
||||
|
@ -63,11 +61,13 @@ export class WaitTrackerClass {
|
|||
waitTill: 'ASC',
|
||||
},
|
||||
};
|
||||
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
||||
const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;
|
||||
if (dbType === 'sqlite') {
|
||||
// This is needed because of issue in TypeORM <> SQLite:
|
||||
// https://github.com/typeorm/typeorm/issues/2286
|
||||
(findQuery.where! as ObjectLiteral).waitTill = LessThanOrEqual(DateUtils.mixedDateToUtcDatetimeString(new Date(Date.now() + 70000)));
|
||||
(findQuery.where! as ObjectLiteral).waitTill = LessThanOrEqual(
|
||||
DateUtils.mixedDateToUtcDatetimeString(new Date(Date.now() + 70000)),
|
||||
);
|
||||
}
|
||||
|
||||
const executions = await Db.collections.Execution!.find(findQuery);
|
||||
|
@ -76,10 +76,13 @@ export class WaitTrackerClass {
|
|||
return;
|
||||
}
|
||||
|
||||
const executionIds = executions.map(execution => execution.id.toString()).join(', ');
|
||||
Logger.debug(`Wait tracker found ${executions.length} executions. Setting timer for IDs: ${executionIds}`);
|
||||
const executionIds = executions.map((execution) => execution.id.toString()).join(', ');
|
||||
Logger.debug(
|
||||
`Wait tracker found ${executions.length} executions. Setting timer for IDs: ${executionIds}`,
|
||||
);
|
||||
|
||||
// Add timers for each waiting execution that they get started at the correct time
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const execution of executions) {
|
||||
const executionId = execution.id.toString();
|
||||
if (this.waitingExecutions[executionId] === undefined) {
|
||||
|
@ -94,7 +97,6 @@ export class WaitTrackerClass {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
async stopExecution(executionId: string): Promise<IExecutionsStopData> {
|
||||
if (this.waitingExecutions[executionId] !== undefined) {
|
||||
// The waiting execution was already sheduled to execute.
|
||||
|
@ -124,7 +126,10 @@ export class WaitTrackerClass {
|
|||
fullExecutionData.stoppedAt = new Date();
|
||||
fullExecutionData.waitTill = undefined;
|
||||
|
||||
await Db.collections.Execution!.update(executionId, ResponseHelper.flattenExecutionData(fullExecutionData));
|
||||
await Db.collections.Execution!.update(
|
||||
executionId,
|
||||
ResponseHelper.flattenExecutionData(fullExecutionData),
|
||||
);
|
||||
|
||||
return {
|
||||
mode: fullExecutionData.mode,
|
||||
|
@ -134,9 +139,8 @@ export class WaitTrackerClass {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
startExecution(executionId: string) {
|
||||
Logger.debug(`Wait tracker resuming execution ${executionId}`, {executionId});
|
||||
Logger.debug(`Wait tracker resuming execution ${executionId}`, { executionId });
|
||||
delete this.waitingExecutions[executionId];
|
||||
|
||||
(async () => {
|
||||
|
@ -149,7 +153,7 @@ export class WaitTrackerClass {
|
|||
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(fullExecutionDataFlatted);
|
||||
|
||||
if (fullExecutionData.finished === true) {
|
||||
if (fullExecutionData.finished) {
|
||||
throw new Error('The execution did succeed and can so not be started again.');
|
||||
}
|
||||
|
||||
|
@ -163,13 +167,14 @@ export class WaitTrackerClass {
|
|||
const workflowRunner = new WorkflowRunner();
|
||||
await workflowRunner.run(data, false, false, executionId);
|
||||
})().catch((error) => {
|
||||
Logger.error(`There was a problem starting the waiting execution with id "${executionId}": "${error.message}"`, { executionId });
|
||||
Logger.error(
|
||||
`There was a problem starting the waiting execution with id "${executionId}": "${error.message}"`,
|
||||
{ executionId },
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let waitTrackerInstance: WaitTrackerClass | undefined;
|
||||
|
||||
export function WaitTracker(): WaitTrackerClass {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/* eslint-disable import/no-cycle */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable no-param-reassign */
|
||||
import {
|
||||
INode,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
IRunExecutionData,
|
||||
NodeHelpers,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
LoggerProxy as Logger,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as express from 'express';
|
||||
|
||||
import {
|
||||
Db,
|
||||
IExecutionResponse,
|
||||
|
@ -6,26 +22,18 @@ import {
|
|||
NodeTypes,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
} from '.';
|
||||
|
||||
import {
|
||||
INode,
|
||||
IRunExecutionData,
|
||||
NodeHelpers,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as express from 'express';
|
||||
import {
|
||||
LoggerProxy as Logger,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class WaitingWebhooks {
|
||||
|
||||
async executeWebhook(httpMethod: WebhookHttpMethod, fullPath: string, req: express.Request, res: express.Response): Promise<IResponseCallbackData> {
|
||||
async executeWebhook(
|
||||
httpMethod: WebhookHttpMethod,
|
||||
fullPath: string,
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<IResponseCallbackData> {
|
||||
Logger.debug(`Received waiting-webhoook "${httpMethod}" for path "${fullPath}"`);
|
||||
|
||||
// Reset request parameters
|
||||
|
@ -44,47 +52,77 @@ export class WaitingWebhooks {
|
|||
const execution = await Db.collections.Execution?.findOne(executionId);
|
||||
|
||||
if (execution === undefined) {
|
||||
throw new ResponseHelper.ResponseError(`The execution "${executionId} does not exist.`, 404, 404);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`The execution "${executionId} does not exist.`,
|
||||
404,
|
||||
404,
|
||||
);
|
||||
}
|
||||
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(execution);
|
||||
|
||||
if (fullExecutionData.finished === true || fullExecutionData.data.resultData.error) {
|
||||
throw new ResponseHelper.ResponseError(`The execution "${executionId} has finished already.`, 409, 409);
|
||||
if (fullExecutionData.finished || fullExecutionData.data.resultData.error) {
|
||||
throw new ResponseHelper.ResponseError(
|
||||
`The execution "${executionId} has finished already.`,
|
||||
409,
|
||||
409,
|
||||
);
|
||||
}
|
||||
|
||||
return this.startExecution(httpMethod, path, fullExecutionData, req, res);
|
||||
}
|
||||
|
||||
|
||||
async startExecution(httpMethod: WebhookHttpMethod, path: string, fullExecutionData: IExecutionResponse, req: express.Request, res: express.Response): Promise<IResponseCallbackData> {
|
||||
async startExecution(
|
||||
httpMethod: WebhookHttpMethod,
|
||||
path: string,
|
||||
fullExecutionData: IExecutionResponse,
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<IResponseCallbackData> {
|
||||
const executionId = fullExecutionData.id;
|
||||
|
||||
if (fullExecutionData.finished === true) {
|
||||
if (fullExecutionData.finished) {
|
||||
throw new Error('The execution did succeed and can so not be started again.');
|
||||
}
|
||||
|
||||
const lastNodeExecuted = fullExecutionData!.data.resultData.lastNodeExecuted as string;
|
||||
const lastNodeExecuted = fullExecutionData.data.resultData.lastNodeExecuted as string;
|
||||
|
||||
// Set the node as disabled so that the data does not get executed again as it would result
|
||||
// in starting the wait all over again
|
||||
fullExecutionData!.data.executionData!.nodeExecutionStack[0].node.disabled = true;
|
||||
fullExecutionData.data.executionData!.nodeExecutionStack[0].node.disabled = true;
|
||||
|
||||
// Remove waitTill information else the execution would stop
|
||||
fullExecutionData!.data.waitTill = undefined;
|
||||
fullExecutionData.data.waitTill = undefined;
|
||||
|
||||
// Remove the data of the node execution again else it will display the node as executed twice
|
||||
fullExecutionData!.data.resultData.runData[lastNodeExecuted].pop();
|
||||
fullExecutionData.data.resultData.runData[lastNodeExecuted].pop();
|
||||
|
||||
const workflowData = fullExecutionData.workflowData;
|
||||
const { workflowData } = fullExecutionData;
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
const workflow = new Workflow({ id: workflowData.id!.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: workflowData.id!.toString(),
|
||||
name: workflowData.name,
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: workflowData.active,
|
||||
nodeTypes,
|
||||
staticData: workflowData.staticData,
|
||||
settings: workflowData.settings,
|
||||
});
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
|
||||
const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(lastNodeExecuted) as INode, additionalData).filter((webhook) => {
|
||||
return (webhook.httpMethod === httpMethod && webhook.path === path && webhook.webhookDescription.restartWebhook === true);
|
||||
const webhookData = NodeHelpers.getNodeWebhooks(
|
||||
workflow,
|
||||
workflow.getNode(lastNodeExecuted) as INode,
|
||||
additionalData,
|
||||
).filter((webhook) => {
|
||||
return (
|
||||
webhook.httpMethod === httpMethod &&
|
||||
webhook.path === path &&
|
||||
webhook.webhookDescription.restartWebhook === true
|
||||
);
|
||||
})[0];
|
||||
|
||||
if (webhookData === undefined) {
|
||||
|
@ -100,18 +138,30 @@ export class WaitingWebhooks {
|
|||
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
|
||||
}
|
||||
|
||||
const runExecutionData = fullExecutionData.data as IRunExecutionData;
|
||||
const runExecutionData = fullExecutionData.data;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const executionMode = 'webhook';
|
||||
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData as IWorkflowDb, workflowStartNode, executionMode, undefined, runExecutionData, fullExecutionData.id, req, res, (error: Error | null, data: object) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
WebhookHelpers.executeWebhook(
|
||||
workflow,
|
||||
webhookData,
|
||||
workflowData as IWorkflowDb,
|
||||
workflowStartNode,
|
||||
executionMode,
|
||||
undefined,
|
||||
runExecutionData,
|
||||
fullExecutionData.id,
|
||||
req,
|
||||
res,
|
||||
// eslint-disable-next-line consistent-return
|
||||
(error: Error | null, data: object) => {
|
||||
if (error !== null) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(data);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable id-denylist */
|
||||
/* eslint-disable prefer-spread */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import * as express from 'express';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { get } from 'lodash';
|
||||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
GenericHelpers,
|
||||
IExecutionDb,
|
||||
IResponseCallbackData,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
ResponseHelper,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
NodeExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
import { BINARY_ENCODING, NodeExecuteFunctions } from 'n8n-core';
|
||||
|
||||
import {
|
||||
IBinaryKeyData,
|
||||
|
@ -35,7 +32,21 @@ import {
|
|||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ActiveExecutions,
|
||||
GenericHelpers,
|
||||
IExecutionDb,
|
||||
IResponseCallbackData,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
ResponseHelper,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
} from '.';
|
||||
|
||||
const activeExecutions = ActiveExecutions.getInstance();
|
||||
|
||||
|
@ -47,7 +58,12 @@ const activeExecutions = ActiveExecutions.getInstance();
|
|||
* @param {Workflow} workflow
|
||||
* @returns {IWebhookData[]}
|
||||
*/
|
||||
export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, destinationNode?: string, ignoreRestartWehbooks = false): IWebhookData[] {
|
||||
export function getWorkflowWebhooks(
|
||||
workflow: Workflow,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
destinationNode?: string,
|
||||
ignoreRestartWehbooks = false,
|
||||
): IWebhookData[] {
|
||||
// Check all the nodes in the workflow if they have webhooks
|
||||
|
||||
const returnData: IWebhookData[] = [];
|
||||
|
@ -63,9 +79,13 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
|
|||
if (parentNodes !== undefined && !parentNodes.includes(node.name)) {
|
||||
// If parentNodes are given check only them if they have webhooks
|
||||
// and no other ones
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
returnData.push.apply(returnData, NodeHelpers.getNodeWebhooks(workflow, node, additionalData, ignoreRestartWehbooks));
|
||||
returnData.push.apply(
|
||||
returnData,
|
||||
NodeHelpers.getNodeWebhooks(workflow, node, additionalData, ignoreRestartWehbooks),
|
||||
);
|
||||
}
|
||||
|
||||
return returnData;
|
||||
|
@ -91,8 +111,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Executes a webhook
|
||||
*
|
||||
* @export
|
||||
|
@ -106,7 +125,19 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
* @param {((error: Error | null, data: IResponseCallbackData) => void)} responseCallback
|
||||
* @returns {(Promise<string | undefined>)}
|
||||
*/
|
||||
export async function executeWebhook(workflow: Workflow, webhookData: IWebhookData, workflowData: IWorkflowDb, workflowStartNode: INode, executionMode: WorkflowExecuteMode, sessionId: string | undefined, runExecutionData: IRunExecutionData | undefined, executionId: string | undefined, req: express.Request, res: express.Response, responseCallback: (error: Error | null, data: IResponseCallbackData) => void): Promise<string | undefined> {
|
||||
export async function executeWebhook(
|
||||
workflow: Workflow,
|
||||
webhookData: IWebhookData,
|
||||
workflowData: IWorkflowDb,
|
||||
workflowStartNode: INode,
|
||||
executionMode: WorkflowExecuteMode,
|
||||
sessionId: string | undefined,
|
||||
runExecutionData: IRunExecutionData | undefined,
|
||||
executionId: string | undefined,
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
responseCallback: (error: Error | null, data: IResponseCallbackData) => void,
|
||||
): Promise<string | undefined> {
|
||||
// Get the nodeType to know which responseMode is set
|
||||
const nodeType = workflow.nodeTypes.getByName(workflowStartNode.type);
|
||||
if (nodeType === undefined) {
|
||||
|
@ -120,8 +151,20 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
};
|
||||
|
||||
// Get the responseMode
|
||||
const responseMode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], executionMode, additionalKeys, 'onReceived');
|
||||
const responseCode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], executionMode, additionalKeys, 200) as number;
|
||||
const responseMode = workflow.expression.getSimpleParameterValue(
|
||||
workflowStartNode,
|
||||
webhookData.webhookDescription.responseMode,
|
||||
executionMode,
|
||||
additionalKeys,
|
||||
'onReceived',
|
||||
);
|
||||
const responseCode = workflow.expression.getSimpleParameterValue(
|
||||
workflowStartNode,
|
||||
webhookData.webhookDescription.responseCode,
|
||||
executionMode,
|
||||
additionalKeys,
|
||||
200,
|
||||
) as number;
|
||||
|
||||
if (!['onReceived', 'lastNode'].includes(responseMode as string)) {
|
||||
// If the mode is not known we error. Is probably best like that instead of using
|
||||
|
@ -147,7 +190,13 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
let webhookResultData: IWebhookResponseData;
|
||||
|
||||
try {
|
||||
webhookResultData = await workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode);
|
||||
webhookResultData = await workflow.runWebhook(
|
||||
webhookData,
|
||||
workflowStartNode,
|
||||
additionalData,
|
||||
NodeExecuteFunctions,
|
||||
executionMode,
|
||||
);
|
||||
} catch (err) {
|
||||
// Send error response to webhook caller
|
||||
const errorMessage = 'Workflow Webhook Error: Workflow could not be started!';
|
||||
|
@ -171,7 +220,7 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
noWebhookResponse: true,
|
||||
// Add empty data that it at least tries to "execute" the webhook
|
||||
// which then so gets the chance to throw the error.
|
||||
workflowData: [[{json: {}}]],
|
||||
workflowData: [[{ json: {} }]],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -182,22 +231,30 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
$executionId: executionId,
|
||||
};
|
||||
|
||||
if (webhookData.webhookDescription['responseHeaders'] !== undefined) {
|
||||
const responseHeaders = workflow.expression.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], executionMode, additionalKeys, undefined) as {
|
||||
entries?: Array<{
|
||||
if (webhookData.webhookDescription.responseHeaders !== undefined) {
|
||||
const responseHeaders = workflow.expression.getComplexParameterValue(
|
||||
workflowStartNode,
|
||||
webhookData.webhookDescription.responseHeaders,
|
||||
executionMode,
|
||||
additionalKeys,
|
||||
undefined,
|
||||
) as {
|
||||
entries?:
|
||||
| Array<{
|
||||
name: string;
|
||||
value: string;
|
||||
}> | undefined;
|
||||
}>
|
||||
| undefined;
|
||||
};
|
||||
|
||||
if (responseHeaders !== undefined && responseHeaders['entries'] !== undefined) {
|
||||
for (const item of responseHeaders['entries']) {
|
||||
res.setHeader(item['name'], item['value']);
|
||||
if (responseHeaders !== undefined && responseHeaders.entries !== undefined) {
|
||||
for (const item of responseHeaders.entries) {
|
||||
res.setHeader(item.name, item.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (webhookResultData.noWebhookResponse === true && didSendResponse === false) {
|
||||
if (webhookResultData.noWebhookResponse === true && !didSendResponse) {
|
||||
// The response got already send
|
||||
responseCallback(null, {
|
||||
noWebhookResponse: true,
|
||||
|
@ -209,7 +266,7 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
// Workflow should not run
|
||||
if (webhookResultData.webhookResponse !== undefined) {
|
||||
// Data to respond with is given
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
responseCallback(null, {
|
||||
data: webhookResultData.webhookResponse,
|
||||
responseCode,
|
||||
|
@ -218,7 +275,8 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
}
|
||||
} else {
|
||||
// Send default response
|
||||
if (didSendResponse === false) {
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (!didSendResponse) {
|
||||
responseCallback(null, {
|
||||
data: {
|
||||
message: 'Webhook call got received.',
|
||||
|
@ -233,7 +291,7 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
|
||||
// Now that we know that the workflow should run we can return the default response
|
||||
// directly if responseMode it set to "onReceived" and a respone should be sent
|
||||
if (responseMode === 'onReceived' && didSendResponse === false) {
|
||||
if (responseMode === 'onReceived' && !didSendResponse) {
|
||||
// Return response directly and do not wait for the workflow to finish
|
||||
if (webhookResultData.webhookResponse !== undefined) {
|
||||
// Data to respond with is given
|
||||
|
@ -255,18 +313,17 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
|
||||
// Initialize the data of the webhook node
|
||||
const nodeExecutionStack: IExecuteData[] = [];
|
||||
nodeExecutionStack.push(
|
||||
{
|
||||
nodeExecutionStack.push({
|
||||
node: workflowStartNode,
|
||||
data: {
|
||||
main: webhookResultData.workflowData,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
runExecutionData = runExecutionData || {
|
||||
startData: {
|
||||
},
|
||||
runExecutionData =
|
||||
runExecutionData ||
|
||||
({
|
||||
startData: {},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
|
@ -275,12 +332,13 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
nodeExecutionStack,
|
||||
waitingExecution: {},
|
||||
},
|
||||
} as IRunExecutionData;
|
||||
} as IRunExecutionData);
|
||||
|
||||
if (executionId !== undefined) {
|
||||
// Set the data the webhook node did return on the waiting node if executionId
|
||||
// already exists as it means that we are restarting an existing execution.
|
||||
runExecutionData.executionData!.nodeExecutionStack[0].data.main = webhookResultData.workflowData;
|
||||
runExecutionData.executionData!.nodeExecutionStack[0].data.main =
|
||||
webhookResultData.workflowData;
|
||||
}
|
||||
|
||||
if (Object.keys(runExecutionDataMerge).length !== 0) {
|
||||
|
@ -299,13 +357,19 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
const workflowRunner = new WorkflowRunner();
|
||||
executionId = await workflowRunner.run(runData, true, !didSendResponse, executionId);
|
||||
|
||||
Logger.verbose(`Started execution of workflow "${workflow.name}" from webhook with execution ID ${executionId}`, { executionId });
|
||||
Logger.verbose(
|
||||
`Started execution of workflow "${workflow.name}" from webhook with execution ID ${executionId}`,
|
||||
{ executionId },
|
||||
);
|
||||
|
||||
// Get a promise which resolves when the workflow did execute and send then response
|
||||
const executePromise = activeExecutions.getPostExecutePromise(executionId) as Promise<IExecutionDb | undefined>;
|
||||
executePromise.then((data) => {
|
||||
const executePromise = activeExecutions.getPostExecutePromise(executionId) as Promise<
|
||||
IExecutionDb | undefined
|
||||
>;
|
||||
executePromise
|
||||
.then((data) => {
|
||||
if (data === undefined) {
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
responseCallback(null, {
|
||||
data: {
|
||||
message: 'Workflow did execute sucessfully but no data got returned.',
|
||||
|
@ -318,8 +382,8 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
}
|
||||
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||
if(data.data.resultData.error || returnData?.error !== undefined) {
|
||||
if (didSendResponse === false) {
|
||||
if (data.data.resultData.error || returnData?.error !== undefined) {
|
||||
if (!didSendResponse) {
|
||||
responseCallback(null, {
|
||||
data: {
|
||||
message: 'Workflow did error.',
|
||||
|
@ -332,10 +396,11 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
}
|
||||
|
||||
if (returnData === undefined) {
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
responseCallback(null, {
|
||||
data: {
|
||||
message: 'Workflow did execute sucessfully but the last node did not return any data.',
|
||||
message:
|
||||
'Workflow did execute sucessfully but the last node did not return any data.',
|
||||
},
|
||||
responseCode,
|
||||
});
|
||||
|
@ -348,9 +413,15 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
$executionId: executionId,
|
||||
};
|
||||
|
||||
const responseData = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], executionMode, additionalKeys, 'firstEntryJson');
|
||||
const responseData = workflow.expression.getSimpleParameterValue(
|
||||
workflowStartNode,
|
||||
webhookData.webhookDescription.responseData,
|
||||
executionMode,
|
||||
additionalKeys,
|
||||
'firstEntryJson',
|
||||
);
|
||||
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
let data: IDataObject | IDataObject[];
|
||||
|
||||
if (responseData === 'firstEntryJson') {
|
||||
|
@ -363,20 +434,36 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
|
||||
data = returnData.data!.main[0]![0].json;
|
||||
|
||||
const responsePropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responsePropertyName'], executionMode, additionalKeys, undefined);
|
||||
const responsePropertyName = workflow.expression.getSimpleParameterValue(
|
||||
workflowStartNode,
|
||||
webhookData.webhookDescription.responsePropertyName,
|
||||
executionMode,
|
||||
additionalKeys,
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (responsePropertyName !== undefined) {
|
||||
data = get(data, responsePropertyName as string) as IDataObject;
|
||||
}
|
||||
|
||||
const responseContentType = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseContentType'], executionMode, additionalKeys, undefined);
|
||||
const responseContentType = workflow.expression.getSimpleParameterValue(
|
||||
workflowStartNode,
|
||||
webhookData.webhookDescription.responseContentType,
|
||||
executionMode,
|
||||
additionalKeys,
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (responseContentType !== undefined) {
|
||||
// Send the webhook response manually to be able to set the content-type
|
||||
res.setHeader('Content-Type', responseContentType as string);
|
||||
|
||||
// Returning an object, boolean, number, ... causes problems so make sure to stringify if needed
|
||||
if (data !== null && data !== undefined && ['Buffer', 'String'].includes(data.constructor.name)) {
|
||||
if (
|
||||
data !== null &&
|
||||
data !== undefined &&
|
||||
['Buffer', 'String'].includes(data.constructor.name)
|
||||
) {
|
||||
res.end(data);
|
||||
} else {
|
||||
res.end(JSON.stringify(data));
|
||||
|
@ -387,7 +474,6 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
});
|
||||
didSendResponse = true;
|
||||
}
|
||||
|
||||
} else if (responseData === 'firstEntryBinary') {
|
||||
// Return the binary data of the first entry
|
||||
data = returnData.data!.main[0]![0];
|
||||
|
@ -402,20 +488,33 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
didSendResponse = true;
|
||||
}
|
||||
|
||||
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseBinaryPropertyName'], executionMode, additionalKeys, 'data');
|
||||
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(
|
||||
workflowStartNode,
|
||||
webhookData.webhookDescription.responseBinaryPropertyName,
|
||||
executionMode,
|
||||
additionalKeys,
|
||||
'data',
|
||||
);
|
||||
|
||||
if (responseBinaryPropertyName === undefined && didSendResponse === false) {
|
||||
if (responseBinaryPropertyName === undefined && !didSendResponse) {
|
||||
responseCallback(new Error('No "responseBinaryPropertyName" is set.'), {});
|
||||
didSendResponse = true;
|
||||
}
|
||||
|
||||
const binaryData = (data.binary as IBinaryKeyData)[responseBinaryPropertyName as string];
|
||||
if (binaryData === undefined && didSendResponse === false) {
|
||||
responseCallback(new Error(`The binary property "${responseBinaryPropertyName}" which should be returned does not exist.`), {});
|
||||
const binaryData = (data.binary as IBinaryKeyData)[
|
||||
responseBinaryPropertyName as string
|
||||
];
|
||||
if (binaryData === undefined && !didSendResponse) {
|
||||
responseCallback(
|
||||
new Error(
|
||||
`The binary property "${responseBinaryPropertyName}" which should be returned does not exist.`,
|
||||
),
|
||||
{},
|
||||
);
|
||||
didSendResponse = true;
|
||||
}
|
||||
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
// Send the webhook response manually
|
||||
res.setHeader('Content-Type', binaryData.mimeType);
|
||||
res.end(Buffer.from(binaryData.data, BINARY_ENCODING));
|
||||
|
@ -424,7 +523,6 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
noWebhookResponse: true,
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
// Return the JSON data of all the entries
|
||||
data = [];
|
||||
|
@ -433,7 +531,7 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
}
|
||||
}
|
||||
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
responseCallback(null, {
|
||||
data,
|
||||
responseCode,
|
||||
|
@ -445,17 +543,17 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
return data;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
responseCallback(new Error('There was a problem executing the workflow.'), {});
|
||||
}
|
||||
|
||||
throw new ResponseHelper.ResponseError(e.message, 500, 500);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return executionId;
|
||||
|
||||
} catch (e) {
|
||||
if (didSendResponse === false) {
|
||||
if (!didSendResponse) {
|
||||
responseCallback(new Error('There was a problem executing the workflow.'), {});
|
||||
}
|
||||
|
||||
|
@ -463,7 +561,6 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the base URL of the webhooks
|
||||
*
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
/* eslint-disable no-console */
|
||||
/* eslint-disable consistent-return */
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import * as express from 'express';
|
||||
import {
|
||||
readFileSync,
|
||||
} from 'fs';
|
||||
import {
|
||||
getConnectionManager,
|
||||
} from 'typeorm';
|
||||
import { readFileSync } from 'fs';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
import * as bodyParser from 'body-parser';
|
||||
require('body-parser-xml')(bodyParser);
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-unused-vars
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import * as compression from 'compression';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as parseUrl from 'parseurl';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ActiveExecutions,
|
||||
ActiveWorkflowRunner,
|
||||
|
@ -19,25 +27,31 @@ import {
|
|||
IExternalHooksClass,
|
||||
IPackageVersions,
|
||||
ResponseHelper,
|
||||
} from './';
|
||||
} from '.';
|
||||
|
||||
import * as compression from 'compression';
|
||||
import * as config from '../config';
|
||||
import * as parseUrl from 'parseurl';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-call
|
||||
require('body-parser-xml')(bodyParser);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function registerProductionWebhooks() {
|
||||
|
||||
// ----------------------------------------
|
||||
// Regular Webhooks
|
||||
// ----------------------------------------
|
||||
|
||||
// HEAD webhook requests
|
||||
this.app.head(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
this.app.head(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhook.length + 2);
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
response = await this.activeWorkflowRunner.executeWebhook('HEAD', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
|
@ -50,12 +64,17 @@ export function registerProductionWebhooks() {
|
|||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// OPTIONS webhook requests
|
||||
this.app.options(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
this.app.options(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhook.length + 2);
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
let allowedMethods: string[];
|
||||
try {
|
||||
|
@ -70,12 +89,17 @@ export function registerProductionWebhooks() {
|
|||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// GET webhook requests
|
||||
this.app.get(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
this.app.get(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhook.length + 2);
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
|
@ -91,12 +115,17 @@ export function registerProductionWebhooks() {
|
|||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// POST webhook requests
|
||||
this.app.post(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
this.app.post(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhook.length + 2);
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
|
@ -112,27 +141,43 @@ export function registerProductionWebhooks() {
|
|||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class App {
|
||||
|
||||
app: express.Application;
|
||||
|
||||
activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner;
|
||||
|
||||
endpointWebhook: string;
|
||||
|
||||
endpointPresetCredentials: string;
|
||||
|
||||
externalHooks: IExternalHooksClass;
|
||||
|
||||
saveDataErrorExecution: string;
|
||||
|
||||
saveDataSuccessExecution: string;
|
||||
|
||||
saveManualExecutions: boolean;
|
||||
|
||||
executionTimeout: number;
|
||||
|
||||
maxExecutionTimeout: number;
|
||||
|
||||
timezone: string;
|
||||
|
||||
activeExecutionsInstance: ActiveExecutions.ActiveExecutions;
|
||||
|
||||
versions: IPackageVersions | undefined;
|
||||
|
||||
restEndpoint: string;
|
||||
|
||||
protocol: string;
|
||||
|
||||
sslKey: string;
|
||||
|
||||
sslCert: string;
|
||||
|
||||
presetCredentialsLoaded: boolean;
|
||||
|
@ -163,7 +208,6 @@ class App {
|
|||
this.endpointPresetCredentials = config.get('credentials.overwrite.endpoint') as string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current epoch time
|
||||
*
|
||||
|
@ -174,9 +218,7 @@ class App {
|
|||
return new Date();
|
||||
}
|
||||
|
||||
|
||||
async config(): Promise<void> {
|
||||
|
||||
this.versions = await GenericHelpers.getVersions();
|
||||
|
||||
// Compress the response data
|
||||
|
@ -191,49 +233,63 @@ class App {
|
|||
});
|
||||
|
||||
// Support application/json type post data
|
||||
this.app.use(bodyParser.json({
|
||||
limit: '16mb', verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
},
|
||||
}));
|
||||
|
||||
// Support application/xml type post data
|
||||
// @ts-ignore
|
||||
this.app.use(bodyParser.xml({
|
||||
limit: '16mb', xmlParseOptions: {
|
||||
normalize: true, // Trim whitespace inside text nodes
|
||||
normalizeTags: true, // Transform tags to lowercase
|
||||
explicitArray: false, // Only put properties in array if length > 1
|
||||
},
|
||||
}));
|
||||
|
||||
this.app.use(bodyParser.text({
|
||||
limit: '16mb', verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
},
|
||||
}));
|
||||
|
||||
//support application/x-www-form-urlencoded post data
|
||||
this.app.use(bodyParser.urlencoded({ extended: false,
|
||||
this.app.use(
|
||||
bodyParser.json({
|
||||
limit: '16mb',
|
||||
verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
},
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
if (process.env['NODE_ENV'] !== 'production') {
|
||||
// Support application/xml type post data
|
||||
this.app.use(
|
||||
// @ts-ignore
|
||||
bodyParser.xml({
|
||||
limit: '16mb',
|
||||
xmlParseOptions: {
|
||||
normalize: true, // Trim whitespace inside text nodes
|
||||
normalizeTags: true, // Transform tags to lowercase
|
||||
explicitArray: false, // Only put properties in array if length > 1
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
this.app.use(
|
||||
bodyParser.text({
|
||||
limit: '16mb',
|
||||
verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// support application/x-www-form-urlencoded post data
|
||||
this.app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: false,
|
||||
verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
// Allow access also from frontend when developing
|
||||
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
|
||||
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, sessionid');
|
||||
res.header(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Origin, X-Requested-With, Content-Type, Accept, sessionid',
|
||||
);
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
if (Db.collections.Workflow === null) {
|
||||
const error = new ResponseHelper.ResponseError('Database is not ready!', undefined, 503);
|
||||
|
@ -243,25 +299,22 @@ class App {
|
|||
next();
|
||||
});
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Healthcheck
|
||||
// ----------------------------------------
|
||||
|
||||
|
||||
// Does very basic health check
|
||||
this.app.get('/healthz', async (req: express.Request, res: express.Response) => {
|
||||
|
||||
const connection = getConnectionManager().get();
|
||||
|
||||
try {
|
||||
if (connection.isConnected === false) {
|
||||
if (!connection.isConnected) {
|
||||
// Connection is not active
|
||||
throw new Error('No active database connection!');
|
||||
}
|
||||
// DB ping
|
||||
await connection.query('SELECT 1');
|
||||
// eslint-disable-next-line id-denylist
|
||||
} catch (err) {
|
||||
const error = new ResponseHelper.ResponseError('No Database connection!', undefined, 503);
|
||||
return ResponseHelper.sendErrorResponse(res, error);
|
||||
|
@ -276,9 +329,7 @@ class App {
|
|||
});
|
||||
|
||||
registerProductionWebhooks.apply(this);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function start(): Promise<void> {
|
||||
|
@ -292,12 +343,14 @@ export async function start(): Promise<void> {
|
|||
let server;
|
||||
|
||||
if (app.protocol === 'https' && app.sslKey && app.sslCert) {
|
||||
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||
const https = require('https');
|
||||
const privateKey = readFileSync(app.sslKey, 'utf8');
|
||||
const cert = readFileSync(app.sslCert, 'utf8');
|
||||
const credentials = { key: privateKey, cert };
|
||||
server = https.createServer(credentials, app.app);
|
||||
} else {
|
||||
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||
const http = require('http');
|
||||
server = http.createServer(app.app);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import {
|
||||
Db,
|
||||
} from './';
|
||||
import {
|
||||
INode,
|
||||
IWorkflowCredentials
|
||||
} from 'n8n-workflow';
|
||||
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
import { INode, IWorkflowCredentials } from 'n8n-workflow';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { Db } from '.';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCredentials> {
|
||||
// Go through all nodes to find which credentials are needed to execute the workflow
|
||||
const returnCredentials: IWorkflowCredentials = {};
|
||||
|
||||
let node, type, name, foundCredentials;
|
||||
let node;
|
||||
let type;
|
||||
let name;
|
||||
let foundCredentials;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (node of nodes) {
|
||||
if (node.disabled === true || !node.credentials) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (type of Object.keys(node.credentials)) {
|
||||
if (!returnCredentials.hasOwnProperty(type)) {
|
||||
returnCredentials[type] = {};
|
||||
|
@ -24,14 +27,15 @@ export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCred
|
|||
name = node.credentials[type];
|
||||
|
||||
if (!returnCredentials[type].hasOwnProperty(name)) {
|
||||
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-non-null-assertion
|
||||
foundCredentials = await Db.collections.Credentials!.find({ name, type });
|
||||
if (!foundCredentials.length) {
|
||||
throw new Error(`Could not find credentials for type "${type}" with name "${name}".`);
|
||||
}
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
returnCredentials[type][name] = foundCredentials[0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return returnCredentials;
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
import {
|
||||
ActiveExecutions,
|
||||
CredentialsHelper,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
IExecutionDb,
|
||||
IExecutionFlattedDb,
|
||||
IExecutionResponse,
|
||||
IPushDataExecutionFinished,
|
||||
IWorkflowBase,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
Push,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
WorkflowCredentials,
|
||||
WorkflowHelpers,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
UserSettings,
|
||||
WorkflowExecute,
|
||||
} from 'n8n-core';
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/await-thenable */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable id-denylist */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable func-names */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { UserSettings, WorkflowExecute } from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
|
@ -43,10 +36,29 @@ import {
|
|||
WorkflowHooks,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as config from '../config';
|
||||
|
||||
import { LessThanOrEqual } from 'typeorm';
|
||||
import { DateUtils } from 'typeorm/util/DateUtils';
|
||||
import * as config from '../config';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ActiveExecutions,
|
||||
CredentialsHelper,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
IExecutionDb,
|
||||
IExecutionFlattedDb,
|
||||
IExecutionResponse,
|
||||
IPushDataExecutionFinished,
|
||||
IWorkflowBase,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
Push,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
WorkflowCredentials,
|
||||
WorkflowHelpers,
|
||||
} from '.';
|
||||
|
||||
const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
||||
|
||||
|
@ -59,10 +71,16 @@ const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
|||
* @param {WorkflowExecuteMode} mode The mode in which the workflow got started in
|
||||
* @param {string} [executionId] The id the execution got saved as
|
||||
*/
|
||||
function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mode: WorkflowExecuteMode, executionId?: string, retryOf?: string): void {
|
||||
function executeErrorWorkflow(
|
||||
workflowData: IWorkflowBase,
|
||||
fullRunData: IRun,
|
||||
mode: WorkflowExecuteMode,
|
||||
executionId?: string,
|
||||
retryOf?: string,
|
||||
): void {
|
||||
// Check if there was an error and if so if an errorWorkflow or a trigger is set
|
||||
|
||||
let pastExecutionUrl: string | undefined = undefined;
|
||||
let pastExecutionUrl: string | undefined;
|
||||
if (executionId !== undefined) {
|
||||
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}execution/${executionId}`;
|
||||
}
|
||||
|
@ -78,20 +96,42 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
|||
retryOf,
|
||||
},
|
||||
workflow: {
|
||||
id: workflowData.id !== undefined ? workflowData.id.toString() as string : undefined,
|
||||
id: workflowData.id !== undefined ? workflowData.id.toString() : undefined,
|
||||
name: workflowData.name,
|
||||
},
|
||||
};
|
||||
|
||||
// Run the error workflow
|
||||
// To avoid an infinite loop do not run the error workflow again if the error-workflow itself failed and it is its own error-workflow.
|
||||
if (workflowData.settings !== undefined && workflowData.settings.errorWorkflow && !(mode === 'error' && workflowData.id && workflowData.settings.errorWorkflow.toString() === workflowData.id.toString())) {
|
||||
Logger.verbose(`Start external error workflow`, { executionId, errorWorkflowId: workflowData.settings.errorWorkflow.toString(), workflowId: workflowData.id });
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
workflowData.settings !== undefined &&
|
||||
workflowData.settings.errorWorkflow &&
|
||||
!(
|
||||
mode === 'error' &&
|
||||
workflowData.id &&
|
||||
workflowData.settings.errorWorkflow.toString() === workflowData.id.toString()
|
||||
)
|
||||
) {
|
||||
Logger.verbose(`Start external error workflow`, {
|
||||
executionId,
|
||||
errorWorkflowId: workflowData.settings.errorWorkflow.toString(),
|
||||
workflowId: workflowData.id,
|
||||
});
|
||||
// If a specific error workflow is set run only that one
|
||||
WorkflowHelpers.executeErrorWorkflow(workflowData.settings.errorWorkflow as string, workflowErrorData);
|
||||
} else if (mode !== 'error' && workflowData.id !== undefined && workflowData.nodes.some((node) => node.type === ERROR_TRIGGER_TYPE)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
WorkflowHelpers.executeErrorWorkflow(
|
||||
workflowData.settings.errorWorkflow as string,
|
||||
workflowErrorData,
|
||||
);
|
||||
} else if (
|
||||
mode !== 'error' &&
|
||||
workflowData.id !== undefined &&
|
||||
workflowData.nodes.some((node) => node.type === ERROR_TRIGGER_TYPE)
|
||||
) {
|
||||
Logger.verbose(`Start internal error workflow`, { executionId, workflowId: workflowData.id });
|
||||
// If the workflow contains
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
WorkflowHelpers.executeErrorWorkflow(workflowData.id.toString(), workflowErrorData);
|
||||
}
|
||||
}
|
||||
|
@ -114,23 +154,34 @@ function pruneExecutionData(this: WorkflowHooks): void {
|
|||
date.setHours(date.getHours() - maxAge);
|
||||
|
||||
// date reformatting needed - see https://github.com/typeorm/typeorm/issues/2286
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const utcDate = DateUtils.mixedDateToUtcDatetimeString(date);
|
||||
|
||||
// throttle just on success to allow for self healing on failure
|
||||
Db.collections.Execution!.delete({ stoppedAt: LessThanOrEqual(utcDate) })
|
||||
.then(data =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
Db.collections
|
||||
.Execution!.delete({ stoppedAt: LessThanOrEqual(utcDate) })
|
||||
.then((data) =>
|
||||
setTimeout(() => {
|
||||
throttling = false;
|
||||
}, timeout * 1000)
|
||||
).catch(error => {
|
||||
}, timeout * 1000),
|
||||
)
|
||||
.catch((error) => {
|
||||
throttling = false;
|
||||
|
||||
Logger.error(`Failed pruning execution data from database for execution ID ${this.executionId} (hookFunctionsSave)`, { ...error, executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
Logger.error(
|
||||
`Failed pruning execution data from database for execution ID ${this.executionId} (hookFunctionsSave)`,
|
||||
{
|
||||
...error,
|
||||
executionId: this.executionId,
|
||||
sessionId: this.sessionId,
|
||||
workflowId: this.workflowData.id,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns hook functions to push data to Editor-UI
|
||||
*
|
||||
|
@ -145,13 +196,21 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
|||
if (this.sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
Logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, { executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
Logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
|
||||
executionId: this.executionId,
|
||||
sessionId: this.sessionId,
|
||||
workflowId: this.workflowData.id,
|
||||
});
|
||||
|
||||
const pushInstance = Push.getInstance();
|
||||
pushInstance.send('nodeExecuteBefore', {
|
||||
pushInstance.send(
|
||||
'nodeExecuteBefore',
|
||||
{
|
||||
executionId: this.executionId,
|
||||
nodeName,
|
||||
}, this.sessionId);
|
||||
},
|
||||
this.sessionId,
|
||||
);
|
||||
},
|
||||
],
|
||||
nodeExecuteAfter: [
|
||||
|
@ -160,37 +219,62 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
|||
if (this.sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
Logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, { executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
Logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
|
||||
executionId: this.executionId,
|
||||
sessionId: this.sessionId,
|
||||
workflowId: this.workflowData.id,
|
||||
});
|
||||
|
||||
const pushInstance = Push.getInstance();
|
||||
pushInstance.send('nodeExecuteAfter', {
|
||||
pushInstance.send(
|
||||
'nodeExecuteAfter',
|
||||
{
|
||||
executionId: this.executionId,
|
||||
nodeName,
|
||||
data,
|
||||
}, this.sessionId);
|
||||
},
|
||||
this.sessionId,
|
||||
);
|
||||
},
|
||||
],
|
||||
workflowExecuteBefore: [
|
||||
async function (this: WorkflowHooks): Promise<void> {
|
||||
Logger.debug(`Executing hook (hookFunctionsPush)`, { executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
Logger.debug(`Executing hook (hookFunctionsPush)`, {
|
||||
executionId: this.executionId,
|
||||
sessionId: this.sessionId,
|
||||
workflowId: this.workflowData.id,
|
||||
});
|
||||
// Push data to session which started the workflow
|
||||
if (this.sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
const pushInstance = Push.getInstance();
|
||||
pushInstance.send('executionStarted', {
|
||||
pushInstance.send(
|
||||
'executionStarted',
|
||||
{
|
||||
executionId: this.executionId,
|
||||
mode: this.mode,
|
||||
startedAt: new Date(),
|
||||
retryOf: this.retryOf,
|
||||
workflowId: this.workflowData.id, sessionId: this.sessionId as string,
|
||||
workflowId: this.workflowData.id,
|
||||
sessionId: this.sessionId,
|
||||
workflowName: this.workflowData.name,
|
||||
}, this.sessionId);
|
||||
},
|
||||
this.sessionId,
|
||||
);
|
||||
},
|
||||
],
|
||||
workflowExecuteAfter: [
|
||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
||||
Logger.debug(`Executing hook (hookFunctionsPush)`, { executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
async function (
|
||||
this: WorkflowHooks,
|
||||
fullRunData: IRun,
|
||||
newStaticData: IDataObject,
|
||||
): Promise<void> {
|
||||
Logger.debug(`Executing hook (hookFunctionsPush)`, {
|
||||
executionId: this.executionId,
|
||||
sessionId: this.sessionId,
|
||||
workflowId: this.workflowData.id,
|
||||
});
|
||||
// Push data to session which started the workflow
|
||||
if (this.sessionId === undefined) {
|
||||
return;
|
||||
|
@ -211,7 +295,10 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
|||
};
|
||||
|
||||
// Push data to editor-ui once workflow finished
|
||||
Logger.debug(`Save execution progress to database for execution ID ${this.executionId} `, { executionId: this.executionId, workflowId: this.workflowData.id });
|
||||
Logger.debug(`Save execution progress to database for execution ID ${this.executionId} `, {
|
||||
executionId: this.executionId,
|
||||
workflowId: this.workflowData.id,
|
||||
});
|
||||
// TODO: Look at this again
|
||||
const sendData: IPushDataExecutionFinished = {
|
||||
executionId: this.executionId,
|
||||
|
@ -226,7 +313,6 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||
const externalHooks = ExternalHooks();
|
||||
|
||||
|
@ -237,20 +323,32 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
|||
},
|
||||
],
|
||||
nodeExecuteAfter: [
|
||||
async function (nodeName: string, data: ITaskData, executionData: IRunExecutionData): Promise<void> {
|
||||
async function (
|
||||
nodeName: string,
|
||||
data: ITaskData,
|
||||
executionData: IRunExecutionData,
|
||||
): Promise<void> {
|
||||
if (this.workflowData.settings !== undefined) {
|
||||
if (this.workflowData.settings.saveExecutionProgress === false) {
|
||||
return;
|
||||
} else if (this.workflowData.settings.saveExecutionProgress !== true && !config.get('executions.saveExecutionProgress') as boolean) {
|
||||
}
|
||||
if (
|
||||
this.workflowData.settings.saveExecutionProgress !== true &&
|
||||
!config.get('executions.saveExecutionProgress')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else if (!config.get('executions.saveExecutionProgress') as boolean) {
|
||||
} else if (!config.get('executions.saveExecutionProgress')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Logger.debug(`Save execution progress to database for execution ID ${this.executionId} `, { executionId: this.executionId, nodeName });
|
||||
Logger.debug(
|
||||
`Save execution progress to database for execution ID ${this.executionId} `,
|
||||
{ executionId: this.executionId, nodeName },
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const execution = await Db.collections.Execution!.findOne(this.executionId);
|
||||
|
||||
if (execution === undefined) {
|
||||
|
@ -258,7 +356,8 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
|||
// This check is here mostly to make typescript happy.
|
||||
return;
|
||||
}
|
||||
const fullExecutionData: IExecutionResponse = ResponseHelper.unflattenExecutionData(execution);
|
||||
const fullExecutionData: IExecutionResponse =
|
||||
ResponseHelper.unflattenExecutionData(execution);
|
||||
|
||||
if (fullExecutionData.finished) {
|
||||
// We already received ´workflowExecuteAfter´ webhook, so this is just an async call
|
||||
|
@ -296,22 +395,32 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
|||
|
||||
const flattenedExecutionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
await Db.collections.Execution!.update(this.executionId, flattenedExecutionData as IExecutionFlattedDb);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Execution!.update(
|
||||
this.executionId,
|
||||
flattenedExecutionData as IExecutionFlattedDb,
|
||||
);
|
||||
} catch (err) {
|
||||
// TODO: Improve in the future!
|
||||
// Errors here might happen because of database access
|
||||
// For busy machines, we may get "Database is locked" errors.
|
||||
|
||||
// We do this to prevent crashes and executions ending in `unknown` state.
|
||||
Logger.error(`Failed saving execution progress to database for execution ID ${this.executionId} (hookFunctionsPreExecute, nodeExecuteAfter)`, { ...err, executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
Logger.error(
|
||||
`Failed saving execution progress to database for execution ID ${this.executionId} (hookFunctionsPreExecute, nodeExecuteAfter)`,
|
||||
{
|
||||
...err,
|
||||
executionId: this.executionId,
|
||||
sessionId: this.sessionId,
|
||||
workflowId: this.workflowData.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns hook functions to save workflow execution and call error workflow
|
||||
*
|
||||
|
@ -323,8 +432,15 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
nodeExecuteAfter: [],
|
||||
workflowExecuteBefore: [],
|
||||
workflowExecuteAfter: [
|
||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
||||
Logger.debug(`Executing hook (hookFunctionsSave)`, { executionId: this.executionId, workflowId: this.workflowData.id });
|
||||
async function (
|
||||
this: WorkflowHooks,
|
||||
fullRunData: IRun,
|
||||
newStaticData: IDataObject,
|
||||
): Promise<void> {
|
||||
Logger.debug(`Executing hook (hookFunctionsSave)`, {
|
||||
executionId: this.executionId,
|
||||
workflowId: this.workflowData.id,
|
||||
});
|
||||
|
||||
// Prune old execution data
|
||||
if (config.get('executions.pruneData')) {
|
||||
|
@ -334,23 +450,37 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
const isManualMode = [this.mode, parentProcessMode].includes('manual');
|
||||
|
||||
try {
|
||||
if (!isManualMode && WorkflowHelpers.isWorkflowIdValid(this.workflowData.id as string) === true && newStaticData) {
|
||||
if (
|
||||
!isManualMode &&
|
||||
WorkflowHelpers.isWorkflowIdValid(this.workflowData.id as string) &&
|
||||
newStaticData
|
||||
) {
|
||||
// Workflow is saved so update in database
|
||||
try {
|
||||
await WorkflowHelpers.saveStaticDataById(this.workflowData.id as string, newStaticData);
|
||||
await WorkflowHelpers.saveStaticDataById(
|
||||
this.workflowData.id as string,
|
||||
newStaticData,
|
||||
);
|
||||
} catch (e) {
|
||||
Logger.error(`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (hookFunctionsSave)`, { executionId: this.executionId, workflowId: this.workflowData.id });
|
||||
Logger.error(
|
||||
`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (hookFunctionsSave)`,
|
||||
{ executionId: this.executionId, workflowId: this.workflowData.id },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean;
|
||||
if (this.workflowData.settings !== undefined && this.workflowData.settings.saveManualExecutions !== undefined) {
|
||||
if (
|
||||
this.workflowData.settings !== undefined &&
|
||||
this.workflowData.settings.saveManualExecutions !== undefined
|
||||
) {
|
||||
// Apply to workflow override
|
||||
saveManualExecutions = this.workflowData.settings.saveManualExecutions as boolean;
|
||||
}
|
||||
|
||||
if (isManualMode && saveManualExecutions === false && !fullRunData.waitTill) {
|
||||
if (isManualMode && !saveManualExecutions && !fullRunData.waitTill) {
|
||||
// Data is always saved, so we remove from database
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Execution!.delete(this.executionId);
|
||||
return;
|
||||
}
|
||||
|
@ -359,17 +489,28 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
||||
if (this.workflowData.settings !== undefined) {
|
||||
saveDataErrorExecution = (this.workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
||||
saveDataSuccessExecution = (this.workflowData.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
|
||||
saveDataErrorExecution =
|
||||
(this.workflowData.settings.saveDataErrorExecution as string) ||
|
||||
saveDataErrorExecution;
|
||||
saveDataSuccessExecution =
|
||||
(this.workflowData.settings.saveDataSuccessExecution as string) ||
|
||||
saveDataSuccessExecution;
|
||||
}
|
||||
|
||||
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
||||
if (workflowDidSucceed === true && saveDataSuccessExecution === 'none' ||
|
||||
workflowDidSucceed === false && saveDataErrorExecution === 'none'
|
||||
if (
|
||||
(workflowDidSucceed && saveDataSuccessExecution === 'none') ||
|
||||
(!workflowDidSucceed && saveDataErrorExecution === 'none')
|
||||
) {
|
||||
if (!fullRunData.waitTill) {
|
||||
if (!isManualMode) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
executeErrorWorkflow(
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
undefined,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
// Data is always saved, so we remove from database
|
||||
await Db.collections.Execution!.delete(this.executionId);
|
||||
|
@ -391,7 +532,10 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
fullExecutionData.retryOf = this.retryOf.toString();
|
||||
}
|
||||
|
||||
if (this.workflowData.id !== undefined && WorkflowHelpers.isWorkflowIdValid(this.workflowData.id.toString()) === true) {
|
||||
if (
|
||||
this.workflowData.id !== undefined &&
|
||||
WorkflowHelpers.isWorkflowIdValid(this.workflowData.id.toString())
|
||||
) {
|
||||
fullExecutionData.workflowId = this.workflowData.id.toString();
|
||||
}
|
||||
|
||||
|
@ -406,16 +550,27 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
// Save the Execution in DB
|
||||
await Db.collections.Execution!.update(this.executionId, executionData as IExecutionFlattedDb);
|
||||
await Db.collections.Execution!.update(
|
||||
this.executionId,
|
||||
executionData as IExecutionFlattedDb,
|
||||
);
|
||||
|
||||
if (fullRunData.finished === true && this.retryOf !== undefined) {
|
||||
// If the retry was successful save the reference it on the original execution
|
||||
// await Db.collections.Execution!.save(executionData as IExecutionFlattedDb);
|
||||
await Db.collections.Execution!.update(this.retryOf, { retrySuccessId: this.executionId });
|
||||
await Db.collections.Execution!.update(this.retryOf, {
|
||||
retrySuccessId: this.executionId,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isManualMode) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, this.executionId, this.retryOf);
|
||||
executeErrorWorkflow(
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
this.executionId,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Failed saving execution data to DB on execution ID ${this.executionId}`, {
|
||||
|
@ -425,7 +580,13 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
});
|
||||
|
||||
if (!isManualMode) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
executeErrorWorkflow(
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
undefined,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -433,7 +594,6 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns hook functions to save workflow execution and call error workflow
|
||||
* for running with queues. Manual executions should never run on queues as
|
||||
|
@ -447,20 +607,36 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
|||
nodeExecuteAfter: [],
|
||||
workflowExecuteBefore: [],
|
||||
workflowExecuteAfter: [
|
||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
||||
async function (
|
||||
this: WorkflowHooks,
|
||||
fullRunData: IRun,
|
||||
newStaticData: IDataObject,
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (WorkflowHelpers.isWorkflowIdValid(this.workflowData.id as string) === true && newStaticData) {
|
||||
if (WorkflowHelpers.isWorkflowIdValid(this.workflowData.id as string) && newStaticData) {
|
||||
// Workflow is saved so update in database
|
||||
try {
|
||||
await WorkflowHelpers.saveStaticDataById(this.workflowData.id as string, newStaticData);
|
||||
await WorkflowHelpers.saveStaticDataById(
|
||||
this.workflowData.id as string,
|
||||
newStaticData,
|
||||
);
|
||||
} catch (e) {
|
||||
Logger.error(`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (workflowExecuteAfter)`, { sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
Logger.error(
|
||||
`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (workflowExecuteAfter)`,
|
||||
{ sessionId: this.sessionId, workflowId: this.workflowData.id },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
||||
if (workflowDidSucceed === false) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
if (!workflowDidSucceed) {
|
||||
executeErrorWorkflow(
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
undefined,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
|
||||
const fullExecutionData: IExecutionDb = {
|
||||
|
@ -477,18 +653,26 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
|||
fullExecutionData.retryOf = this.retryOf.toString();
|
||||
}
|
||||
|
||||
if (this.workflowData.id !== undefined && WorkflowHelpers.isWorkflowIdValid(this.workflowData.id.toString()) === true) {
|
||||
if (
|
||||
this.workflowData.id !== undefined &&
|
||||
WorkflowHelpers.isWorkflowIdValid(this.workflowData.id.toString())
|
||||
) {
|
||||
fullExecutionData.workflowId = this.workflowData.id.toString();
|
||||
}
|
||||
|
||||
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
// Save the Execution in DB
|
||||
await Db.collections.Execution!.update(this.executionId, executionData as IExecutionFlattedDb);
|
||||
await Db.collections.Execution!.update(
|
||||
this.executionId,
|
||||
executionData as IExecutionFlattedDb,
|
||||
);
|
||||
|
||||
if (fullRunData.finished === true && this.retryOf !== undefined) {
|
||||
// If the retry was successful save the reference it on the original execution
|
||||
await Db.collections.Execution!.update(this.retryOf, { retrySuccessId: this.executionId });
|
||||
await Db.collections.Execution!.update(this.retryOf, {
|
||||
retrySuccessId: this.executionId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
|
@ -498,13 +682,17 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
|||
};
|
||||
}
|
||||
|
||||
export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeExecutionData[]): Promise<IWorkflowExecutionDataProcess> {
|
||||
export async function getRunData(
|
||||
workflowData: IWorkflowBase,
|
||||
inputData?: INodeExecutionData[],
|
||||
): Promise<IWorkflowExecutionDataProcess> {
|
||||
const mode = 'integrated';
|
||||
|
||||
// Find Start-Node
|
||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
||||
let startNode: INode | undefined;
|
||||
for (const node of workflowData!.nodes) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const node of workflowData.nodes) {
|
||||
if (requiredNodeTypes.includes(node.type)) {
|
||||
startNode = node;
|
||||
break;
|
||||
|
@ -525,18 +713,15 @@ export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeE
|
|||
|
||||
// Initialize the incoming data
|
||||
const nodeExecutionStack: IExecuteData[] = [];
|
||||
nodeExecutionStack.push(
|
||||
{
|
||||
nodeExecutionStack.push({
|
||||
node: startNode,
|
||||
data: {
|
||||
main: [inputData],
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const runExecutionData: IRunExecutionData = {
|
||||
startData: {
|
||||
},
|
||||
startData: {},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
|
@ -557,13 +742,14 @@ export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeE
|
|||
return runData;
|
||||
}
|
||||
|
||||
|
||||
export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promise<IWorkflowBase> {
|
||||
if (workflowInfo.id === undefined && workflowInfo.code === undefined) {
|
||||
throw new Error(`No information about the workflow to execute found. Please provide either the "id" or "code"!`);
|
||||
throw new Error(
|
||||
`No information about the workflow to execute found. Please provide either the "id" or "code"!`,
|
||||
);
|
||||
}
|
||||
|
||||
if (Db.collections!.Workflow === null) {
|
||||
if (Db.collections.Workflow === null) {
|
||||
// The first time executeWorkflow gets called the Database has
|
||||
// to get initialized first
|
||||
await Db.init();
|
||||
|
@ -571,7 +757,7 @@ export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promi
|
|||
|
||||
let workflowData: IWorkflowBase | undefined;
|
||||
if (workflowInfo.id !== undefined) {
|
||||
workflowData = await Db.collections!.Workflow!.findOne(workflowInfo.id);
|
||||
workflowData = await Db.collections.Workflow!.findOne(workflowInfo.id);
|
||||
if (workflowData === undefined) {
|
||||
throw new Error(`The workflow with the id "${workflowInfo.id}" does not exist.`);
|
||||
}
|
||||
|
@ -582,7 +768,6 @@ export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promi
|
|||
return workflowData!;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the workflow with the given ID
|
||||
*
|
||||
|
@ -592,25 +777,45 @@ export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promi
|
|||
* @param {INodeExecutionData[]} [inputData]
|
||||
* @returns {(Promise<Array<INodeExecutionData[] | null>>)}
|
||||
*/
|
||||
export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[], parentExecutionId?: string, loadedWorkflowData?: IWorkflowBase, loadedRunData?: IWorkflowExecutionDataProcess): Promise<Array<INodeExecutionData[] | null> | IWorkflowExecuteProcess> {
|
||||
export async function executeWorkflow(
|
||||
workflowInfo: IExecuteWorkflowInfo,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
inputData?: INodeExecutionData[],
|
||||
parentExecutionId?: string,
|
||||
loadedWorkflowData?: IWorkflowBase,
|
||||
loadedRunData?: IWorkflowExecutionDataProcess,
|
||||
): Promise<Array<INodeExecutionData[] | null> | IWorkflowExecuteProcess> {
|
||||
const externalHooks = ExternalHooks();
|
||||
await externalHooks.init();
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
const workflowData = loadedWorkflowData !== undefined ? loadedWorkflowData : await getWorkflowData(workflowInfo);
|
||||
const workflowData =
|
||||
loadedWorkflowData !== undefined ? loadedWorkflowData : await getWorkflowData(workflowInfo);
|
||||
|
||||
const workflowName = workflowData ? workflowData.name : undefined;
|
||||
const workflow = new Workflow({ id: workflowInfo.id, name: workflowName, nodes: workflowData!.nodes, connections: workflowData!.connections, active: workflowData!.active, nodeTypes, staticData: workflowData!.staticData });
|
||||
const workflow = new Workflow({
|
||||
id: workflowInfo.id,
|
||||
name: workflowName,
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: workflowData.active,
|
||||
nodeTypes,
|
||||
staticData: workflowData.staticData,
|
||||
});
|
||||
|
||||
const runData = loadedRunData !== undefined ? loadedRunData : await getRunData(workflowData, inputData);
|
||||
const runData =
|
||||
loadedRunData !== undefined ? loadedRunData : await getRunData(workflowData, inputData);
|
||||
|
||||
let executionId;
|
||||
|
||||
if (parentExecutionId !== undefined) {
|
||||
executionId = parentExecutionId;
|
||||
} else {
|
||||
executionId = parentExecutionId !== undefined ? parentExecutionId : await ActiveExecutions.getInstance().add(runData);
|
||||
executionId =
|
||||
parentExecutionId !== undefined
|
||||
? parentExecutionId
|
||||
: await ActiveExecutions.getInstance().add(runData);
|
||||
}
|
||||
|
||||
let data;
|
||||
|
@ -618,18 +823,29 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
// Create new additionalData to have different workflow loaded and to call
|
||||
// different webooks
|
||||
const additionalDataIntegrated = await getBase();
|
||||
additionalDataIntegrated.hooks = getWorkflowHooksIntegrated(runData.executionMode, executionId, workflowData!, { parentProcessMode: additionalData.hooks!.mode });
|
||||
additionalDataIntegrated.hooks = getWorkflowHooksIntegrated(
|
||||
runData.executionMode,
|
||||
executionId,
|
||||
workflowData,
|
||||
{ parentProcessMode: additionalData.hooks!.mode },
|
||||
);
|
||||
// Make sure we pass on the original executeWorkflow function we received
|
||||
// This one already contains changes to talk to parent process
|
||||
// and get executionID from `activeExecutions` running on main process
|
||||
additionalDataIntegrated.executeWorkflow = additionalData.executeWorkflow;
|
||||
|
||||
let subworkflowTimeout = additionalData.executionTimeoutTimestamp;
|
||||
if (workflowData.settings?.executionTimeout !== undefined && workflowData.settings.executionTimeout > 0) {
|
||||
if (
|
||||
workflowData.settings?.executionTimeout !== undefined &&
|
||||
workflowData.settings.executionTimeout > 0
|
||||
) {
|
||||
// We might have received a max timeout timestamp from the parent workflow
|
||||
// If we did, then we get the minimum time between the two timeouts
|
||||
// If no timeout was given from the parent, then we use our timeout.
|
||||
subworkflowTimeout = Math.min(additionalData.executionTimeoutTimestamp || Number.MAX_SAFE_INTEGER, Date.now() + (workflowData.settings.executionTimeout as number * 1000));
|
||||
subworkflowTimeout = Math.min(
|
||||
additionalData.executionTimeoutTimestamp || Number.MAX_SAFE_INTEGER,
|
||||
Date.now() + (workflowData.settings.executionTimeout as number) * 1000,
|
||||
);
|
||||
}
|
||||
|
||||
additionalDataIntegrated.executionTimeoutTimestamp = subworkflowTimeout;
|
||||
|
@ -637,7 +853,11 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
const runExecutionData = runData.executionData as IRunExecutionData;
|
||||
|
||||
// Execute the workflow
|
||||
const workflowExecute = new WorkflowExecute(additionalDataIntegrated, runData.executionMode, runExecutionData);
|
||||
const workflowExecute = new WorkflowExecute(
|
||||
additionalDataIntegrated,
|
||||
runData.executionMode,
|
||||
runExecutionData,
|
||||
);
|
||||
if (parentExecutionId !== undefined) {
|
||||
// Must be changed to become typed
|
||||
return {
|
||||
|
@ -678,7 +898,7 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
await Db.collections.Execution!.update(executionId, executionData as IExecutionFlattedDb);
|
||||
throw {
|
||||
...error,
|
||||
stack: error!.stack,
|
||||
stack: error.stack,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -690,19 +910,19 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||
return returnData!.data!.main;
|
||||
} else {
|
||||
}
|
||||
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||
// Workflow did fail
|
||||
const { error } = data.data.resultData;
|
||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||
throw {
|
||||
...error,
|
||||
stack: error!.stack,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function sendMessageToUI(source: string, message: any) { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function sendMessageToUI(source: string, message: any) {
|
||||
if (this.sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -710,16 +930,19 @@ export function sendMessageToUI(source: string, message: any) { // tslint:disabl
|
|||
// Push data to session which started workflow
|
||||
try {
|
||||
const pushInstance = Push.getInstance();
|
||||
pushInstance.send('sendConsoleMessage', {
|
||||
pushInstance.send(
|
||||
'sendConsoleMessage',
|
||||
{
|
||||
source: `Node: "${source}"`,
|
||||
message,
|
||||
}, this.sessionId);
|
||||
},
|
||||
this.sessionId,
|
||||
);
|
||||
} catch (error) {
|
||||
Logger.warn(`There was a problem sending messsage to UI: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the base additional data without webhooks
|
||||
*
|
||||
|
@ -728,13 +951,16 @@ export function sendMessageToUI(source: string, message: any) { // tslint:disabl
|
|||
* @param {INodeParameters} currentNodeParameters
|
||||
* @returns {Promise<IWorkflowExecuteAdditionalData>}
|
||||
*/
|
||||
export async function getBase(currentNodeParameters?: INodeParameters, executionTimeoutTimestamp?: number): Promise<IWorkflowExecuteAdditionalData> {
|
||||
export async function getBase(
|
||||
currentNodeParameters?: INodeParameters,
|
||||
executionTimeoutTimestamp?: number,
|
||||
): Promise<IWorkflowExecuteAdditionalData> {
|
||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||
|
||||
const timezone = config.get('generic.timezone') as string;
|
||||
const webhookBaseUrl = urlBaseWebhook + config.get('endpoints.webhook') as string;
|
||||
const webhookWaitingBaseUrl = urlBaseWebhook + config.get('endpoints.webhookWaiting') as string;
|
||||
const webhookTestBaseUrl = urlBaseWebhook + config.get('endpoints.webhookTest') as string;
|
||||
const webhookBaseUrl = urlBaseWebhook + config.get('endpoints.webhook');
|
||||
const webhookWaitingBaseUrl = urlBaseWebhook + config.get('endpoints.webhookWaiting');
|
||||
const webhookTestBaseUrl = urlBaseWebhook + config.get('endpoints.webhookTest');
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
if (encryptionKey === undefined) {
|
||||
|
@ -745,7 +971,7 @@ export async function getBase(currentNodeParameters?: INodeParameters, execution
|
|||
credentialsHelper: new CredentialsHelper(encryptionKey),
|
||||
encryptionKey,
|
||||
executeWorkflow,
|
||||
restApiUrl: urlBaseWebhook + config.get('endpoints.rest') as string,
|
||||
restApiUrl: urlBaseWebhook + config.get('endpoints.rest'),
|
||||
timezone,
|
||||
webhookBaseUrl,
|
||||
webhookWaitingBaseUrl,
|
||||
|
@ -755,12 +981,16 @@ export async function getBase(currentNodeParameters?: INodeParameters, execution
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns WorkflowHooks instance for running integrated workflows
|
||||
* (Workflows which get started inside of another workflow)
|
||||
*/
|
||||
export function getWorkflowHooksIntegrated(mode: WorkflowExecuteMode, executionId: string, workflowData: IWorkflowBase, optionalParameters?: IWorkflowHooksOptionalParameters): WorkflowHooks {
|
||||
export function getWorkflowHooksIntegrated(
|
||||
mode: WorkflowExecuteMode,
|
||||
executionId: string,
|
||||
workflowData: IWorkflowBase,
|
||||
optionalParameters?: IWorkflowHooksOptionalParameters,
|
||||
): WorkflowHooks {
|
||||
optionalParameters = optionalParameters || {};
|
||||
const hookFunctions = hookFunctionsSave(optionalParameters.parentProcessMode);
|
||||
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
||||
|
@ -777,7 +1007,12 @@ export function getWorkflowHooksIntegrated(mode: WorkflowExecuteMode, executionI
|
|||
* Returns WorkflowHooks instance for running integrated workflows
|
||||
* (Workflows which get started inside of another workflow)
|
||||
*/
|
||||
export function getWorkflowHooksWorkerExecuter(mode: WorkflowExecuteMode, executionId: string, workflowData: IWorkflowBase, optionalParameters?: IWorkflowHooksOptionalParameters): WorkflowHooks {
|
||||
export function getWorkflowHooksWorkerExecuter(
|
||||
mode: WorkflowExecuteMode,
|
||||
executionId: string,
|
||||
workflowData: IWorkflowBase,
|
||||
optionalParameters?: IWorkflowHooksOptionalParameters,
|
||||
): WorkflowHooks {
|
||||
optionalParameters = optionalParameters || {};
|
||||
const hookFunctions = hookFunctionsSaveWorker();
|
||||
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
||||
|
@ -793,7 +1028,12 @@ export function getWorkflowHooksWorkerExecuter(mode: WorkflowExecuteMode, execut
|
|||
/**
|
||||
* Returns WorkflowHooks instance for main process if workflow runs via worker
|
||||
*/
|
||||
export function getWorkflowHooksWorkerMain(mode: WorkflowExecuteMode, executionId: string, workflowData: IWorkflowBase, optionalParameters?: IWorkflowHooksOptionalParameters): WorkflowHooks {
|
||||
export function getWorkflowHooksWorkerMain(
|
||||
mode: WorkflowExecuteMode,
|
||||
executionId: string,
|
||||
workflowData: IWorkflowBase,
|
||||
optionalParameters?: IWorkflowHooksOptionalParameters,
|
||||
): WorkflowHooks {
|
||||
optionalParameters = optionalParameters || {};
|
||||
const hookFunctions = hookFunctionsPush();
|
||||
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
||||
|
@ -812,7 +1052,6 @@ export function getWorkflowHooksWorkerMain(mode: WorkflowExecuteMode, executionI
|
|||
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns WorkflowHooks instance for running the main workflow
|
||||
*
|
||||
|
@ -821,7 +1060,11 @@ export function getWorkflowHooksWorkerMain(mode: WorkflowExecuteMode, executionI
|
|||
* @param {string} executionId
|
||||
* @returns {WorkflowHooks}
|
||||
*/
|
||||
export function getWorkflowHooksMain(data: IWorkflowExecutionDataProcess, executionId: string, isMainProcess = false): WorkflowHooks {
|
||||
export function getWorkflowHooksMain(
|
||||
data: IWorkflowExecutionDataProcess,
|
||||
executionId: string,
|
||||
isMainProcess = false,
|
||||
): WorkflowHooks {
|
||||
const hookFunctions = hookFunctionsSave();
|
||||
const pushFunctions = hookFunctionsPush();
|
||||
for (const key of Object.keys(pushFunctions)) {
|
||||
|
@ -841,5 +1084,8 @@ export function getWorkflowHooksMain(data: IWorkflowExecutionDataProcess, execut
|
|||
}
|
||||
}
|
||||
|
||||
return new WorkflowHooks(hookFunctions, data.executionMode, executionId, data.workflowData, { sessionId: data.sessionId, retryOf: data.retryOf as string });
|
||||
return new WorkflowHooks(hookFunctions, data.executionMode, executionId, data.workflowData, {
|
||||
sessionId: data.sessionId,
|
||||
retryOf: data.retryOf as string,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,24 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-param-reassign */
|
||||
import {
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
INode,
|
||||
IRun,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
IWorkflowCredentials,
|
||||
LoggerProxy as Logger,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import { validate } from 'class-validator';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
CredentialTypes,
|
||||
Db,
|
||||
|
@ -7,28 +28,17 @@ import {
|
|||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
ResponseHelper,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowRunner,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
INode,
|
||||
IRun,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
IWorkflowCredentials,
|
||||
LoggerProxy as Logger,
|
||||
Workflow,} from 'n8n-workflow';
|
||||
} from '.';
|
||||
|
||||
import * as config from '../config';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
|
||||
import { validate } from 'class-validator';
|
||||
|
||||
const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data of the last executed node
|
||||
*
|
||||
|
@ -37,8 +47,8 @@ const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
|||
* @returns {(ITaskData | undefined)}
|
||||
*/
|
||||
export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefined {
|
||||
const runData = inputData.data.resultData.runData;
|
||||
const lastNodeExecuted = inputData.data.resultData.lastNodeExecuted;
|
||||
const { runData } = inputData.data.resultData;
|
||||
const { lastNodeExecuted } = inputData.data.resultData;
|
||||
|
||||
if (lastNodeExecuted === undefined) {
|
||||
return undefined;
|
||||
|
@ -51,8 +61,6 @@ export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefi
|
|||
return runData[lastNodeExecuted][runData[lastNodeExecuted].length - 1];
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given id is a valid workflow id
|
||||
*
|
||||
|
@ -60,20 +68,18 @@ export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefi
|
|||
* @returns {boolean}
|
||||
* @memberof App
|
||||
*/
|
||||
export function isWorkflowIdValid (id: string | null | undefined | number): boolean {
|
||||
export function isWorkflowIdValid(id: string | null | undefined | number): boolean {
|
||||
if (typeof id === 'string') {
|
||||
id = parseInt(id, 10);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (isNaN(id as number)) {
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the error workflow
|
||||
*
|
||||
|
@ -82,21 +88,37 @@ export function isWorkflowIdValid (id: string | null | undefined | number): bool
|
|||
* @param {IWorkflowErrorData} workflowErrorData The error data
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function executeErrorWorkflow(workflowId: string, workflowErrorData: IWorkflowErrorData): Promise<void> {
|
||||
export async function executeErrorWorkflow(
|
||||
workflowId: string,
|
||||
workflowErrorData: IWorkflowErrorData,
|
||||
): Promise<void> {
|
||||
// Wrap everything in try/catch to make sure that no errors bubble up and all get caught here
|
||||
try {
|
||||
const workflowData = await Db.collections.Workflow!.findOne({ id: Number(workflowId) });
|
||||
|
||||
if (workflowData === undefined) {
|
||||
// The error workflow could not be found
|
||||
Logger.error(`Calling Error Workflow for "${workflowErrorData.workflow.id}". Could not find error workflow "${workflowId}"`, { workflowId });
|
||||
Logger.error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Calling Error Workflow for "${workflowErrorData.workflow.id}". Could not find error workflow "${workflowId}"`,
|
||||
{ workflowId },
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const executionMode = 'error';
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
const workflowInstance = new Workflow({ id: workflowId, name: workflowData.name, nodeTypes, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, staticData: workflowData.staticData, settings: workflowData.settings});
|
||||
const workflowInstance = new Workflow({
|
||||
id: workflowId,
|
||||
name: workflowData.name,
|
||||
nodeTypes,
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: workflowData.active,
|
||||
staticData: workflowData.staticData,
|
||||
settings: workflowData.settings,
|
||||
});
|
||||
|
||||
let node: INode;
|
||||
let workflowStartNode: INode | undefined;
|
||||
|
@ -108,7 +130,9 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
|||
}
|
||||
|
||||
if (workflowStartNode === undefined) {
|
||||
Logger.error(`Calling Error Workflow for "${workflowErrorData.workflow.id}". Could not find "${ERROR_TRIGGER_TYPE}" in workflow "${workflowId}"`);
|
||||
Logger.error(
|
||||
`Calling Error Workflow for "${workflowErrorData.workflow.id}". Could not find "${ERROR_TRIGGER_TYPE}" in workflow "${workflowId}"`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -116,8 +140,7 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
|||
|
||||
// Initialize the data of the webhook node
|
||||
const nodeExecutionStack: IExecuteData[] = [];
|
||||
nodeExecutionStack.push(
|
||||
{
|
||||
nodeExecutionStack.push({
|
||||
node: workflowStartNode,
|
||||
data: {
|
||||
main: [
|
||||
|
@ -128,12 +151,10 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
|||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const runExecutionData: IRunExecutionData = {
|
||||
startData: {
|
||||
},
|
||||
startData: {},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
|
@ -153,12 +174,13 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
|||
const workflowRunner = new WorkflowRunner();
|
||||
await workflowRunner.run(runData);
|
||||
} catch (error) {
|
||||
Logger.error(`Calling Error Workflow for "${workflowErrorData.workflow.id}": "${error.message}"`, { workflowId: workflowErrorData.workflow.id });
|
||||
Logger.error(
|
||||
`Calling Error Workflow for "${workflowErrorData.workflow.id}": "${error.message}"`,
|
||||
{ workflowId: workflowErrorData.workflow.id },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the defined NodeTypes
|
||||
*
|
||||
|
@ -185,8 +207,6 @@ export function getAllNodeTypeData(): ITransferNodeTypes {
|
|||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data of the node types that are needed
|
||||
* to execute the given nodes
|
||||
|
@ -199,6 +219,7 @@ export function getNodeTypeData(nodes: INode[]): ITransferNodeTypes {
|
|||
const nodeTypes = NodeTypes();
|
||||
|
||||
// Check which node-types have to be loaded
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const neededNodeTypes = getNeededNodeTypes(nodes);
|
||||
|
||||
// Get all the data of the needed node types that they
|
||||
|
@ -218,8 +239,6 @@ export function getNodeTypeData(nodes: INode[]): ITransferNodeTypes {
|
|||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the credentials data of the given type and its parent types
|
||||
* it extends
|
||||
|
@ -251,8 +270,6 @@ export function getCredentialsDataWithParents(type: string): ICredentialsTypeDat
|
|||
return credentialTypeData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the credentialTypes which are needed to resolve
|
||||
* the given workflow credentials
|
||||
|
@ -262,14 +279,13 @@ export function getCredentialsDataWithParents(type: string): ICredentialsTypeDat
|
|||
* @returns {ICredentialsTypeData}
|
||||
*/
|
||||
export function getCredentialsDataByNodes(nodes: INode[]): ICredentialsTypeData {
|
||||
|
||||
const credentialTypeData: ICredentialsTypeData = {};
|
||||
|
||||
for (const node of nodes) {
|
||||
const credentialsUsedByThisNode = node.credentials;
|
||||
if (credentialsUsedByThisNode) {
|
||||
// const credentialTypesUsedByThisNode = Object.keys(credentialsUsedByThisNode!);
|
||||
for (const credentialType of Object.keys(credentialsUsedByThisNode!)) {
|
||||
for (const credentialType of Object.keys(credentialsUsedByThisNode)) {
|
||||
if (credentialTypeData[credentialType] !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
@ -277,14 +293,11 @@ export function getCredentialsDataByNodes(nodes: INode[]): ICredentialsTypeData
|
|||
Object.assign(credentialTypeData, getCredentialsDataWithParents(credentialType));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return credentialTypeData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the names of the NodeTypes which are are needed
|
||||
* to execute the gives nodes
|
||||
|
@ -305,8 +318,6 @@ export function getNeededNodeTypes(nodes: INode[]): string[] {
|
|||
return neededNodeTypes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Saves the static data if it changed
|
||||
*
|
||||
|
@ -314,23 +325,25 @@ export function getNeededNodeTypes(nodes: INode[]): string[] {
|
|||
* @param {Workflow} workflow
|
||||
* @returns {Promise <void>}
|
||||
*/
|
||||
export async function saveStaticData(workflow: Workflow): Promise <void> {
|
||||
export async function saveStaticData(workflow: Workflow): Promise<void> {
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed and so has to be saved
|
||||
if (isWorkflowIdValid(workflow.id) === true) {
|
||||
if (isWorkflowIdValid(workflow.id)) {
|
||||
// Workflow is saved so update in database
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
await saveStaticDataById(workflow.id!, workflow.staticData);
|
||||
workflow.staticData.__dataChanged = false;
|
||||
} catch (e) {
|
||||
Logger.error(`There was a problem saving the workflow with id "${workflow.id}" to save changed staticData: "${e.message}"`, { workflowId: workflow.id });
|
||||
Logger.error(
|
||||
`There was a problem saving the workflow with id "${workflow.id}" to save changed staticData: "${e.message}"`,
|
||||
{ workflowId: workflow.id },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Saves the given static data on workflow
|
||||
*
|
||||
|
@ -339,15 +352,15 @@ export async function saveStaticData(workflow: Workflow): Promise <void> {
|
|||
* @param {IDataObject} newStaticData The static data to save
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function saveStaticDataById(workflowId: string | number, newStaticData: IDataObject): Promise<void> {
|
||||
await Db.collections.Workflow!
|
||||
.update(workflowId, {
|
||||
export async function saveStaticDataById(
|
||||
workflowId: string | number,
|
||||
newStaticData: IDataObject,
|
||||
): Promise<void> {
|
||||
await Db.collections.Workflow!.update(workflowId, {
|
||||
staticData: newStaticData,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the static data of workflow
|
||||
*
|
||||
|
@ -355,20 +368,23 @@ export async function saveStaticDataById(workflowId: string | number, newStaticD
|
|||
* @param {(string | number)} workflowId The id of the workflow to get static data of
|
||||
* @returns
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export async function getStaticDataById(workflowId: string | number) {
|
||||
const workflowData = await Db.collections.Workflow!
|
||||
.findOne(workflowId, { select: ['staticData']});
|
||||
const workflowData = await Db.collections.Workflow!.findOne(workflowId, {
|
||||
select: ['staticData'],
|
||||
});
|
||||
|
||||
if (workflowData === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
return workflowData.staticData || {};
|
||||
}
|
||||
|
||||
|
||||
// TODO: Deduplicate `validateWorkflow` and `throwDuplicateEntryError` with TagHelpers?
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export async function validateWorkflow(newWorkflow: WorkflowEntity) {
|
||||
const errors = await validate(newWorkflow);
|
||||
|
||||
|
@ -378,10 +394,15 @@ export async function validateWorkflow(newWorkflow: WorkflowEntity) {
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function throwDuplicateEntryError(error: Error) {
|
||||
const errorMessage = error.message.toLowerCase();
|
||||
if (errorMessage.includes('unique') || errorMessage.includes('duplicate')) {
|
||||
throw new ResponseHelper.ResponseError('There is already a workflow with this name', undefined, 400);
|
||||
throw new ResponseHelper.ResponseError(
|
||||
'There is already a workflow with this name',
|
||||
undefined,
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
throw new ResponseHelper.ResponseError(errorMessage, undefined, 400);
|
||||
|
@ -391,6 +412,5 @@ export type WorkflowNameRequest = Express.Request & {
|
|||
query: {
|
||||
name?: string;
|
||||
offset?: string;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,37 @@
|
|||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable import/no-cycle */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { IProcessMessage, WorkflowExecute } from 'n8n-core';
|
||||
|
||||
import {
|
||||
ExecutionError,
|
||||
IRun,
|
||||
IWorkflowBase,
|
||||
LoggerProxy as Logger,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
WorkflowHooks,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
import { join as pathJoin } from 'path';
|
||||
import { fork } from 'child_process';
|
||||
|
||||
import * as Bull from 'bull';
|
||||
import * as config from '../config';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ActiveExecutions,
|
||||
CredentialsOverwrites,
|
||||
|
@ -20,38 +54,17 @@ import {
|
|||
ResponseHelper,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
IProcessMessage,
|
||||
WorkflowExecute,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ExecutionError,
|
||||
IRun,
|
||||
IWorkflowBase,
|
||||
LoggerProxy as Logger,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
WorkflowHooks,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as config from '../config';
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
import { join as pathJoin } from 'path';
|
||||
import { fork } from 'child_process';
|
||||
|
||||
import * as Bull from 'bull';
|
||||
} from '.';
|
||||
import * as Queue from './Queue';
|
||||
|
||||
export class WorkflowRunner {
|
||||
activeExecutions: ActiveExecutions.ActiveExecutions;
|
||||
credentialsOverwrites: ICredentialsOverwrite;
|
||||
push: Push.Push;
|
||||
jobQueue: Bull.Queue;
|
||||
|
||||
credentialsOverwrites: ICredentialsOverwrite;
|
||||
|
||||
push: Push.Push;
|
||||
|
||||
jobQueue: Bull.Queue;
|
||||
|
||||
constructor() {
|
||||
this.push = Push.getInstance();
|
||||
|
@ -65,7 +78,6 @@ export class WorkflowRunner {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The process did send a hook message so execute the appropiate hook
|
||||
*
|
||||
|
@ -74,10 +86,10 @@ export class WorkflowRunner {
|
|||
* @memberof WorkflowRunner
|
||||
*/
|
||||
processHookMessage(workflowHooks: WorkflowHooks, hookData: IProcessMessageDataHook) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The process did error
|
||||
*
|
||||
|
@ -87,7 +99,13 @@ export class WorkflowRunner {
|
|||
* @param {string} executionId
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async processError(error: ExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string, hooks?: WorkflowHooks) {
|
||||
async processError(
|
||||
error: ExecutionError,
|
||||
startedAt: Date,
|
||||
executionMode: WorkflowExecuteMode,
|
||||
executionId: string,
|
||||
hooks?: WorkflowHooks,
|
||||
) {
|
||||
const fullRunData: IRun = {
|
||||
data: {
|
||||
resultData: {
|
||||
|
@ -123,7 +141,12 @@ export class WorkflowRunner {
|
|||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, realtime?: boolean, executionId?: string): Promise<string> {
|
||||
async run(
|
||||
data: IWorkflowExecutionDataProcess,
|
||||
loadStaticData?: boolean,
|
||||
realtime?: boolean,
|
||||
executionId?: string,
|
||||
): Promise<string> {
|
||||
const executionsProcess = config.get('executions.process') as string;
|
||||
const executionsMode = config.get('executions.mode') as string;
|
||||
|
||||
|
@ -139,11 +162,12 @@ export class WorkflowRunner {
|
|||
|
||||
const externalHooks = ExternalHooks();
|
||||
if (externalHooks.exists('workflow.postExecute')) {
|
||||
this.activeExecutions.getPostExecutePromise(executionId)
|
||||
this.activeExecutions
|
||||
.getPostExecutePromise(executionId)
|
||||
.then(async (executionData) => {
|
||||
await externalHooks.run('workflow.postExecute', [executionData, data.workflowData]);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error('There was a problem running hook "workflow.postExecute"', error);
|
||||
});
|
||||
}
|
||||
|
@ -151,7 +175,6 @@ export class WorkflowRunner {
|
|||
return executionId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the workflow in current process
|
||||
*
|
||||
|
@ -161,9 +184,15 @@ export class WorkflowRunner {
|
|||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async runMainProcess(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string): Promise<string> {
|
||||
async runMainProcess(
|
||||
data: IWorkflowExecutionDataProcess,
|
||||
loadStaticData?: boolean,
|
||||
restartExecutionId?: string,
|
||||
): Promise<string> {
|
||||
if (loadStaticData === true && data.workflowData.id) {
|
||||
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(data.workflowData.id as string);
|
||||
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(
|
||||
data.workflowData.id as string,
|
||||
);
|
||||
}
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
|
@ -174,67 +203,120 @@ export class WorkflowRunner {
|
|||
let executionTimeout: NodeJS.Timeout;
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (data.workflowData.settings && data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
workflowTimeout = data.workflowData.settings.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
||||
}
|
||||
|
||||
const workflow = new Workflow({ id: data.workflowData.id as string | undefined, name: data.workflowData.name, nodes: data.workflowData!.nodes, connections: data.workflowData!.connections, active: data.workflowData!.active, nodeTypes, staticData: data.workflowData!.staticData });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
const workflow = new Workflow({
|
||||
id: data.workflowData.id as string | undefined,
|
||||
name: data.workflowData.name,
|
||||
nodes: data.workflowData.nodes,
|
||||
connections: data.workflowData.connections,
|
||||
active: data.workflowData.active,
|
||||
nodeTypes,
|
||||
staticData: data.workflowData.staticData,
|
||||
});
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(
|
||||
undefined,
|
||||
workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000,
|
||||
);
|
||||
|
||||
// Register the active execution
|
||||
const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId) as string;
|
||||
const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId);
|
||||
additionalData.executionId = executionId;
|
||||
|
||||
Logger.verbose(`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`, {executionId});
|
||||
Logger.verbose(
|
||||
`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`,
|
||||
{ executionId },
|
||||
);
|
||||
let workflowExecution: PCancelable<IRun>;
|
||||
|
||||
try {
|
||||
Logger.verbose(`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`, { executionId });
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId, true);
|
||||
additionalData.sendMessageToUI = WorkflowExecuteAdditionalData.sendMessageToUI.bind({sessionId: data.sessionId});
|
||||
Logger.verbose(
|
||||
`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`,
|
||||
{ executionId },
|
||||
);
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(
|
||||
data,
|
||||
executionId,
|
||||
true,
|
||||
);
|
||||
additionalData.sendMessageToUI = WorkflowExecuteAdditionalData.sendMessageToUI.bind({
|
||||
sessionId: data.sessionId,
|
||||
});
|
||||
|
||||
if (data.executionData !== undefined) {
|
||||
Logger.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, {executionId});
|
||||
const workflowExecute = new WorkflowExecute(additionalData, data.executionMode, data.executionData);
|
||||
Logger.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, {
|
||||
executionId,
|
||||
});
|
||||
const workflowExecute = new WorkflowExecute(
|
||||
additionalData,
|
||||
data.executionMode,
|
||||
data.executionData,
|
||||
);
|
||||
workflowExecution = workflowExecute.processRunExecutionData(workflow);
|
||||
} else if (data.runData === undefined || data.startNodes === undefined || data.startNodes.length === 0 || data.destinationNode === undefined) {
|
||||
Logger.debug(`Execution ID ${executionId} will run executing all nodes.`, {executionId});
|
||||
} else if (
|
||||
data.runData === undefined ||
|
||||
data.startNodes === undefined ||
|
||||
data.startNodes.length === 0 ||
|
||||
data.destinationNode === undefined
|
||||
) {
|
||||
Logger.debug(`Execution ID ${executionId} will run executing all nodes.`, { executionId });
|
||||
// Execute all nodes
|
||||
|
||||
// Can execute without webhook so go on
|
||||
const workflowExecute = new WorkflowExecute(additionalData, data.executionMode);
|
||||
workflowExecution = workflowExecute.run(workflow, undefined, data.destinationNode);
|
||||
} else {
|
||||
Logger.debug(`Execution ID ${executionId} is a partial execution.`, {executionId});
|
||||
Logger.debug(`Execution ID ${executionId} is a partial execution.`, { executionId });
|
||||
// Execute only the nodes between start and destination nodes
|
||||
const workflowExecute = new WorkflowExecute(additionalData, data.executionMode);
|
||||
workflowExecution = workflowExecute.runPartialWorkflow(workflow, data.runData, data.startNodes, data.destinationNode);
|
||||
workflowExecution = workflowExecute.runPartialWorkflow(
|
||||
workflow,
|
||||
data.runData,
|
||||
data.startNodes,
|
||||
data.destinationNode,
|
||||
);
|
||||
}
|
||||
|
||||
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
||||
|
||||
if (workflowTimeout > 0) {
|
||||
const timeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number) * 1000; // as seconds
|
||||
const timeout =
|
||||
Math.min(workflowTimeout, config.get('executions.maxTimeout') as number) * 1000; // as seconds
|
||||
executionTimeout = setTimeout(() => {
|
||||
this.activeExecutions.stopExecution(executionId, 'timeout');
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
workflowExecution.then((fullRunData) => {
|
||||
workflowExecution
|
||||
.then((fullRunData) => {
|
||||
clearTimeout(executionTimeout);
|
||||
if (workflowExecution.isCanceled) {
|
||||
fullRunData.finished = false;
|
||||
}
|
||||
this.activeExecutions.remove(executionId, fullRunData);
|
||||
}).catch((error) => {
|
||||
this.processError(error, new Date(), data.executionMode, executionId, additionalData.hooks);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.processError(
|
||||
error,
|
||||
new Date(),
|
||||
data.executionMode,
|
||||
executionId,
|
||||
additionalData.hooks,
|
||||
);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
await this.processError(error, new Date(), data.executionMode, executionId, additionalData.hooks);
|
||||
await this.processError(
|
||||
error,
|
||||
new Date(),
|
||||
data.executionMode,
|
||||
executionId,
|
||||
additionalData.hooks,
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
@ -242,8 +324,12 @@ export class WorkflowRunner {
|
|||
return executionId;
|
||||
}
|
||||
|
||||
async runBull(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, realtime?: boolean, restartExecutionId?: string): Promise<string> {
|
||||
|
||||
async runBull(
|
||||
data: IWorkflowExecutionDataProcess,
|
||||
loadStaticData?: boolean,
|
||||
realtime?: boolean,
|
||||
restartExecutionId?: string,
|
||||
): Promise<string> {
|
||||
// TODO: If "loadStaticData" is set to true it has to load data new on worker
|
||||
|
||||
// Register the active execution
|
||||
|
@ -271,9 +357,14 @@ export class WorkflowRunner {
|
|||
try {
|
||||
job = await this.jobQueue.add(jobData, jobOptions);
|
||||
|
||||
console.log('Started with ID: ' + job.id.toString());
|
||||
console.log(`Started with ID: ${job.id.toString()}`);
|
||||
|
||||
hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerMain(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined });
|
||||
hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerMain(
|
||||
data.executionMode,
|
||||
executionId,
|
||||
data.workflowData,
|
||||
{ retryOf: data.retryOf ? data.retryOf.toString() : undefined },
|
||||
);
|
||||
|
||||
// Normally also workflow should be supplied here but as it only used for sending
|
||||
// data to editor-UI is not needed.
|
||||
|
@ -281,19 +372,30 @@ export class WorkflowRunner {
|
|||
} catch (error) {
|
||||
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
||||
// "workflowExecuteAfter" which we require.
|
||||
const hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined });
|
||||
const hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(
|
||||
data.executionMode,
|
||||
executionId,
|
||||
data.workflowData,
|
||||
{ retryOf: data.retryOf ? data.retryOf.toString() : undefined },
|
||||
);
|
||||
await this.processError(error, new Date(), data.executionMode, executionId, hooks);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const workflowExecution: PCancelable<IRun> = new PCancelable(async (resolve, reject, onCancel) => {
|
||||
const workflowExecution: PCancelable<IRun> = new PCancelable(
|
||||
async (resolve, reject, onCancel) => {
|
||||
onCancel.shouldReject = false;
|
||||
onCancel(async () => {
|
||||
await Queue.getInstance().stopJob(job);
|
||||
|
||||
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
||||
// "workflowExecuteAfter" which we require.
|
||||
const hooksWorker = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined });
|
||||
const hooksWorker = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(
|
||||
data.executionMode,
|
||||
executionId,
|
||||
data.workflowData,
|
||||
{ retryOf: data.retryOf ? data.retryOf.toString() : undefined },
|
||||
);
|
||||
|
||||
const error = new WorkflowOperationError('Workflow-Execution has been canceled!');
|
||||
await this.processError(error, new Date(), data.executionMode, executionId, hooksWorker);
|
||||
|
@ -309,7 +411,7 @@ export class WorkflowRunner {
|
|||
|
||||
let clearWatchdogInterval;
|
||||
if (queueRecoveryInterval > 0) {
|
||||
/*************************************************
|
||||
/** ***********************************************
|
||||
* Long explanation about what this solves: *
|
||||
* This only happens in a very specific scenario *
|
||||
* when Redis crashes and recovers shortly *
|
||||
|
@ -320,7 +422,7 @@ export class WorkflowRunner {
|
|||
* the queue that allows us to identify that the *
|
||||
* execution finished and get information from *
|
||||
* the database. *
|
||||
*************************************************/
|
||||
************************************************ */
|
||||
let watchDogInterval: NodeJS.Timeout | undefined;
|
||||
|
||||
const watchDog: Promise<object> = new Promise((res) => {
|
||||
|
@ -329,7 +431,7 @@ export class WorkflowRunner {
|
|||
// When null means job is finished (not found in queue)
|
||||
if (currentJob === null) {
|
||||
// Mimic worker's success message
|
||||
res({success: true});
|
||||
res({ success: true });
|
||||
}
|
||||
}, queueRecoveryInterval * 1000);
|
||||
});
|
||||
|
@ -352,7 +454,12 @@ export class WorkflowRunner {
|
|||
} catch (error) {
|
||||
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
||||
// "workflowExecuteAfter" which we require.
|
||||
const hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined });
|
||||
const hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(
|
||||
data.executionMode,
|
||||
executionId,
|
||||
data.workflowData,
|
||||
{ retryOf: data.retryOf ? data.retryOf.toString() : undefined },
|
||||
);
|
||||
Logger.error(`Problem with execution ${executionId}: ${error.message}. Aborting.`);
|
||||
if (clearWatchdogInterval !== undefined) {
|
||||
clearWatchdogInterval();
|
||||
|
@ -362,8 +469,10 @@ export class WorkflowRunner {
|
|||
reject(error);
|
||||
}
|
||||
|
||||
const executionDb = await Db.collections.Execution!.findOne(executionId) as IExecutionFlattedDb;
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb) as IExecutionResponse;
|
||||
const executionDb = (await Db.collections.Execution!.findOne(
|
||||
executionId,
|
||||
)) as IExecutionFlattedDb;
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb);
|
||||
const runData = {
|
||||
data: fullExecutionData.data,
|
||||
finished: fullExecutionData.finished,
|
||||
|
@ -382,29 +491,35 @@ export class WorkflowRunner {
|
|||
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
||||
if (data.workflowData.settings !== undefined) {
|
||||
saveDataErrorExecution = (data.workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
||||
saveDataSuccessExecution = (data.workflowData.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
|
||||
saveDataErrorExecution =
|
||||
(data.workflowData.settings.saveDataErrorExecution as string) ||
|
||||
saveDataErrorExecution;
|
||||
saveDataSuccessExecution =
|
||||
(data.workflowData.settings.saveDataSuccessExecution as string) ||
|
||||
saveDataSuccessExecution;
|
||||
}
|
||||
|
||||
const workflowDidSucceed = !runData.data.resultData.error;
|
||||
if (workflowDidSucceed === true && saveDataSuccessExecution === 'none' ||
|
||||
workflowDidSucceed === false && saveDataErrorExecution === 'none'
|
||||
if (
|
||||
(workflowDidSucceed && saveDataSuccessExecution === 'none') ||
|
||||
(!workflowDidSucceed && saveDataErrorExecution === 'none')
|
||||
) {
|
||||
await Db.collections.Execution!.delete(executionId);
|
||||
}
|
||||
// eslint-disable-next-line id-denylist
|
||||
} catch (err) {
|
||||
// We don't want errors here to crash n8n. Just log and proceed.
|
||||
console.log('Error removing saved execution from database. More details: ', err);
|
||||
}
|
||||
|
||||
resolve(runData);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
||||
return executionId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the workflow
|
||||
*
|
||||
|
@ -414,12 +529,18 @@ export class WorkflowRunner {
|
|||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async runSubprocess(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string): Promise<string> {
|
||||
async runSubprocess(
|
||||
data: IWorkflowExecutionDataProcess,
|
||||
loadStaticData?: boolean,
|
||||
restartExecutionId?: string,
|
||||
): Promise<string> {
|
||||
let startedAt = new Date();
|
||||
const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js'));
|
||||
|
||||
if (loadStaticData === true && data.workflowData.id) {
|
||||
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(data.workflowData.id as string);
|
||||
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(
|
||||
data.workflowData.id as string,
|
||||
);
|
||||
}
|
||||
|
||||
// Register the active execution
|
||||
|
@ -437,8 +558,9 @@ export class WorkflowRunner {
|
|||
}
|
||||
let nodeTypeData: ITransferNodeTypes;
|
||||
let credentialTypeData: ICredentialsTypeData;
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
let credentialsOverwrites = this.credentialsOverwrites;
|
||||
if (loadAllNodeTypes === true) {
|
||||
if (loadAllNodeTypes) {
|
||||
// Supply all nodeTypes and credentialTypes
|
||||
nodeTypeData = WorkflowHelpers.getAllNodeTypeData();
|
||||
const credentialTypes = CredentialTypes();
|
||||
|
@ -458,8 +580,10 @@ export class WorkflowRunner {
|
|||
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = this.credentialsOverwrites;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData = credentialTypeData;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite =
|
||||
this.credentialsOverwrites;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData =
|
||||
credentialTypeData;
|
||||
|
||||
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
||||
|
||||
|
@ -475,7 +599,7 @@ export class WorkflowRunner {
|
|||
let executionTimeout: NodeJS.Timeout;
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (data.workflowData.settings && data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
workflowTimeout = data.workflowData.settings.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
const processTimeoutFunction = (timeout: number) => {
|
||||
|
@ -484,11 +608,16 @@ export class WorkflowRunner {
|
|||
};
|
||||
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number) * 1000; // as seconds
|
||||
workflowTimeout =
|
||||
Math.min(workflowTimeout, config.get('executions.maxTimeout') as number) * 1000; // as seconds
|
||||
// Start timeout already now but give process at least 5 seconds to start.
|
||||
// Without it could would it be possible that the workflow executions times out before it even got started if
|
||||
// the timeout time is very short as the process start time can be quite long.
|
||||
executionTimeout = setTimeout(processTimeoutFunction, Math.max(5000, workflowTimeout), workflowTimeout);
|
||||
executionTimeout = setTimeout(
|
||||
processTimeoutFunction,
|
||||
Math.max(5000, workflowTimeout),
|
||||
workflowTimeout,
|
||||
);
|
||||
}
|
||||
|
||||
// Create a list of child spawned executions
|
||||
|
@ -498,7 +627,10 @@ export class WorkflowRunner {
|
|||
|
||||
// Listen to data from the subprocess
|
||||
subprocess.on('message', async (message: IProcessMessage) => {
|
||||
Logger.debug(`Received child process message of type ${message.type} for execution ID ${executionId}.`, {executionId});
|
||||
Logger.debug(
|
||||
`Received child process message of type ${message.type} for execution ID ${executionId}.`,
|
||||
{ executionId },
|
||||
);
|
||||
if (message.type === 'start') {
|
||||
// Now that the execution actually started set the timeout again so that does not time out to early.
|
||||
startedAt = new Date();
|
||||
|
@ -506,18 +638,25 @@ export class WorkflowRunner {
|
|||
clearTimeout(executionTimeout);
|
||||
executionTimeout = setTimeout(processTimeoutFunction, workflowTimeout, workflowTimeout);
|
||||
}
|
||||
|
||||
} else if (message.type === 'end') {
|
||||
clearTimeout(executionTimeout);
|
||||
this.activeExecutions.remove(executionId!, message.data.runData);
|
||||
|
||||
this.activeExecutions.remove(executionId, message.data.runData);
|
||||
} else if (message.type === 'sendMessageToUI') {
|
||||
WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId })(message.data.source, message.data.message);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId })(
|
||||
message.data.source,
|
||||
message.data.message,
|
||||
);
|
||||
} else if (message.type === 'processError') {
|
||||
clearTimeout(executionTimeout);
|
||||
const executionError = message.data.executionError as ExecutionError;
|
||||
await this.processError(executionError, startedAt, data.executionMode, executionId, workflowHooks);
|
||||
await this.processError(
|
||||
executionError,
|
||||
startedAt,
|
||||
data.executionMode,
|
||||
executionId,
|
||||
workflowHooks,
|
||||
);
|
||||
} else if (message.type === 'processHook') {
|
||||
this.processHookMessage(workflowHooks, message.data as IProcessMessageDataHook);
|
||||
} else if (message.type === 'timeout') {
|
||||
|
@ -529,43 +668,61 @@ export class WorkflowRunner {
|
|||
} else if (message.type === 'startExecution') {
|
||||
const executionId = await this.activeExecutions.add(message.data.runData);
|
||||
childExecutionIds.push(executionId);
|
||||
subprocess.send({ type: 'executionId', data: {executionId} } as IProcessMessage);
|
||||
subprocess.send({ type: 'executionId', data: { executionId } } as IProcessMessage);
|
||||
} else if (message.type === 'finishExecution') {
|
||||
const executionIdIndex = childExecutionIds.indexOf(message.data.executionId);
|
||||
if (executionIdIndex !== -1) {
|
||||
childExecutionIds.splice(executionIdIndex, 1);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await this.activeExecutions.remove(message.data.executionId, message.data.result);
|
||||
}
|
||||
});
|
||||
|
||||
// Also get informed when the processes does exit especially when it did crash or timed out
|
||||
subprocess.on('exit', async (code, signal) => {
|
||||
if (signal === 'SIGTERM'){
|
||||
Logger.debug(`Subprocess for execution ID ${executionId} timed out.`, {executionId});
|
||||
if (signal === 'SIGTERM') {
|
||||
Logger.debug(`Subprocess for execution ID ${executionId} timed out.`, { executionId });
|
||||
// Execution timed out and its process has been terminated
|
||||
const timeoutError = new WorkflowOperationError('Workflow execution timed out!');
|
||||
|
||||
await this.processError(timeoutError, startedAt, data.executionMode, executionId, workflowHooks);
|
||||
await this.processError(
|
||||
timeoutError,
|
||||
startedAt,
|
||||
data.executionMode,
|
||||
executionId,
|
||||
workflowHooks,
|
||||
);
|
||||
} else if (code !== 0) {
|
||||
Logger.debug(`Subprocess for execution ID ${executionId} finished with error code ${code}.`, {executionId});
|
||||
Logger.debug(
|
||||
`Subprocess for execution ID ${executionId} finished with error code ${code}.`,
|
||||
{ executionId },
|
||||
);
|
||||
// Process did exit with error code, so something went wrong.
|
||||
const executionError = new WorkflowOperationError('Workflow execution process did crash for an unknown reason!');
|
||||
const executionError = new WorkflowOperationError(
|
||||
'Workflow execution process did crash for an unknown reason!',
|
||||
);
|
||||
|
||||
await this.processError(executionError, startedAt, data.executionMode, executionId, workflowHooks);
|
||||
await this.processError(
|
||||
executionError,
|
||||
startedAt,
|
||||
data.executionMode,
|
||||
executionId,
|
||||
workflowHooks,
|
||||
);
|
||||
}
|
||||
|
||||
for(const executionId of childExecutionIds) {
|
||||
for (const executionId of childExecutionIds) {
|
||||
// When the child process exits, if we still have
|
||||
// pending child executions, we mark them as finished
|
||||
// They will display as unknown to the user
|
||||
// Instead of pending forever as executing when it
|
||||
// actually isn't anymore.
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable, no-await-in-loop
|
||||
await this.activeExecutions.remove(executionId);
|
||||
}
|
||||
|
||||
|
||||
clearTimeout(executionTimeout);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
|
||||
import {
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcessWithExecution,
|
||||
NodeTypes,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
IProcessMessage,
|
||||
WorkflowExecute,
|
||||
} from 'n8n-core';
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable consistent-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { IProcessMessage, WorkflowExecute } from 'n8n-core';
|
||||
|
||||
import {
|
||||
ExecutionError,
|
||||
|
@ -34,24 +25,41 @@ import {
|
|||
WorkflowHooks,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcessWithExecution,
|
||||
NodeTypes,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
} from '.';
|
||||
|
||||
import { getLogger } from './Logger';
|
||||
|
||||
import * as config from '../config';
|
||||
|
||||
export class WorkflowRunnerProcess {
|
||||
data: IWorkflowExecutionDataProcessWithExecution | undefined;
|
||||
|
||||
logger: ILogger;
|
||||
|
||||
startedAt = new Date();
|
||||
|
||||
workflow: Workflow | undefined;
|
||||
|
||||
workflowExecute: WorkflowExecute | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||
executionIdCallback: (executionId: string) => void | undefined;
|
||||
|
||||
childExecutions: {
|
||||
[key: string]: IWorkflowExecuteProcess,
|
||||
[key: string]: IWorkflowExecuteProcess;
|
||||
} = {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static async stopProcess() {
|
||||
setTimeout(() => {
|
||||
// Attempt a graceful shutdown, giving executions 30 seconds to finish
|
||||
|
@ -59,17 +67,20 @@ export class WorkflowRunnerProcess {
|
|||
}, 30000);
|
||||
}
|
||||
|
||||
|
||||
async runWorkflow(inputData: IWorkflowExecutionDataProcessWithExecution): Promise<IRun> {
|
||||
process.on('SIGTERM', WorkflowRunnerProcess.stopProcess);
|
||||
process.on('SIGINT', WorkflowRunnerProcess.stopProcess);
|
||||
|
||||
const logger = this.logger = getLogger();
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
const logger = (this.logger = getLogger());
|
||||
LoggerProxy.init(logger);
|
||||
|
||||
this.data = inputData;
|
||||
|
||||
logger.verbose('Initializing n8n sub-process', { pid: process.pid, workflowId: this.data.workflowData.id });
|
||||
logger.verbose('Initializing n8n sub-process', {
|
||||
pid: process.pid,
|
||||
workflowId: this.data.workflowData.id,
|
||||
});
|
||||
|
||||
let className: string;
|
||||
let tempNode: INodeType;
|
||||
|
@ -78,13 +89,16 @@ export class WorkflowRunnerProcess {
|
|||
this.startedAt = new Date();
|
||||
|
||||
const nodeTypesData: INodeTypeData = {};
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const nodeTypeName of Object.keys(this.data.nodeTypeData)) {
|
||||
className = this.data.nodeTypeData[nodeTypeName].className;
|
||||
|
||||
filePath = this.data.nodeTypeData[nodeTypeName].sourcePath;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
|
||||
const tempModule = require(filePath);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
tempNode = new tempModule[className]() as INodeType;
|
||||
} catch (error) {
|
||||
throw new Error(`Error loading node "${nodeTypeName}" from: "${filePath}"`);
|
||||
|
@ -115,7 +129,8 @@ export class WorkflowRunnerProcess {
|
|||
// We check if any node uses credentials. If it does, then
|
||||
// init database.
|
||||
let shouldInitializaDb = false;
|
||||
inputData.workflowData.nodes.map(node => {
|
||||
// eslint-disable-next-line array-callback-return
|
||||
inputData.workflowData.nodes.map((node) => {
|
||||
if (Object.keys(node.credentials === undefined ? {} : node.credentials).length > 0) {
|
||||
shouldInitializaDb = true;
|
||||
}
|
||||
|
@ -126,45 +141,77 @@ export class WorkflowRunnerProcess {
|
|||
if (shouldInitializaDb) {
|
||||
// initialize db as we need to load credentials
|
||||
await Db.init();
|
||||
} else if (inputData.workflowData.settings !== undefined && inputData.workflowData.settings.saveExecutionProgress === true) {
|
||||
} else if (
|
||||
inputData.workflowData.settings !== undefined &&
|
||||
inputData.workflowData.settings.saveExecutionProgress === true
|
||||
) {
|
||||
// Workflow settings specifying it should save
|
||||
await Db.init();
|
||||
} else if (inputData.workflowData.settings !== undefined && inputData.workflowData.settings.saveExecutionProgress !== false && config.get('executions.saveExecutionProgress') as boolean) {
|
||||
} else if (
|
||||
inputData.workflowData.settings !== undefined &&
|
||||
inputData.workflowData.settings.saveExecutionProgress !== false &&
|
||||
(config.get('executions.saveExecutionProgress') as boolean)
|
||||
) {
|
||||
// Workflow settings not saying anything about saving but default settings says so
|
||||
await Db.init();
|
||||
} else if (inputData.workflowData.settings === undefined && config.get('executions.saveExecutionProgress') as boolean) {
|
||||
} else if (
|
||||
inputData.workflowData.settings === undefined &&
|
||||
(config.get('executions.saveExecutionProgress') as boolean)
|
||||
) {
|
||||
// Workflow settings not saying anything about saving but default settings says so
|
||||
await Db.init();
|
||||
}
|
||||
|
||||
// Start timeout for the execution
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
if (this.data.workflowData.settings && this.data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = this.data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
workflowTimeout = this.data.workflowData.settings.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
||||
}
|
||||
|
||||
this.workflow = new Workflow({ id: this.data.workflowData.id as string | undefined, name: this.data.workflowData.name, nodes: this.data.workflowData!.nodes, connections: this.data.workflowData!.connections, active: this.data.workflowData!.active, nodeTypes, staticData: this.data.workflowData!.staticData, settings: this.data.workflowData!.settings });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
this.workflow = new Workflow({
|
||||
id: this.data.workflowData.id as string | undefined,
|
||||
name: this.data.workflowData.name,
|
||||
nodes: this.data.workflowData.nodes,
|
||||
connections: this.data.workflowData.connections,
|
||||
active: this.data.workflowData.active,
|
||||
nodeTypes,
|
||||
staticData: this.data.workflowData.staticData,
|
||||
settings: this.data.workflowData.settings,
|
||||
});
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(
|
||||
undefined,
|
||||
workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000,
|
||||
);
|
||||
additionalData.hooks = this.getProcessForwardHooks();
|
||||
additionalData.executionId = inputData.executionId;
|
||||
|
||||
additionalData.sendMessageToUI = async (source: string, message: any) => { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
additionalData.sendMessageToUI = async (source: string, message: any) => {
|
||||
if (workflowRunner.data!.executionMode !== 'manual') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
await sendToParentProcess('sendMessageToUI', { source, message });
|
||||
} catch (error) {
|
||||
this.logger.error(`There was a problem sending UI data to parent process: "${error.message}"`);
|
||||
this.logger.error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
`There was a problem sending UI data to parent process: "${error.message}"`,
|
||||
);
|
||||
}
|
||||
};
|
||||
const executeWorkflowFunction = additionalData.executeWorkflow;
|
||||
additionalData.executeWorkflow = async (workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[] | undefined): Promise<Array<INodeExecutionData[] | null> | IRun> => {
|
||||
additionalData.executeWorkflow = async (
|
||||
workflowInfo: IExecuteWorkflowInfo,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
inputData?: INodeExecutionData[] | undefined,
|
||||
): Promise<Array<INodeExecutionData[] | null> | IRun> => {
|
||||
const workflowData = await WorkflowExecuteAdditionalData.getWorkflowData(workflowInfo);
|
||||
const runData = await WorkflowExecuteAdditionalData.getRunData(workflowData, inputData);
|
||||
await sendToParentProcess('startExecution', { runData });
|
||||
|
@ -175,11 +222,18 @@ export class WorkflowRunnerProcess {
|
|||
});
|
||||
let result: IRun;
|
||||
try {
|
||||
const executeWorkflowFunctionOutput = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData) as {workflowExecute: WorkflowExecute, workflow: Workflow} as IWorkflowExecuteProcess;
|
||||
const workflowExecute = executeWorkflowFunctionOutput.workflowExecute;
|
||||
const executeWorkflowFunctionOutput = (await executeWorkflowFunction(
|
||||
workflowInfo,
|
||||
additionalData,
|
||||
inputData,
|
||||
executionId,
|
||||
workflowData,
|
||||
runData,
|
||||
)) as { workflowExecute: WorkflowExecute; workflow: Workflow } as IWorkflowExecuteProcess;
|
||||
const { workflowExecute } = executeWorkflowFunctionOutput;
|
||||
this.childExecutions[executionId] = executeWorkflowFunctionOutput;
|
||||
const workflow = executeWorkflowFunctionOutput.workflow;
|
||||
result = await workflowExecute.processRunExecutionData(workflow) as IRun;
|
||||
const { workflow } = executeWorkflowFunctionOutput;
|
||||
result = await workflowExecute.processRunExecutionData(workflow);
|
||||
await externalHooks.run('workflow.postExecute', [result, workflowData]);
|
||||
await sendToParentProcess('finishExecution', { executionId, result });
|
||||
delete this.childExecutions[executionId];
|
||||
|
@ -197,21 +251,34 @@ export class WorkflowRunnerProcess {
|
|||
};
|
||||
|
||||
if (this.data.executionData !== undefined) {
|
||||
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode, this.data.executionData);
|
||||
this.workflowExecute = new WorkflowExecute(
|
||||
additionalData,
|
||||
this.data.executionMode,
|
||||
this.data.executionData,
|
||||
);
|
||||
return this.workflowExecute.processRunExecutionData(this.workflow);
|
||||
} else if (this.data.runData === undefined || this.data.startNodes === undefined || this.data.startNodes.length === 0 || this.data.destinationNode === undefined) {
|
||||
}
|
||||
if (
|
||||
this.data.runData === undefined ||
|
||||
this.data.startNodes === undefined ||
|
||||
this.data.startNodes.length === 0 ||
|
||||
this.data.destinationNode === undefined
|
||||
) {
|
||||
// Execute all nodes
|
||||
|
||||
// Can execute without webhook so go on
|
||||
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode);
|
||||
return this.workflowExecute.run(this.workflow, undefined, this.data.destinationNode);
|
||||
} else {
|
||||
}
|
||||
// Execute only the nodes between start and destination nodes
|
||||
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode);
|
||||
return this.workflowExecute.runPartialWorkflow(this.workflow, this.data.runData, this.data.startNodes, this.data.destinationNode);
|
||||
return this.workflowExecute.runPartialWorkflow(
|
||||
this.workflow,
|
||||
this.data.runData,
|
||||
this.data.startNodes,
|
||||
this.data.destinationNode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends hook data to the parent process that it executes them
|
||||
|
@ -220,18 +287,18 @@ export class WorkflowRunnerProcess {
|
|||
* @param {any[]} parameters
|
||||
* @memberof WorkflowRunnerProcess
|
||||
*/
|
||||
async sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
async sendHookToParentProcess(hook: string, parameters: any[]) {
|
||||
try {
|
||||
await sendToParentProcess('processHook', {
|
||||
hook,
|
||||
parameters,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(`There was a problem sending hook: "${hook}"`, { parameters, error});
|
||||
this.logger.error(`There was a problem sending hook: "${hook}"`, { parameters, error });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a wrapper for hooks which simply forwards the data to
|
||||
* the parent process where they then can be executed with access
|
||||
|
@ -264,6 +331,7 @@ export class WorkflowRunnerProcess {
|
|||
};
|
||||
|
||||
const preExecuteFunctions = WorkflowExecuteAdditionalData.hookFunctionsPreExecute();
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of Object.keys(preExecuteFunctions)) {
|
||||
if (hookFunctions[key] === undefined) {
|
||||
hookFunctions[key] = [];
|
||||
|
@ -271,13 +339,16 @@ export class WorkflowRunnerProcess {
|
|||
hookFunctions[key]!.push.apply(hookFunctions[key], preExecuteFunctions[key]);
|
||||
}
|
||||
|
||||
return new WorkflowHooks(hookFunctions, this.data!.executionMode, this.data!.executionId, this.data!.workflowData, { sessionId: this.data!.sessionId, retryOf: this.data!.retryOf as string });
|
||||
return new WorkflowHooks(
|
||||
hookFunctions,
|
||||
this.data!.executionMode,
|
||||
this.data!.executionId,
|
||||
this.data!.workflowData,
|
||||
{ sessionId: this.data!.sessionId, retryOf: this.data!.retryOf as string },
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sends data to parent process
|
||||
*
|
||||
|
@ -285,25 +356,27 @@ export class WorkflowRunnerProcess {
|
|||
* @param {*} data The data
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendToParentProcess(type: string, data: any): Promise<void> { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async function sendToParentProcess(type: string, data: any): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
process.send!({
|
||||
process.send!(
|
||||
{
|
||||
type,
|
||||
data,
|
||||
}, (error: Error) => {
|
||||
},
|
||||
(error: Error) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const workflowRunner = new WorkflowRunnerProcess();
|
||||
|
||||
|
||||
// Listen to messages from parent process which send the data of
|
||||
// the worflow to process
|
||||
process.on('message', async (message: IProcessMessage) => {
|
||||
|
@ -324,25 +397,42 @@ process.on('message', async (message: IProcessMessage) => {
|
|||
let runData: IRun;
|
||||
|
||||
if (workflowRunner.workflowExecute !== undefined) {
|
||||
|
||||
const executionIds = Object.keys(workflowRunner.childExecutions);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const executionId of executionIds) {
|
||||
const childWorkflowExecute = workflowRunner.childExecutions[executionId];
|
||||
runData = childWorkflowExecute.workflowExecute.getFullRunData(workflowRunner.childExecutions[executionId].startedAt);
|
||||
const timeOutError = message.type === 'timeout' ? new WorkflowOperationError('Workflow execution timed out!') : new WorkflowOperationError('Workflow-Execution has been canceled!');
|
||||
runData = childWorkflowExecute.workflowExecute.getFullRunData(
|
||||
workflowRunner.childExecutions[executionId].startedAt,
|
||||
);
|
||||
const timeOutError =
|
||||
message.type === 'timeout'
|
||||
? new WorkflowOperationError('Workflow execution timed out!')
|
||||
: new WorkflowOperationError('Workflow-Execution has been canceled!');
|
||||
|
||||
// If there is any data send it to parent process, if execution timedout add the error
|
||||
await childWorkflowExecute.workflowExecute.processSuccessExecution(workflowRunner.childExecutions[executionId].startedAt, childWorkflowExecute.workflow, timeOutError);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await childWorkflowExecute.workflowExecute.processSuccessExecution(
|
||||
workflowRunner.childExecutions[executionId].startedAt,
|
||||
childWorkflowExecute.workflow,
|
||||
timeOutError,
|
||||
);
|
||||
}
|
||||
|
||||
// Workflow started already executing
|
||||
runData = workflowRunner.workflowExecute.getFullRunData(workflowRunner.startedAt);
|
||||
|
||||
const timeOutError = message.type === 'timeout' ? new WorkflowOperationError('Workflow execution timed out!') : new WorkflowOperationError('Workflow-Execution has been canceled!');
|
||||
const timeOutError =
|
||||
message.type === 'timeout'
|
||||
? new WorkflowOperationError('Workflow execution timed out!')
|
||||
: new WorkflowOperationError('Workflow-Execution has been canceled!');
|
||||
|
||||
// If there is any data send it to parent process, if execution timedout add the error
|
||||
await workflowRunner.workflowExecute.processSuccessExecution(workflowRunner.startedAt, workflowRunner.workflow!, timeOutError);
|
||||
await workflowRunner.workflowExecute.processSuccessExecution(
|
||||
workflowRunner.startedAt,
|
||||
workflowRunner.workflow!,
|
||||
timeOutError,
|
||||
);
|
||||
} else {
|
||||
// Workflow did not get started yet
|
||||
runData = {
|
||||
|
@ -352,11 +442,14 @@ process.on('message', async (message: IProcessMessage) => {
|
|||
},
|
||||
},
|
||||
finished: false,
|
||||
mode: workflowRunner.data ? workflowRunner.data!.executionMode : 'own' as WorkflowExecuteMode,
|
||||
mode: workflowRunner.data
|
||||
? workflowRunner.data.executionMode
|
||||
: ('own' as WorkflowExecuteMode),
|
||||
startedAt: workflowRunner.startedAt,
|
||||
stoppedAt: new Date(),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
workflowRunner.sendHookToParentProcess('workflowExecuteAfter', [runData]);
|
||||
}
|
||||
|
||||
|
@ -367,16 +460,16 @@ process.on('message', async (message: IProcessMessage) => {
|
|||
// Stop process
|
||||
process.exit();
|
||||
} else if (message.type === 'executionId') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
workflowRunner.executionIdCallback(message.data.executionId);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
// Catch all uncaught errors and forward them to parent process
|
||||
const executionError = {
|
||||
...error,
|
||||
name: error!.name || 'Error',
|
||||
message: error!.message,
|
||||
stack: error!.stack,
|
||||
name: error.name || 'Error',
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
} as ExecutionError;
|
||||
|
||||
await sendToParentProcess('processError', {
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
import {
|
||||
ICredentialNodeAccess,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getTimestampSyntax,
|
||||
resolveDataType
|
||||
} from '../utils';
|
||||
|
||||
import {
|
||||
ICredentialsDb,
|
||||
} from '../..';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { ICredentialNodeAccess } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
BeforeUpdate,
|
||||
|
@ -20,10 +11,12 @@ import {
|
|||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { getTimestampSyntax, resolveDataType } from '../utils';
|
||||
|
||||
import { ICredentialsDb } from '../..';
|
||||
|
||||
@Entity()
|
||||
export class CredentialsEntity implements ICredentialsDb {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
|
@ -47,7 +40,11 @@ export class CredentialsEntity implements ICredentialsDb {
|
|||
@CreateDateColumn({ precision: 3, default: () => getTimestampSyntax() })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ precision: 3, default: () => getTimestampSyntax(), onUpdate: getTimestampSyntax() })
|
||||
@UpdateDateColumn({
|
||||
precision: 3,
|
||||
default: () => getTimestampSyntax(),
|
||||
onUpdate: getTimestampSyntax(),
|
||||
})
|
||||
updatedAt: Date;
|
||||
|
||||
@BeforeUpdate()
|
||||
|
|
|
@ -1,27 +1,13 @@
|
|||
import {
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { WorkflowExecuteMode } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
IExecutionFlattedDb,
|
||||
IWorkflowDb,
|
||||
} from '../../';
|
||||
import { Column, ColumnOptions, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { IExecutionFlattedDb, IWorkflowDb } from '../..';
|
||||
|
||||
import {
|
||||
resolveDataType
|
||||
} from '../utils';
|
||||
|
||||
import {
|
||||
Column,
|
||||
ColumnOptions,
|
||||
Entity,
|
||||
Index,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { resolveDataType } from '../utils';
|
||||
|
||||
@Entity()
|
||||
export class ExecutionEntity implements IExecutionFlattedDb {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
import { BeforeUpdate, Column, CreateDateColumn, Entity, Index, ManyToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable import/no-cycle */
|
||||
import {
|
||||
BeforeUpdate,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
ManyToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { IsDate, IsOptional, IsString, Length } from 'class-validator';
|
||||
|
||||
import { ITagDb } from '../../Interfaces';
|
||||
|
@ -7,7 +18,6 @@ import { getTimestampSyntax } from '../utils';
|
|||
|
||||
@Entity()
|
||||
export class TagEntity implements ITagDb {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
|
@ -22,12 +32,16 @@ export class TagEntity implements ITagDb {
|
|||
@IsDate()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ precision: 3, default: () => getTimestampSyntax(), onUpdate: getTimestampSyntax() })
|
||||
@UpdateDateColumn({
|
||||
precision: 3,
|
||||
default: () => getTimestampSyntax(),
|
||||
onUpdate: getTimestampSyntax(),
|
||||
})
|
||||
@IsOptional() // ignored by validation because set at DB level
|
||||
@IsDate()
|
||||
updatedAt: Date;
|
||||
|
||||
@ManyToMany(() => WorkflowEntity, workflow => workflow.tags)
|
||||
@ManyToMany(() => WorkflowEntity, (workflow) => workflow.tags)
|
||||
workflows: WorkflowEntity[];
|
||||
|
||||
@BeforeUpdate()
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
PrimaryColumn,
|
||||
} from 'typeorm';
|
||||
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import {
|
||||
IWebhookDb,
|
||||
} from '../../Interfaces';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { IWebhookDb } from '../../Interfaces';
|
||||
|
||||
@Entity()
|
||||
@Index(['webhookId', 'method', 'pathLength'])
|
||||
export class WebhookEntity implements IWebhookDb {
|
||||
|
||||
@Column()
|
||||
workflowId: number;
|
||||
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import {
|
||||
Length,
|
||||
} from 'class-validator';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { Length } from 'class-validator';
|
||||
|
||||
import {
|
||||
IConnections,
|
||||
IDataObject,
|
||||
INode,
|
||||
IWorkflowSettings,
|
||||
} from 'n8n-workflow';
|
||||
import { IConnections, IDataObject, INode, IWorkflowSettings } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
BeforeUpdate,
|
||||
|
@ -22,22 +17,14 @@ import {
|
|||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import {
|
||||
IWorkflowDb,
|
||||
} from '../../';
|
||||
import { IWorkflowDb } from '../..';
|
||||
|
||||
import {
|
||||
getTimestampSyntax,
|
||||
resolveDataType
|
||||
} from '../utils';
|
||||
import { getTimestampSyntax, resolveDataType } from '../utils';
|
||||
|
||||
import {
|
||||
TagEntity,
|
||||
} from './TagEntity';
|
||||
import { TagEntity } from './TagEntity';
|
||||
|
||||
@Entity()
|
||||
export class WorkflowEntity implements IWorkflowDb {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
|
@ -58,7 +45,11 @@ export class WorkflowEntity implements IWorkflowDb {
|
|||
@CreateDateColumn({ precision: 3, default: () => getTimestampSyntax() })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ precision: 3, default: () => getTimestampSyntax(), onUpdate: getTimestampSyntax() })
|
||||
@UpdateDateColumn({
|
||||
precision: 3,
|
||||
default: () => getTimestampSyntax(),
|
||||
onUpdate: getTimestampSyntax(),
|
||||
})
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({
|
||||
|
@ -73,16 +64,16 @@ export class WorkflowEntity implements IWorkflowDb {
|
|||
})
|
||||
staticData?: IDataObject;
|
||||
|
||||
@ManyToMany(() => TagEntity, tag => tag.workflows)
|
||||
@ManyToMany(() => TagEntity, (tag) => tag.workflows)
|
||||
@JoinTable({
|
||||
name: "workflows_tags", // table name for the junction table of this relation
|
||||
name: 'workflows_tags', // table name for the junction table of this relation
|
||||
joinColumn: {
|
||||
name: "workflowId",
|
||||
referencedColumnName: "id",
|
||||
name: 'workflowId',
|
||||
referencedColumnName: 'id',
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "tagId",
|
||||
referencedColumnName: "id",
|
||||
name: 'tagId',
|
||||
referencedColumnName: 'id',
|
||||
},
|
||||
})
|
||||
tags: TagEntity[];
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { CredentialsEntity } from './CredentialsEntity';
|
||||
import { ExecutionEntity } from './ExecutionEntity';
|
||||
import { WorkflowEntity } from './WorkflowEntity';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
DatabaseType,
|
||||
} from '../index';
|
||||
import { getConfigValueSync } from '../../src/GenericHelpers';
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { DatabaseType } from '../index';
|
||||
import { getConfigValueSync } from '../GenericHelpers';
|
||||
|
||||
/**
|
||||
* Resolves the data type for the used database type
|
||||
|
@ -10,6 +9,7 @@ import { getConfigValueSync } from '../../src/GenericHelpers';
|
|||
* @param {string} dataType
|
||||
* @returns {string}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function resolveDataType(dataType: string) {
|
||||
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
||||
|
||||
|
@ -27,16 +27,16 @@ export function resolveDataType(dataType: string) {
|
|||
return typeMap[dbType][dataType] ?? dataType;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getTimestampSyntax() {
|
||||
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
||||
|
||||
const map: { [key in DatabaseType]: string } = {
|
||||
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
|
||||
postgresdb: "CURRENT_TIMESTAMP(3)",
|
||||
mysqldb: "CURRENT_TIMESTAMP(3)",
|
||||
mariadb: "CURRENT_TIMESTAMP(3)",
|
||||
postgresdb: 'CURRENT_TIMESTAMP(3)',
|
||||
mysqldb: 'CURRENT_TIMESTAMP(3)',
|
||||
mariadb: 'CURRENT_TIMESTAMP(3)',
|
||||
};
|
||||
|
||||
return map[dbType];
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable import/first */
|
||||
/* eslint-disable import/no-cycle */
|
||||
export * from './CredentialsHelper';
|
||||
export * from './CredentialTypes';
|
||||
export * from './CredentialsOverwrites';
|
||||
|
@ -22,6 +24,7 @@ import * as WebhookHelpers from './WebhookHelpers';
|
|||
import * as WebhookServer from './WebhookServer';
|
||||
import * as WorkflowExecuteAdditionalData from './WorkflowExecuteAdditionalData';
|
||||
import * as WorkflowHelpers from './WorkflowHelpers';
|
||||
|
||||
export {
|
||||
ActiveExecutions,
|
||||
ActiveWorkflowRunner,
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
describe('Placeholder', () => {
|
||||
|
||||
test('example', () => {
|
||||
expect(1 + 1).toEqual(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "npm run watch",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/core/**/**.ts --write",
|
||||
"lint": "cd ../.. && node_modules/eslint/bin/eslint.js packages/core",
|
||||
"lintfix": "cd ../.. && node_modules/eslint/bin/eslint.js packages/core --fix",
|
||||
"watch": "tsc --watch",
|
||||
"test": "jest"
|
||||
},
|
||||
|
@ -38,7 +39,7 @@
|
|||
"source-map-support": "^0.5.9",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"client-oauth2": "^4.2.5",
|
||||
|
|
|
@ -6,10 +6,8 @@ import {
|
|||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NodeExecuteFunctions } from '.';
|
||||
|
||||
export class ActiveWebhooks {
|
||||
private workflowWebhooks: {
|
||||
|
@ -22,7 +20,6 @@ export class ActiveWebhooks {
|
|||
|
||||
testWebhooks = false;
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new webhook
|
||||
*
|
||||
|
@ -31,19 +28,31 @@ export class ActiveWebhooks {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWebhooks
|
||||
*/
|
||||
async add(workflow: Workflow, webhookData: IWebhookData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<void> {
|
||||
async add(
|
||||
workflow: Workflow,
|
||||
webhookData: IWebhookData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): Promise<void> {
|
||||
if (workflow.id === undefined) {
|
||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
||||
}
|
||||
if (webhookData.path.endsWith('/')) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
webhookData.path = webhookData.path.slice(0, -1);
|
||||
}
|
||||
|
||||
const webhookKey = this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId);
|
||||
const webhookKey = this.getWebhookKey(
|
||||
webhookData.httpMethod,
|
||||
webhookData.path,
|
||||
webhookData.webhookId,
|
||||
);
|
||||
|
||||
//check that there is not a webhook already registed with that path/method
|
||||
// check that there is not a webhook already registed with that path/method
|
||||
if (this.webhookUrls[webhookKey] && !webhookData.webhookId) {
|
||||
throw new Error(`Test-Webhook can not be activated because another one with the same method "${webhookData.httpMethod}" and path "${webhookData.path}" is already active!`);
|
||||
throw new Error(
|
||||
`Test-Webhook can not be activated because another one with the same method "${webhookData.httpMethod}" and path "${webhookData.path}" is already active!`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.workflowWebhooks[webhookData.workflowId] === undefined) {
|
||||
|
@ -58,18 +67,33 @@ export class ActiveWebhooks {
|
|||
this.webhookUrls[webhookKey].push(webhookData);
|
||||
|
||||
try {
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, activation, this.testWebhooks);
|
||||
const webhookExists = await workflow.runWebhookMethod(
|
||||
'checkExists',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
activation,
|
||||
this.testWebhooks,
|
||||
);
|
||||
if (webhookExists !== true) {
|
||||
// If webhook does not exist yet create it
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, activation, this.testWebhooks);
|
||||
|
||||
await workflow.runWebhookMethod(
|
||||
'create',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
activation,
|
||||
this.testWebhooks,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// If there was a problem unregister the webhook again
|
||||
if (this.webhookUrls[webhookKey].length <= 1) {
|
||||
delete this.webhookUrls[webhookKey];
|
||||
} else {
|
||||
this.webhookUrls[webhookKey] = this.webhookUrls[webhookKey].filter(webhook => webhook.path !== webhookData.path);
|
||||
this.webhookUrls[webhookKey] = this.webhookUrls[webhookKey].filter(
|
||||
(webhook) => webhook.path !== webhookData.path,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -77,7 +101,6 @@ export class ActiveWebhooks {
|
|||
this.workflowWebhooks[webhookData.workflowId].push(webhookData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns webhookData if a webhook with matches is currently registered
|
||||
*
|
||||
|
@ -98,9 +121,9 @@ export class ActiveWebhooks {
|
|||
const pathElementsSet = new Set(path.split('/'));
|
||||
// check if static elements match in path
|
||||
// if more results have been returned choose the one with the most static-route matches
|
||||
this.webhookUrls[webhookKey].forEach(dynamicWebhook => {
|
||||
const staticElements = dynamicWebhook.path.split('/').filter(ele => !ele.startsWith(':'));
|
||||
const allStaticExist = staticElements.every(staticEle => pathElementsSet.has(staticEle));
|
||||
this.webhookUrls[webhookKey].forEach((dynamicWebhook) => {
|
||||
const staticElements = dynamicWebhook.path.split('/').filter((ele) => !ele.startsWith(':'));
|
||||
const allStaticExist = staticElements.every((staticEle) => pathElementsSet.has(staticEle));
|
||||
|
||||
if (allStaticExist && staticElements.length > maxMatches) {
|
||||
maxMatches = staticElements.length;
|
||||
|
@ -120,11 +143,12 @@ export class ActiveWebhooks {
|
|||
* @param path
|
||||
*/
|
||||
getWebhookMethods(path: string): string[] {
|
||||
const methods : string[] = [];
|
||||
const methods: string[] = [];
|
||||
|
||||
Object.keys(this.webhookUrls)
|
||||
.filter(key => key.includes(path))
|
||||
.map(key => {
|
||||
.filter((key) => key.includes(path))
|
||||
// eslint-disable-next-line array-callback-return
|
||||
.map((key) => {
|
||||
methods.push(key.split('|')[0]);
|
||||
});
|
||||
|
||||
|
@ -141,7 +165,6 @@ export class ActiveWebhooks {
|
|||
return Object.keys(this.workflowWebhooks);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns key to uniquely identify a webhook
|
||||
*
|
||||
|
@ -155,6 +178,7 @@ export class ActiveWebhooks {
|
|||
if (webhookId) {
|
||||
if (path.startsWith(webhookId)) {
|
||||
const cutFromIndex = path.indexOf('/') + 1;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
path = path.slice(cutFromIndex);
|
||||
}
|
||||
return `${httpMethod}|${webhookId}|${path.split('/').length}`;
|
||||
|
@ -162,7 +186,6 @@ export class ActiveWebhooks {
|
|||
return `${httpMethod}|${path}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all webhooks of a workflow
|
||||
*
|
||||
|
@ -171,6 +194,7 @@ export class ActiveWebhooks {
|
|||
* @memberof ActiveWebhooks
|
||||
*/
|
||||
async removeWorkflow(workflow: Workflow): Promise<boolean> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const workflowId = workflow.id!.toString();
|
||||
|
||||
if (this.workflowWebhooks[workflowId] === undefined) {
|
||||
|
@ -183,10 +207,21 @@ export class ActiveWebhooks {
|
|||
const mode = 'internal';
|
||||
|
||||
// Go through all the registered webhooks of the workflow and remove them
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const webhookData of webhooks) {
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, 'update', this.testWebhooks);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await workflow.runWebhookMethod(
|
||||
'delete',
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
'update',
|
||||
this.testWebhooks,
|
||||
);
|
||||
|
||||
delete this.webhookUrls[this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId)];
|
||||
delete this.webhookUrls[
|
||||
this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId)
|
||||
];
|
||||
}
|
||||
|
||||
// Remove also the workflow-webhook entry
|
||||
|
@ -195,18 +230,16 @@ export class ActiveWebhooks {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all the webhooks of the given workflows
|
||||
*/
|
||||
async removeAll(workflows: Workflow[]): Promise<void> {
|
||||
const removePromises = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const workflow of workflows) {
|
||||
removePromises.push(this.removeWorkflow(workflow));
|
||||
}
|
||||
|
||||
await Promise.all(removePromises);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { CronJob } from 'cron';
|
||||
|
||||
import {
|
||||
|
@ -13,18 +16,14 @@ import {
|
|||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
ITriggerTime,
|
||||
IWorkflowData,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { ITriggerTime, IWorkflowData } from '.';
|
||||
|
||||
export class ActiveWorkflows {
|
||||
private workflowData: {
|
||||
[key: string]: IWorkflowData;
|
||||
} = {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the workflow is active
|
||||
*
|
||||
|
@ -33,10 +32,10 @@ export class ActiveWorkflows {
|
|||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
isActive(id: string): boolean {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
return this.workflowData.hasOwnProperty(id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ids of the currently active workflows
|
||||
*
|
||||
|
@ -47,7 +46,6 @@ export class ActiveWorkflows {
|
|||
return Object.keys(this.workflowData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the Workflow data for the workflow with
|
||||
* the given id if it is currently active
|
||||
|
@ -60,7 +58,6 @@ export class ActiveWorkflows {
|
|||
return this.workflowData[id];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes a workflow active
|
||||
*
|
||||
|
@ -70,16 +67,31 @@ export class ActiveWorkflows {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise<void> {
|
||||
async add(
|
||||
id: string,
|
||||
workflow: Workflow,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
getTriggerFunctions: IGetExecuteTriggerFunctions,
|
||||
getPollFunctions: IGetExecutePollFunctions,
|
||||
): Promise<void> {
|
||||
this.workflowData[id] = {};
|
||||
const triggerNodes = workflow.getTriggerNodes();
|
||||
|
||||
let triggerResponse: ITriggerResponse | undefined;
|
||||
this.workflowData[id].triggerResponses = [];
|
||||
for (const triggerNode of triggerNodes) {
|
||||
triggerResponse = await workflow.runTrigger(triggerNode, getTriggerFunctions, additionalData, mode, activation);
|
||||
triggerResponse = await workflow.runTrigger(
|
||||
triggerNode,
|
||||
getTriggerFunctions,
|
||||
additionalData,
|
||||
mode,
|
||||
activation,
|
||||
);
|
||||
if (triggerResponse !== undefined) {
|
||||
// If a response was given save it
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
||||
}
|
||||
}
|
||||
|
@ -88,12 +100,21 @@ export class ActiveWorkflows {
|
|||
if (pollNodes.length) {
|
||||
this.workflowData[id].pollResponses = [];
|
||||
for (const pollNode of pollNodes) {
|
||||
this.workflowData[id].pollResponses!.push(await this.activatePolling(pollNode, workflow, additionalData, getPollFunctions, mode, activation));
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.workflowData[id].pollResponses!.push(
|
||||
await this.activatePolling(
|
||||
pollNode,
|
||||
workflow,
|
||||
additionalData,
|
||||
getPollFunctions,
|
||||
mode,
|
||||
activation,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Activates polling for the given node
|
||||
*
|
||||
|
@ -104,7 +125,14 @@ export class ActiveWorkflows {
|
|||
* @returns {Promise<IPollResponse>}
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async activatePolling(node: INode, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getPollFunctions: IGetExecutePollFunctions, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<IPollResponse> {
|
||||
async activatePolling(
|
||||
node: INode,
|
||||
workflow: Workflow,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
getPollFunctions: IGetExecutePollFunctions,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): Promise<IPollResponse> {
|
||||
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode, activation);
|
||||
|
||||
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
|
||||
|
@ -165,10 +193,15 @@ export class ActiveWorkflows {
|
|||
|
||||
// The trigger function to execute when the cron-time got reached
|
||||
const executeTrigger = async () => {
|
||||
Logger.info(`Polling trigger initiated for workflow "${workflow.name}"`, {workflowName: workflow.name, workflowId: workflow.id});
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.info(`Polling trigger initiated for workflow "${workflow.name}"`, {
|
||||
workflowName: workflow.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
const pollResponse = await workflow.runPoll(node, pollFunctions);
|
||||
|
||||
if (pollResponse !== null) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
pollFunctions.__emit(pollResponse);
|
||||
}
|
||||
};
|
||||
|
@ -180,6 +213,7 @@ export class ActiveWorkflows {
|
|||
|
||||
// Start the cron-jobs
|
||||
const cronJobs: CronJob[] = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
for (const cronTime of cronTimes) {
|
||||
const cronTimeParts = cronTime.split(' ');
|
||||
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
|
||||
|
@ -201,7 +235,6 @@ export class ActiveWorkflows {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes a workflow inactive
|
||||
*
|
||||
|
@ -212,7 +245,9 @@ export class ActiveWorkflows {
|
|||
async remove(id: string): Promise<void> {
|
||||
if (!this.isActive(id)) {
|
||||
// Workflow is currently not registered
|
||||
throw new Error(`The workflow with the id "${id}" is currently not active and can so not be removed`);
|
||||
throw new Error(
|
||||
`The workflow with the id "${id}" is currently not active and can so not be removed`,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowData = this.workflowData[id];
|
||||
|
@ -235,5 +270,4 @@ export class ActiveWorkflows {
|
|||
|
||||
delete this.workflowData[id];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,16 +7,13 @@ import {
|
|||
|
||||
import { AES, enc } from 'crypto-js';
|
||||
|
||||
|
||||
export class Credentials extends ICredentials {
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given nodeType has access to data
|
||||
*/
|
||||
hasNodeAccess(nodeType: string): boolean {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const accessData of this.nodesAccess) {
|
||||
|
||||
if (accessData.nodeType === nodeType) {
|
||||
return true;
|
||||
}
|
||||
|
@ -25,7 +22,6 @@ export class Credentials extends ICredentials {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets new credential object
|
||||
*/
|
||||
|
@ -33,7 +29,6 @@ export class Credentials extends ICredentials {
|
|||
this.data = AES.encrypt(JSON.stringify(data), encryptionKey).toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets new credentials for given key
|
||||
*/
|
||||
|
@ -50,13 +45,14 @@ export class Credentials extends ICredentials {
|
|||
return this.setData(fullData, encryptionKey);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the decrypted credential object
|
||||
*/
|
||||
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
|
||||
if (nodeType && !this.hasNodeAccess(nodeType)) {
|
||||
throw new Error(`The node of type "${nodeType}" does not have access to credentials "${this.name}" of type "${this.type}".`);
|
||||
throw new Error(
|
||||
`The node of type "${nodeType}" does not have access to credentials "${this.name}" of type "${this.type}".`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.data === undefined) {
|
||||
|
@ -66,13 +62,15 @@ export class Credentials extends ICredentials {
|
|||
const decryptedData = AES.decrypt(this.data, encryptionKey);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return JSON.parse(decryptedData.toString(enc.Utf8));
|
||||
} catch (e) {
|
||||
throw new Error('Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.');
|
||||
throw new Error(
|
||||
'Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the decrypted credentials for given key
|
||||
*/
|
||||
|
@ -83,6 +81,7 @@ export class Credentials extends ICredentials {
|
|||
throw new Error(`No data was set.`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!fullData.hasOwnProperty(key)) {
|
||||
throw new Error(`No data for key "${key}" exists.`);
|
||||
}
|
||||
|
@ -90,7 +89,6 @@ export class Credentials extends ICredentials {
|
|||
return fullData[key];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the encrypted credentials to be saved
|
||||
*/
|
||||
|
|
|
@ -5,10 +5,10 @@ export interface IDeferredPromise<T> {
|
|||
resolve: (result: T) => void;
|
||||
}
|
||||
|
||||
export function createDeferredPromise<T>(): Promise<IDeferredPromise<T>> {
|
||||
return new Promise<IDeferredPromise<T>>(resolveCreate => {
|
||||
export async function createDeferredPromise<T>(): Promise<IDeferredPromise<T>> {
|
||||
return new Promise<IDeferredPromise<T>>((resolveCreate) => {
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
resolveCreate({ promise: () => promise, resolve, reject });
|
||||
resolveCreate({ promise: async () => promise, resolve, reject });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
IAllExecuteFunctions,
|
||||
IBinaryData,
|
||||
|
@ -16,70 +17,116 @@ import {
|
|||
ITriggerResponse,
|
||||
IWebhookFunctions as IWebhookFunctionsBase,
|
||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
|
||||
interface Constructable<T> {
|
||||
new(): T;
|
||||
new (): T;
|
||||
}
|
||||
|
||||
export interface IProcessMessage {
|
||||
data?: any; // tslint:disable-line:no-any
|
||||
data?: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IPollFunctions extends IPollFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IResponseError extends Error {
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface ITriggerTime {
|
||||
mode: string;
|
||||
hour: number;
|
||||
|
@ -89,7 +136,6 @@ export interface ITriggerTime {
|
|||
[key: string]: string | number;
|
||||
}
|
||||
|
||||
|
||||
export interface IUserSettings {
|
||||
encryptionKey?: string;
|
||||
tunnelSubdomain?: string;
|
||||
|
@ -97,28 +143,57 @@ export interface IUserSettings {
|
|||
|
||||
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
||||
helpers: {
|
||||
request?: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2?: (this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options) => Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1?(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
request?: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2?: (
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1?(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IHookFunctions extends IHookFunctionsBase {
|
||||
helpers: {
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IWebhookFunctions extends IWebhookFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
@ -129,19 +204,16 @@ export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
|||
saveManualRuns?: boolean;
|
||||
}
|
||||
|
||||
|
||||
// New node definition in file
|
||||
export interface INodeDefinitionFile {
|
||||
[key: string]: Constructable<INodeType | ICredentialType>;
|
||||
}
|
||||
|
||||
|
||||
// Is identical to TaskDataConnections but does not allow null value to be used as input for nodes
|
||||
export interface INodeInputDataConnections {
|
||||
[key: string]: INodeExecutionData[][];
|
||||
}
|
||||
|
||||
|
||||
export interface IWorkflowData {
|
||||
pollResponses?: IPollResponse[];
|
||||
triggerResponses?: ITriggerResponse[];
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import {
|
||||
INode,
|
||||
INodeCredentials,
|
||||
|
@ -8,21 +9,24 @@ import {
|
|||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NodeExecuteFunctions } from '.';
|
||||
|
||||
const TEMP_NODE_NAME = 'Temp-Node';
|
||||
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
|
||||
|
||||
|
||||
export class LoadNodeParameterOptions {
|
||||
path: string;
|
||||
|
||||
workflow: Workflow;
|
||||
|
||||
|
||||
constructor(nodeTypeName: string, nodeTypes: INodeTypes, path: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) {
|
||||
constructor(
|
||||
nodeTypeName: string,
|
||||
nodeTypes: INodeTypes,
|
||||
path: string,
|
||||
currentNodeParameters: INodeParameters,
|
||||
credentials?: INodeCredentials,
|
||||
) {
|
||||
this.path = path;
|
||||
const nodeType = nodeTypes.getByName(nodeTypeName);
|
||||
|
||||
|
@ -35,10 +39,7 @@ export class LoadNodeParameterOptions {
|
|||
name: TEMP_NODE_NAME,
|
||||
type: nodeTypeName,
|
||||
typeVersion: 1,
|
||||
position: [
|
||||
0,
|
||||
0,
|
||||
],
|
||||
position: [0, 0],
|
||||
};
|
||||
|
||||
if (credentials) {
|
||||
|
@ -46,22 +47,25 @@ export class LoadNodeParameterOptions {
|
|||
}
|
||||
|
||||
const workflowData = {
|
||||
nodes: [
|
||||
nodeData,
|
||||
],
|
||||
nodes: [nodeData],
|
||||
connections: {},
|
||||
};
|
||||
|
||||
this.workflow = new Workflow({ nodes: workflowData.nodes, connections: workflowData.connections, active: false, nodeTypes });
|
||||
this.workflow = new Workflow({
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns data of a fake workflow
|
||||
*
|
||||
* @returns
|
||||
* @memberof LoadNodeParameterOptions
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
getWorkflowData() {
|
||||
return {
|
||||
name: TEMP_WORKFLOW_NAME,
|
||||
|
@ -73,7 +77,6 @@ export class LoadNodeParameterOptions {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the available options
|
||||
*
|
||||
|
@ -82,18 +85,31 @@ export class LoadNodeParameterOptions {
|
|||
* @returns {Promise<INodePropertyOptions[]>}
|
||||
* @memberof LoadNodeParameterOptions
|
||||
*/
|
||||
getOptions(methodName: string, additionalData: IWorkflowExecuteAdditionalData): Promise<INodePropertyOptions[]> {
|
||||
async getOptions(
|
||||
methodName: string,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const node = this.workflow.getNode(TEMP_NODE_NAME);
|
||||
|
||||
const nodeType = this.workflow.nodeTypes.getByName(node!.type);
|
||||
|
||||
if (nodeType!.methods === undefined || nodeType!.methods.loadOptions === undefined || nodeType!.methods.loadOptions[methodName] === undefined) {
|
||||
throw new Error(`The node-type "${node!.type}" does not have the method "${methodName}" defined!`);
|
||||
if (
|
||||
nodeType!.methods === undefined ||
|
||||
nodeType!.methods.loadOptions === undefined ||
|
||||
nodeType!.methods.loadOptions[methodName] === undefined
|
||||
) {
|
||||
throw new Error(
|
||||
`The node-type "${node!.type}" does not have the method "${methodName}" defined!`,
|
||||
);
|
||||
}
|
||||
|
||||
const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(this.workflow, node!, this.path, additionalData);
|
||||
const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(
|
||||
this.workflow,
|
||||
node!,
|
||||
this.path,
|
||||
additionalData,
|
||||
);
|
||||
|
||||
return nodeType!.methods.loadOptions[methodName].call(thisArgs);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,11 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ENCRYPTION_KEY_ENV_OVERWRITE,
|
||||
EXTENSIONS_SUBDIRECTORY,
|
||||
|
@ -7,20 +15,15 @@ import {
|
|||
USER_SETTINGS_SUBFOLDER,
|
||||
} from '.';
|
||||
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { promisify } = require('util');
|
||||
|
||||
const fsAccess = promisify(fs.access);
|
||||
const fsReadFile = promisify(fs.readFile);
|
||||
const fsMkdir = promisify(fs.mkdir);
|
||||
const fsWriteFile = promisify(fs.writeFile);
|
||||
|
||||
|
||||
|
||||
let settingsCache: IUserSettings | undefined = undefined;
|
||||
|
||||
let settingsCache: IUserSettings | undefined;
|
||||
|
||||
/**
|
||||
* Creates the user settings if they do not exist yet
|
||||
|
@ -49,12 +52,12 @@ export async function prepareUserSettings(): Promise<IUserSettings> {
|
|||
userSettings.encryptionKey = randomBytes(24).toString('base64');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`UserSettings were generated and saved to: ${settingsPath}`);
|
||||
|
||||
return writeUserSettings(userSettings, settingsPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the encryption key which is used to encrypt
|
||||
* the credentials.
|
||||
|
@ -62,6 +65,7 @@ export async function prepareUserSettings(): Promise<IUserSettings> {
|
|||
* @export
|
||||
* @returns
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export async function getEncryptionKey() {
|
||||
if (process.env[ENCRYPTION_KEY_ENV_OVERWRITE] !== undefined) {
|
||||
return process.env[ENCRYPTION_KEY_ENV_OVERWRITE];
|
||||
|
@ -80,7 +84,6 @@ export async function getEncryptionKey() {
|
|||
return userSettings.encryptionKey;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds/Overwrite the given settings in the currently
|
||||
* saved user settings
|
||||
|
@ -90,7 +93,10 @@ export async function getEncryptionKey() {
|
|||
* @param {string} [settingsPath] Optional settings file path
|
||||
* @returns {Promise<IUserSettings>}
|
||||
*/
|
||||
export async function addToUserSettings(addSettings: IUserSettings, settingsPath?: string): Promise<IUserSettings> {
|
||||
export async function addToUserSettings(
|
||||
addSettings: IUserSettings,
|
||||
settingsPath?: string,
|
||||
): Promise<IUserSettings> {
|
||||
if (settingsPath === undefined) {
|
||||
settingsPath = getUserSettingsPath();
|
||||
}
|
||||
|
@ -107,7 +113,6 @@ export async function addToUserSettings(addSettings: IUserSettings, settingsPath
|
|||
return writeUserSettings(userSettings, settingsPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes a user settings file
|
||||
*
|
||||
|
@ -116,7 +121,10 @@ export async function addToUserSettings(addSettings: IUserSettings, settingsPath
|
|||
* @param {string} [settingsPath] Optional settings file path
|
||||
* @returns {Promise<IUserSettings>}
|
||||
*/
|
||||
export async function writeUserSettings(userSettings: IUserSettings, settingsPath?: string): Promise<IUserSettings> {
|
||||
export async function writeUserSettings(
|
||||
userSettings: IUserSettings,
|
||||
settingsPath?: string,
|
||||
): Promise<IUserSettings> {
|
||||
if (settingsPath === undefined) {
|
||||
settingsPath = getUserSettingsPath();
|
||||
}
|
||||
|
@ -139,14 +147,16 @@ export async function writeUserSettings(userSettings: IUserSettings, settingsPat
|
|||
return userSettings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the content of the user settings
|
||||
*
|
||||
* @export
|
||||
* @returns {UserSettings}
|
||||
*/
|
||||
export async function getUserSettings(settingsPath?: string, ignoreCache?: boolean): Promise<IUserSettings | undefined> {
|
||||
export async function getUserSettings(
|
||||
settingsPath?: string,
|
||||
ignoreCache?: boolean,
|
||||
): Promise<IUserSettings | undefined> {
|
||||
if (settingsCache !== undefined && ignoreCache !== true) {
|
||||
return settingsCache;
|
||||
}
|
||||
|
@ -167,13 +177,14 @@ export async function getUserSettings(settingsPath?: string, ignoreCache?: boole
|
|||
try {
|
||||
settingsCache = JSON.parse(settingsFile);
|
||||
} catch (error) {
|
||||
throw new Error(`Error parsing n8n-config file "${settingsPath}". It does not seem to be valid JSON.`);
|
||||
throw new Error(
|
||||
`Error parsing n8n-config file "${settingsPath}". It does not seem to be valid JSON.`,
|
||||
);
|
||||
}
|
||||
|
||||
return settingsCache as IUserSettings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to the user settings
|
||||
*
|
||||
|
@ -186,8 +197,6 @@ export function getUserSettingsPath(): string {
|
|||
return path.join(n8nFolder, USER_SETTINGS_FILE_NAME);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Retruns the path to the n8n folder in which all n8n
|
||||
* related data gets saved
|
||||
|
@ -206,7 +215,6 @@ export function getUserN8nFolderPath(): string {
|
|||
return path.join(userFolder, USER_SETTINGS_SUBFOLDER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to the n8n user folder with the custom
|
||||
* extensions like nodes and credentials
|
||||
|
@ -218,7 +226,6 @@ export function getUserN8nFolderCustomExtensionPath(): string {
|
|||
return path.join(getUserN8nFolderPath(), EXTENSIONS_SUBDIRECTORY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the home folder path of the user if
|
||||
* none can be found it falls back to the current
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-labels */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
|
||||
import {
|
||||
|
@ -20,23 +31,27 @@ import {
|
|||
WorkflowExecuteMode,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
} from './';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { get } from 'lodash';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NodeExecuteFunctions } from '.';
|
||||
|
||||
export class WorkflowExecute {
|
||||
runExecutionData: IRunExecutionData;
|
||||
|
||||
private additionalData: IWorkflowExecuteAdditionalData;
|
||||
|
||||
private mode: WorkflowExecuteMode;
|
||||
|
||||
constructor(additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData) {
|
||||
constructor(
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
runExecutionData?: IRunExecutionData,
|
||||
) {
|
||||
this.additionalData = additionalData;
|
||||
this.mode = mode;
|
||||
this.runExecutionData = runExecutionData || {
|
||||
startData: {
|
||||
},
|
||||
startData: {},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
|
@ -48,8 +63,6 @@ export class WorkflowExecute {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given workflow.
|
||||
*
|
||||
|
@ -59,7 +72,8 @@ export class WorkflowExecute {
|
|||
* @returns {(Promise<string>)}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
run(workflow: Workflow, startNode?: INode, destinationNode?: string): PCancelable<IRun> {
|
||||
// @ts-ignore
|
||||
async run(workflow: Workflow, startNode?: INode, destinationNode?: string): PCancelable<IRun> {
|
||||
// Get the nodes to start workflow execution from
|
||||
startNode = startNode || workflow.getStartNode(destinationNode);
|
||||
|
||||
|
@ -68,7 +82,7 @@ export class WorkflowExecute {
|
|||
}
|
||||
|
||||
// If a destination node is given we only run the direct parent nodes and no others
|
||||
let runNodeFilter: string[] | undefined = undefined;
|
||||
let runNodeFilter: string[] | undefined;
|
||||
if (destinationNode) {
|
||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||
runNodeFilter.push(destinationNode);
|
||||
|
@ -108,8 +122,6 @@ export class WorkflowExecute {
|
|||
return this.processRunExecutionData(workflow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given workflow but only
|
||||
*
|
||||
|
@ -121,7 +133,13 @@ export class WorkflowExecute {
|
|||
* @memberof WorkflowExecute
|
||||
*/
|
||||
// @ts-ignore
|
||||
async runPartialWorkflow(workflow: Workflow, runData: IRunData, startNodes: string[], destinationNode: string): PCancelable<IRun> {
|
||||
async runPartialWorkflow(
|
||||
workflow: Workflow,
|
||||
runData: IRunData,
|
||||
startNodes: string[],
|
||||
destinationNode: string,
|
||||
// @ts-ignore
|
||||
): PCancelable<IRun> {
|
||||
let incomingNodeConnections: INodeConnections | undefined;
|
||||
let connection: IConnection;
|
||||
|
||||
|
@ -149,7 +167,8 @@ export class WorkflowExecute {
|
|||
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
|
||||
connection = connections[inputIndex];
|
||||
incomingData.push(
|
||||
runData[connection.node!][runIndex].data![connection.type][connection.index]!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
runData[connection.node][runIndex].data![connection.type][connection.index]!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -182,11 +201,12 @@ export class WorkflowExecute {
|
|||
waitingExecution[destinationNode][runIndex][connection.type] = [];
|
||||
}
|
||||
|
||||
|
||||
if (runData[connection.node!] !== undefined) {
|
||||
if (runData[connection.node] !== undefined) {
|
||||
// Input data exists so add as waiting
|
||||
// incomingDataDestination.push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(
|
||||
runData[connection.node][runIndex].data![connection.type][connection.index],
|
||||
);
|
||||
} else {
|
||||
waitingExecution[destinationNode][runIndex][connection.type].push(null);
|
||||
}
|
||||
|
@ -196,7 +216,8 @@ export class WorkflowExecute {
|
|||
}
|
||||
|
||||
// Only run the parent nodes and no others
|
||||
let runNodeFilter: string[] | undefined = undefined;
|
||||
let runNodeFilter: string[] | undefined;
|
||||
// eslint-disable-next-line prefer-const
|
||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||
runNodeFilter.push(destinationNode);
|
||||
|
||||
|
@ -218,8 +239,6 @@ export class WorkflowExecute {
|
|||
return this.processRunExecutionData(workflow);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the hook with the given name
|
||||
*
|
||||
|
@ -228,22 +247,31 @@ export class WorkflowExecute {
|
|||
* @returns {Promise<IRun>}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
async executeHook(hookName: string, parameters: any[]): Promise<void> { // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async executeHook(hookName: string, parameters: any[]): Promise<void> {
|
||||
// tslint:disable-line:no-any
|
||||
if (this.additionalData.hooks === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return this.additionalData.hooks.executeHookFunctions(hookName, parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the incoming connection does not receive any data
|
||||
*/
|
||||
incomingConnectionIsEmpty(runData: IRunData, inputConnections: IConnection[], runIndex: number): boolean {
|
||||
incomingConnectionIsEmpty(
|
||||
runData: IRunData,
|
||||
inputConnections: IConnection[],
|
||||
runIndex: number,
|
||||
): boolean {
|
||||
// for (const inputConnection of workflow.connectionsByDestinationNode[nodeToAdd].main[0]) {
|
||||
for (const inputConnection of inputConnections) {
|
||||
const nodeIncomingData = get(runData, `[${inputConnection.node}][${runIndex}].data.main[${inputConnection.index}]`);
|
||||
const nodeIncomingData = get(
|
||||
runData,
|
||||
`[${inputConnection.node}][${runIndex}].data.main[${inputConnection.index}]`,
|
||||
);
|
||||
if (nodeIncomingData !== undefined && (nodeIncomingData as object[]).length !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
@ -251,79 +279,117 @@ export class WorkflowExecute {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
addNodeToBeExecuted(workflow: Workflow, connectionData: IConnection, outputIndex: number, parentNodeName: string, nodeSuccessData: INodeExecutionData[][], runIndex: number): void {
|
||||
addNodeToBeExecuted(
|
||||
workflow: Workflow,
|
||||
connectionData: IConnection,
|
||||
outputIndex: number,
|
||||
parentNodeName: string,
|
||||
nodeSuccessData: INodeExecutionData[][],
|
||||
runIndex: number,
|
||||
): void {
|
||||
let stillDataMissing = false;
|
||||
|
||||
// Check if node has multiple inputs as then we have to wait for all input data
|
||||
// to be present before we can add it to the node-execution-stack
|
||||
if (workflow.connectionsByDestinationNode[connectionData.node]['main'].length > 1) {
|
||||
if (workflow.connectionsByDestinationNode[connectionData.node].main.length > 1) {
|
||||
// Node has multiple inputs
|
||||
let nodeWasWaiting = true;
|
||||
|
||||
// Check if there is already data for the node
|
||||
if (this.runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined) {
|
||||
if (
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined
|
||||
) {
|
||||
// Node does not have data yet so create a new empty one
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||
nodeWasWaiting = false;
|
||||
}
|
||||
if (this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] === undefined) {
|
||||
if (
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] ===
|
||||
undefined
|
||||
) {
|
||||
// Node does not have data for runIndex yet so create also empty one and init it
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
main: [],
|
||||
};
|
||||
for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.push(null);
|
||||
for (
|
||||
let i = 0;
|
||||
i < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||
i++
|
||||
) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||
runIndex
|
||||
].main.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new data
|
||||
if (nodeSuccessData === null) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = null;
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
connectionData.index
|
||||
] = null;
|
||||
} else {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[connectionData.index] = nodeSuccessData[outputIndex];
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
connectionData.index
|
||||
] = nodeSuccessData[outputIndex];
|
||||
}
|
||||
|
||||
// Check if all data exists now
|
||||
let thisExecutionData: INodeExecutionData[] | null;
|
||||
let allDataFound = true;
|
||||
for (let i = 0; i < this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.length; i++) {
|
||||
thisExecutionData = this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[i];
|
||||
for (
|
||||
let i = 0;
|
||||
i <
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main
|
||||
.length;
|
||||
i++
|
||||
) {
|
||||
thisExecutionData =
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||
i
|
||||
];
|
||||
if (thisExecutionData === null) {
|
||||
allDataFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allDataFound === true) {
|
||||
if (allDataFound) {
|
||||
// All data exists for node to be executed
|
||||
// So add it to the execution stack
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.nodes[connectionData.node],
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex],
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||
runIndex
|
||||
],
|
||||
});
|
||||
|
||||
// Remove the data from waiting
|
||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
||||
|
||||
if (Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node]).length === 0) {
|
||||
if (
|
||||
Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node])
|
||||
.length === 0
|
||||
) {
|
||||
// No more data left for the node so also delete that one
|
||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
stillDataMissing = true;
|
||||
}
|
||||
stillDataMissing = true;
|
||||
|
||||
if (nodeWasWaiting === false) {
|
||||
|
||||
if (!nodeWasWaiting) {
|
||||
// Get a list of all the output nodes that we can check for siblings easier
|
||||
const checkOutputNodes = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (const outputIndexParent in workflow.connectionsBySourceNode[parentNodeName].main) {
|
||||
if (!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)) {
|
||||
if (
|
||||
!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[outputIndexParent]) {
|
||||
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[
|
||||
outputIndexParent
|
||||
]) {
|
||||
checkOutputNodes.push(connectionDataCheck.node);
|
||||
}
|
||||
}
|
||||
|
@ -332,14 +398,22 @@ export class WorkflowExecute {
|
|||
// checked. So we have to go through all the inputs and check if they
|
||||
// are already on the list to be processed.
|
||||
// If that is not the case add it.
|
||||
for (let inputIndex = 0; inputIndex < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; inputIndex++) {
|
||||
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node]['main'][inputIndex]) {
|
||||
for (
|
||||
let inputIndex = 0;
|
||||
inputIndex < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||
inputIndex++
|
||||
) {
|
||||
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node].main[
|
||||
inputIndex
|
||||
]) {
|
||||
if (inputData.node === parentNodeName) {
|
||||
// Is the node we come from so its data will be available for sure
|
||||
continue;
|
||||
}
|
||||
|
||||
const executionStackNodes = this.runExecutionData.executionData!.nodeExecutionStack.map((stackData) => stackData.node.name);
|
||||
const executionStackNodes = this.runExecutionData.executionData!.nodeExecutionStack.map(
|
||||
(stackData) => stackData.node.name,
|
||||
);
|
||||
|
||||
// Check if that node is also an output connection of the
|
||||
// previously processed one
|
||||
|
@ -348,7 +422,13 @@ export class WorkflowExecute {
|
|||
// will then process this node next. So nothing to do
|
||||
// unless the incoming data of the node is empty
|
||||
// because then it would not be executed
|
||||
if (!this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[inputData.node].main[0], runIndex)) {
|
||||
if (
|
||||
!this.incomingConnectionIsEmpty(
|
||||
this.runExecutionData.resultData.runData,
|
||||
workflow.connectionsByDestinationNode[inputData.node].main[0],
|
||||
runIndex,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -401,7 +481,10 @@ export class WorkflowExecute {
|
|||
nodeToAdd = parentNode;
|
||||
}
|
||||
const parentNodesNodeToAdd = workflow.getParentNodes(nodeToAdd as string);
|
||||
if (parentNodesNodeToAdd.includes(parentNodeName) && nodeSuccessData[outputIndex].length === 0) {
|
||||
if (
|
||||
parentNodesNodeToAdd.includes(parentNodeName) &&
|
||||
nodeSuccessData[outputIndex].length === 0
|
||||
) {
|
||||
// We do not add the node if there is no input data and the node that should be connected
|
||||
// is a child of the parent node. Because else it would run a node even though it should be
|
||||
// specifically not run, as it did not receive any data.
|
||||
|
@ -418,18 +501,21 @@ export class WorkflowExecute {
|
|||
if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
|
||||
// Add empty item if the node does not have any input connections
|
||||
addEmptyItem = true;
|
||||
} else {
|
||||
if (this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[nodeToAdd].main[0], runIndex)) {
|
||||
} else if (
|
||||
this.incomingConnectionIsEmpty(
|
||||
this.runExecutionData.resultData.runData,
|
||||
workflow.connectionsByDestinationNode[nodeToAdd].main[0],
|
||||
runIndex,
|
||||
)
|
||||
) {
|
||||
// Add empty item also if the input data is empty
|
||||
addEmptyItem = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (addEmptyItem === true) {
|
||||
if (addEmptyItem) {
|
||||
// Add only node if it does not have any inputs because else it will
|
||||
// be added by its input node later anyway.
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(
|
||||
{
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.getNode(nodeToAdd) as INode,
|
||||
data: {
|
||||
main: [
|
||||
|
@ -440,8 +526,7 @@ export class WorkflowExecute {
|
|||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -461,9 +546,11 @@ export class WorkflowExecute {
|
|||
connectionDataArray[connectionData.index] = nodeSuccessData[outputIndex];
|
||||
}
|
||||
|
||||
if (stillDataMissing === true) {
|
||||
if (stillDataMissing) {
|
||||
// Additional data is needed to run node so add it to waiting
|
||||
if (!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)) {
|
||||
if (
|
||||
!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)
|
||||
) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||
}
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
|
@ -480,7 +567,6 @@ export class WorkflowExecute {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the given execution data.
|
||||
*
|
||||
|
@ -488,14 +574,17 @@ export class WorkflowExecute {
|
|||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowExecute
|
||||
*/
|
||||
processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
||||
// @ts-ignore
|
||||
async processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
||||
Logger.verbose('Workflow execution started', { workflowId: workflow.id });
|
||||
|
||||
const startedAt = new Date();
|
||||
|
||||
const workflowIssues = workflow.checkReadyForExecution();
|
||||
if (workflowIssues !== null) {
|
||||
throw new Error('The workflow has issues and can for that reason not be executed. Please fix them first.');
|
||||
throw new Error(
|
||||
'The workflow has issues and can for that reason not be executed. Please fix them first.',
|
||||
);
|
||||
}
|
||||
|
||||
// Variables which hold temporary data for each node-execution
|
||||
|
@ -521,7 +610,7 @@ export class WorkflowExecute {
|
|||
let currentExecutionTry = '';
|
||||
let lastExecutionTry = '';
|
||||
|
||||
return new PCancelable((resolve, reject, onCancel) => {
|
||||
return new PCancelable(async (resolve, reject, onCancel) => {
|
||||
let gotCancel = false;
|
||||
|
||||
onCancel.shouldReject = false;
|
||||
|
@ -533,7 +622,6 @@ export class WorkflowExecute {
|
|||
try {
|
||||
await this.executeHook('workflowExecuteBefore', [workflow]);
|
||||
} catch (error) {
|
||||
|
||||
// Set the error that it can be saved correctly
|
||||
executionError = {
|
||||
...error,
|
||||
|
@ -542,16 +630,17 @@ export class WorkflowExecute {
|
|||
};
|
||||
|
||||
// Set the incoming data of the node that it can be saved correctly
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0] as IExecuteData;
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0];
|
||||
this.runExecutionData.resultData = {
|
||||
runData: {
|
||||
[executionData.node.name]: [
|
||||
{
|
||||
startTime,
|
||||
executionTime: (new Date().getTime()) - startTime,
|
||||
data: ({
|
||||
'main': executionData.data.main,
|
||||
} as ITaskDataConnections),
|
||||
executionTime: new Date().getTime() - startTime,
|
||||
data: {
|
||||
main: executionData.data.main,
|
||||
} as ITaskDataConnections,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -562,24 +651,31 @@ export class WorkflowExecute {
|
|||
throw error;
|
||||
}
|
||||
|
||||
executionLoop:
|
||||
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
||||
|
||||
if (this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp) {
|
||||
executionLoop: while (
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.length !== 0
|
||||
) {
|
||||
if (
|
||||
this.additionalData.executionTimeoutTimestamp !== undefined &&
|
||||
Date.now() >= this.additionalData.executionTimeoutTimestamp
|
||||
) {
|
||||
gotCancel = true;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (gotCancel === true) {
|
||||
if (gotCancel) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
nodeSuccessData = null;
|
||||
executionError = undefined;
|
||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||
executionData =
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||
executionNode = executionData.node;
|
||||
|
||||
Logger.debug(`Start processing node "${executionNode.name}"`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Start processing node "${executionNode.name}"`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
await this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||
|
||||
// Get the index of the current run
|
||||
|
@ -594,7 +690,10 @@ export class WorkflowExecute {
|
|||
throw new Error('Did stop execution because execution seems to be in endless loop.');
|
||||
}
|
||||
|
||||
if (this.runExecutionData.startData!.runNodeFilter !== undefined && this.runExecutionData.startData!.runNodeFilter!.indexOf(executionNode.name) === -1) {
|
||||
if (
|
||||
this.runExecutionData.startData!.runNodeFilter !== undefined &&
|
||||
this.runExecutionData.startData!.runNodeFilter.indexOf(executionNode.name) === -1
|
||||
) {
|
||||
// If filter is set and node is not on filter skip it, that avoids the problem that it executes
|
||||
// leafs that are parallel to a selected destinationNode. Normally it would execute them because
|
||||
// they have the same parent and it executes all child nodes.
|
||||
|
@ -608,17 +707,24 @@ export class WorkflowExecute {
|
|||
let inputConnections: IConnection[][];
|
||||
let connectionIndex: number;
|
||||
|
||||
inputConnections = workflow.connectionsByDestinationNode[executionNode.name]['main'];
|
||||
// eslint-disable-next-line prefer-const
|
||||
inputConnections = workflow.connectionsByDestinationNode[executionNode.name].main;
|
||||
|
||||
for (connectionIndex = 0; connectionIndex < inputConnections.length; connectionIndex++) {
|
||||
if (workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0) {
|
||||
for (
|
||||
connectionIndex = 0;
|
||||
connectionIndex < inputConnections.length;
|
||||
connectionIndex++
|
||||
) {
|
||||
if (
|
||||
workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0
|
||||
) {
|
||||
// If there is no valid incoming node (if all are disabled)
|
||||
// then ignore that it has inputs and simply execute it as it is without
|
||||
// any data
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!executionData.data!.hasOwnProperty('main')) {
|
||||
if (!executionData.data.hasOwnProperty('main')) {
|
||||
// ExecutionData does not even have the connection set up so can
|
||||
// not have that data, so add it again to be executed later
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||
|
@ -629,7 +735,10 @@ export class WorkflowExecute {
|
|||
// Check if it has the data for all the inputs
|
||||
// The most nodes just have one but merge node for example has two and data
|
||||
// of both inputs has to be available to be able to process the node.
|
||||
if (executionData.data!.main!.length < connectionIndex || executionData.data!.main![connectionIndex] === null) {
|
||||
if (
|
||||
executionData.data.main!.length < connectionIndex ||
|
||||
executionData.data.main![connectionIndex] === null
|
||||
) {
|
||||
// Does not have the data of the connections so add back to stack
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||
lastExecutionTry = currentExecutionTry;
|
||||
|
@ -653,22 +762,25 @@ export class WorkflowExecute {
|
|||
let waitBetweenTries = 0;
|
||||
if (executionData.node.retryOnFail === true) {
|
||||
// TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
|
||||
waitBetweenTries = Math.min(5000, Math.max(0, executionData.node.waitBetweenTries || 1000));
|
||||
waitBetweenTries = Math.min(
|
||||
5000,
|
||||
Math.max(0, executionData.node.waitBetweenTries || 1000),
|
||||
);
|
||||
}
|
||||
|
||||
for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) {
|
||||
// @ts-ignore
|
||||
if (gotCancel === true) {
|
||||
if (gotCancel) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
try {
|
||||
|
||||
if (tryIndex !== 0) {
|
||||
// Reset executionError from previous error try
|
||||
executionError = undefined;
|
||||
if (waitBetweenTries !== 0) {
|
||||
// TODO: Improve that in the future and check if other nodes can
|
||||
// be executed in the meantime
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(undefined);
|
||||
|
@ -677,9 +789,23 @@ export class WorkflowExecute {
|
|||
}
|
||||
}
|
||||
|
||||
Logger.debug(`Running node "${executionNode.name}" started`, { node: executionNode.name, workflowId: workflow.id });
|
||||
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
|
||||
Logger.debug(`Running node "${executionNode.name}" finished successfully`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Running node "${executionNode.name}" started`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
nodeSuccessData = await workflow.runNode(
|
||||
executionData.node,
|
||||
executionData.data,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.additionalData,
|
||||
NodeExecuteFunctions,
|
||||
this.mode,
|
||||
);
|
||||
Logger.debug(`Running node "${executionNode.name}" finished successfully`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
|
||||
if (nodeSuccessData === undefined) {
|
||||
// Node did not get executed
|
||||
|
@ -699,7 +825,7 @@ export class WorkflowExecute {
|
|||
}
|
||||
}
|
||||
|
||||
if (nodeSuccessData === null && !this.runExecutionData.waitTill!!) {
|
||||
if (nodeSuccessData === null && !this.runExecutionData.waitTill!) {
|
||||
// If null gets returned it means that the node did succeed
|
||||
// but did not have any data. So the branch should end
|
||||
// (meaning the nodes afterwards should not be processed)
|
||||
|
@ -708,7 +834,6 @@ export class WorkflowExecute {
|
|||
|
||||
break;
|
||||
} catch (error) {
|
||||
|
||||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||
|
||||
executionError = {
|
||||
|
@ -717,7 +842,10 @@ export class WorkflowExecute {
|
|||
stack: error.stack,
|
||||
};
|
||||
|
||||
Logger.debug(`Running node "${executionNode.name}" finished with error`, { node: executionNode.name, workflowId: workflow.id });
|
||||
Logger.debug(`Running node "${executionNode.name}" finished with error`, {
|
||||
node: executionNode.name,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -729,7 +857,7 @@ export class WorkflowExecute {
|
|||
}
|
||||
taskData = {
|
||||
startTime,
|
||||
executionTime: (new Date().getTime()) - startTime,
|
||||
executionTime: new Date().getTime() - startTime,
|
||||
};
|
||||
|
||||
if (executionError !== undefined) {
|
||||
|
@ -741,7 +869,7 @@ export class WorkflowExecute {
|
|||
// Simply get the input data of the node if it has any and pass it through
|
||||
// to the next node
|
||||
if (executionData.data.main[0] !== null) {
|
||||
nodeSuccessData = [executionData.data.main[0] as INodeExecutionData[]];
|
||||
nodeSuccessData = [executionData.data.main[0]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -751,30 +879,46 @@ export class WorkflowExecute {
|
|||
// Add the execution data again so that it can get restarted
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Node executed successfully. So add data and go on.
|
||||
taskData.data = ({
|
||||
'main': nodeSuccessData,
|
||||
} as ITaskDataConnections);
|
||||
taskData.data = {
|
||||
main: nodeSuccessData,
|
||||
} as ITaskDataConnections;
|
||||
|
||||
this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
||||
|
||||
if (this.runExecutionData.startData && this.runExecutionData.startData.destinationNode && this.runExecutionData.startData.destinationNode === executionNode.name) {
|
||||
if (
|
||||
this.runExecutionData.startData &&
|
||||
this.runExecutionData.startData.destinationNode &&
|
||||
this.runExecutionData.startData.destinationNode === executionNode.name
|
||||
) {
|
||||
// Before stopping, make sure we are executing hooks so
|
||||
// That frontend is notified for example for manual executions.
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
// If destination node is defined and got executed stop execution
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.runExecutionData.waitTill!!) {
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
if (this.runExecutionData.waitTill!) {
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
|
||||
// Add the node back to the stack that the workflow can start to execute again from that node
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
|
@ -786,24 +930,46 @@ export class WorkflowExecute {
|
|||
// be executed next
|
||||
if (workflow.connectionsBySourceNode.hasOwnProperty(executionNode.name)) {
|
||||
if (workflow.connectionsBySourceNode[executionNode.name].hasOwnProperty('main')) {
|
||||
let outputIndex: string, connectionData: IConnection;
|
||||
let outputIndex: string;
|
||||
let connectionData: IConnection;
|
||||
// Iterate over all the outputs
|
||||
|
||||
// Add the nodes to be executed
|
||||
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name]['main']) {
|
||||
if (!workflow.connectionsBySourceNode[executionNode.name]['main'].hasOwnProperty(outputIndex)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name].main) {
|
||||
if (
|
||||
!workflow.connectionsBySourceNode[executionNode.name].main.hasOwnProperty(
|
||||
outputIndex,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over all the different connections of this output
|
||||
for (connectionData of workflow.connectionsBySourceNode[executionNode.name]['main'][outputIndex]) {
|
||||
for (connectionData of workflow.connectionsBySourceNode[executionNode.name].main[
|
||||
outputIndex
|
||||
]) {
|
||||
if (!workflow.nodes.hasOwnProperty(connectionData.node)) {
|
||||
return Promise.reject(new Error(`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`));
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (nodeSuccessData![outputIndex] && (nodeSuccessData![outputIndex].length !== 0 || connectionData.index > 0)) {
|
||||
if (
|
||||
nodeSuccessData![outputIndex] &&
|
||||
(nodeSuccessData![outputIndex].length !== 0 || connectionData.index > 0)
|
||||
) {
|
||||
// Add the node only if it did execute or if connected to second "optional" input
|
||||
this.addNodeToBeExecuted(workflow, connectionData, parseInt(outputIndex, 10), executionNode.name, nodeSuccessData!, runIndex);
|
||||
this.addNodeToBeExecuted(
|
||||
workflow,
|
||||
connectionData,
|
||||
parseInt(outputIndex, 10),
|
||||
executionNode.name,
|
||||
nodeSuccessData!,
|
||||
runIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -814,15 +980,22 @@ export class WorkflowExecute {
|
|||
// Execute hooks now to make sure that all hooks are executed properly
|
||||
// Await is needed to make sure that we don't fall into concurrency problems
|
||||
// When saving node execution data
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
|
||||
await this.executeHook('nodeExecuteAfter', [
|
||||
executionNode.name,
|
||||
taskData,
|
||||
this.runExecutionData,
|
||||
]);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
})()
|
||||
.then(async () => {
|
||||
if (gotCancel && executionError === undefined) {
|
||||
return this.processSuccessExecution(startedAt, workflow, new WorkflowOperationError('Workflow has been canceled or timed out!'));
|
||||
return this.processSuccessExecution(
|
||||
startedAt,
|
||||
workflow,
|
||||
new WorkflowOperationError('Workflow has been canceled or timed out!'),
|
||||
);
|
||||
}
|
||||
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||
})
|
||||
|
@ -837,13 +1010,18 @@ export class WorkflowExecute {
|
|||
|
||||
// Check if static data changed
|
||||
let newStaticData: IDataObject | undefined;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed
|
||||
newStaticData = workflow.staticData;
|
||||
}
|
||||
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(error => {
|
||||
await this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
(error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('There was a problem running hook "workflowExecuteAfter"', error);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return fullRunData;
|
||||
});
|
||||
|
@ -852,20 +1030,29 @@ export class WorkflowExecute {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
async processSuccessExecution(
|
||||
startedAt: Date,
|
||||
workflow: Workflow,
|
||||
executionError?: ExecutionError,
|
||||
// @ts-ignore
|
||||
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: ExecutionError): PCancelable<IRun> {
|
||||
): PCancelable<IRun> {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
|
||||
if (executionError !== undefined) {
|
||||
Logger.verbose(`Workflow execution finished with error`, { error: executionError, workflowId: workflow.id });
|
||||
Logger.verbose(`Workflow execution finished with error`, {
|
||||
error: executionError,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
fullRunData.data.resultData.error = {
|
||||
...executionError,
|
||||
message: executionError.message,
|
||||
stack: executionError.stack,
|
||||
} as ExecutionError;
|
||||
} else if (this.runExecutionData.waitTill!!) {
|
||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, { workflowId: workflow.id });
|
||||
} else if (this.runExecutionData.waitTill!) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, {
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
fullRunData.waitTill = this.runExecutionData.waitTill;
|
||||
} else {
|
||||
Logger.verbose(`Workflow execution finished successfully`, { workflowId: workflow.id });
|
||||
|
@ -874,6 +1061,7 @@ export class WorkflowExecute {
|
|||
|
||||
// Check if static data changed
|
||||
let newStaticData: IDataObject | undefined;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed
|
||||
newStaticData = workflow.staticData;
|
||||
|
@ -894,5 +1082,4 @@ export class WorkflowExecute {
|
|||
|
||||
return fullRunData;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
try {
|
||||
require('source-map-support').install();
|
||||
} catch (error) {
|
||||
/* eslint-disable import/no-cycle */
|
||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||
import * as UserSettings from './UserSettings';
|
||||
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, import/no-extraneous-dependencies, global-require, @typescript-eslint/no-var-requires
|
||||
require('source-map-support').install();
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (error) {}
|
||||
|
||||
export * from './ActiveWorkflows';
|
||||
export * from './ActiveWebhooks';
|
||||
|
@ -13,10 +17,4 @@ export * from './Interfaces';
|
|||
export * from './LoadNodeParameterOptions';
|
||||
export * from './NodeExecuteFunctions';
|
||||
export * from './WorkflowExecute';
|
||||
|
||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||
import * as UserSettings from './UserSettings';
|
||||
export {
|
||||
NodeExecuteFunctions,
|
||||
UserSettings,
|
||||
};
|
||||
export { NodeExecuteFunctions, UserSettings };
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
|
||||
import { Credentials } from '../src';
|
||||
|
||||
describe('Credentials', () => {
|
||||
|
||||
describe('without nodeType set', () => {
|
||||
|
||||
test('should be able to set and read key data without initial data set', () => {
|
||||
|
||||
const credentials = new Credentials('testName', 'testType', []);
|
||||
|
||||
const key = 'key1';
|
||||
|
@ -20,7 +16,6 @@ describe('Credentials', () => {
|
|||
});
|
||||
|
||||
test('should be able to set and read key data with initial data set', () => {
|
||||
|
||||
const key = 'key2';
|
||||
const password = 'password';
|
||||
|
||||
|
@ -39,13 +34,10 @@ describe('Credentials', () => {
|
|||
// Read the data which got provided encrypted on init
|
||||
expect(credentials.getDataKey('key1', password)).toEqual(initialData);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with nodeType set', () => {
|
||||
|
||||
test('should be able to set and read key data without initial data set', () => {
|
||||
|
||||
const nodeAccess = [
|
||||
{
|
||||
nodeType: 'base.noOp',
|
||||
|
@ -72,7 +64,9 @@ describe('Credentials', () => {
|
|||
credentials.getDataKey(key, password, 'base.otherNode');
|
||||
expect(true).toBe(false);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('The node of type "base.otherNode" does not have access to credentials "testName" of type "testType".');
|
||||
expect(e.message).toBe(
|
||||
'The node of type "base.otherNode" does not have access to credentials "testName" of type "testType".',
|
||||
);
|
||||
}
|
||||
|
||||
// Get the data which will be saved in database
|
||||
|
@ -81,8 +75,9 @@ describe('Credentials', () => {
|
|||
expect(dbData.type).toEqual('testType');
|
||||
expect(dbData.nodesAccess).toEqual(nodeAccess);
|
||||
// Compare only the first 6 characters as the rest seems to change with each execution
|
||||
expect(dbData.data!.slice(0, 6)).toEqual('U2FsdGVkX1+wpQWkj+YTzaPSNTFATjnlmFKIsUTZdhk='.slice(0, 6));
|
||||
expect(dbData.data!.slice(0, 6)).toEqual(
|
||||
'U2FsdGVkX1+wpQWkj+YTzaPSNTFATjnlmFKIsUTZdhk='.slice(0, 6),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -18,30 +18,27 @@ import {
|
|||
WorkflowHooks,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
Credentials,
|
||||
IDeferredPromise,
|
||||
IExecuteFunctions,
|
||||
} from '../src';
|
||||
|
||||
import { Credentials, IDeferredPromise, IExecuteFunctions } from '../src';
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
getDecrypted(name: string, type: string): Promise<ICredentialDataDecryptedObject> {
|
||||
return new Promise(res => res({}));
|
||||
return new Promise((res) => res({}));
|
||||
}
|
||||
|
||||
getCredentials(name: string, type: string): Promise<Credentials> {
|
||||
return new Promise(res => {
|
||||
return new Promise((res) => {
|
||||
res(new Credentials('', '', [], ''));
|
||||
});
|
||||
}
|
||||
|
||||
async updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void> {}
|
||||
async updateCredentials(
|
||||
name: string,
|
||||
type: string,
|
||||
data: ICredentialDataDecryptedObject,
|
||||
): Promise<void> {}
|
||||
}
|
||||
|
||||
|
||||
class NodeTypesClass implements INodeTypes {
|
||||
|
||||
nodeTypes: INodeTypeData = {
|
||||
'n8n-nodes-base.if': {
|
||||
sourcePath: '',
|
||||
|
@ -161,9 +158,7 @@ class NodeTypesClass implements INodeTypes {
|
|||
type: 'number',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operation: [
|
||||
'isEmpty',
|
||||
],
|
||||
operation: ['isEmpty'],
|
||||
},
|
||||
},
|
||||
default: 0,
|
||||
|
@ -229,10 +224,7 @@ class NodeTypesClass implements INodeTypes {
|
|||
type: 'string',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operation: [
|
||||
'isEmpty',
|
||||
'regex',
|
||||
],
|
||||
operation: ['isEmpty', 'regex'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
@ -244,9 +236,7 @@ class NodeTypesClass implements INodeTypes {
|
|||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'regex',
|
||||
],
|
||||
operation: ['regex'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
@ -274,7 +264,8 @@ class NodeTypesClass implements INodeTypes {
|
|||
},
|
||||
],
|
||||
default: 'all',
|
||||
description: 'If multiple rules got set this settings decides if it is true as soon as ANY condition matches or only if ALL get meet.',
|
||||
description:
|
||||
'If multiple rules got set this settings decides if it is true as soon as ANY condition matches or only if ALL get meet.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -291,19 +282,30 @@ class NodeTypesClass implements INodeTypes {
|
|||
const compareOperationFunctions: {
|
||||
[key: string]: (value1: NodeParameterValue, value2: NodeParameterValue) => boolean;
|
||||
} = {
|
||||
contains: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || '').toString().includes((value2 || '').toString()),
|
||||
notContains: (value1: NodeParameterValue, value2: NodeParameterValue) => !(value1 || '').toString().includes((value2 || '').toString()),
|
||||
endsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 as string).endsWith(value2 as string),
|
||||
contains: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || '').toString().includes((value2 || '').toString()),
|
||||
notContains: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
!(value1 || '').toString().includes((value2 || '').toString()),
|
||||
endsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 as string).endsWith(value2 as string),
|
||||
equal: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 === value2,
|
||||
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
||||
larger: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) > (value2 || 0),
|
||||
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) >= (value2 || 0),
|
||||
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) < (value2 || 0),
|
||||
smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) <= (value2 || 0),
|
||||
startsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 as string).startsWith(value2 as string),
|
||||
isEmpty: (value1: NodeParameterValue) => [undefined, null, ''].includes(value1 as string),
|
||||
larger: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) > (value2 || 0),
|
||||
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) >= (value2 || 0),
|
||||
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) < (value2 || 0),
|
||||
smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) <= (value2 || 0),
|
||||
startsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 as string).startsWith(value2 as string),
|
||||
isEmpty: (value1: NodeParameterValue) =>
|
||||
[undefined, null, ''].includes(value1 as string),
|
||||
regex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
||||
const regexMatch = (value2 || '')
|
||||
.toString()
|
||||
.match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
||||
|
||||
let regex: RegExp;
|
||||
if (!regexMatch) {
|
||||
|
@ -319,18 +321,13 @@ class NodeTypesClass implements INodeTypes {
|
|||
};
|
||||
|
||||
// The different dataTypes to check the values in
|
||||
const dataTypes = [
|
||||
'boolean',
|
||||
'number',
|
||||
'string',
|
||||
];
|
||||
const dataTypes = ['boolean', 'number', 'string'];
|
||||
|
||||
// Itterate over all items to check which ones should be output as via output "true" and
|
||||
// which ones via output "false"
|
||||
let dataType: string;
|
||||
let compareOperationResult: boolean;
|
||||
itemLoop:
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
itemLoop: for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
item = items[itemIndex];
|
||||
|
||||
let compareData: INodeParameters;
|
||||
|
@ -340,9 +337,16 @@ class NodeTypesClass implements INodeTypes {
|
|||
// Check all the values of the different dataTypes
|
||||
for (dataType of dataTypes) {
|
||||
// Check all the values of the current dataType
|
||||
for (compareData of this.getNodeParameter(`conditions.${dataType}`, itemIndex, []) as INodeParameters[]) {
|
||||
for (compareData of this.getNodeParameter(
|
||||
`conditions.${dataType}`,
|
||||
itemIndex,
|
||||
[],
|
||||
) as INodeParameters[]) {
|
||||
// Check if the values passes
|
||||
compareOperationResult = compareOperationFunctions[compareData.operation as string](compareData.value1 as NodeParameterValue, compareData.value2 as NodeParameterValue);
|
||||
compareOperationResult = compareOperationFunctions[compareData.operation as string](
|
||||
compareData.value1 as NodeParameterValue,
|
||||
compareData.value2 as NodeParameterValue,
|
||||
);
|
||||
|
||||
if (compareOperationResult === true && combineOperation === 'any') {
|
||||
// If it passes and the operation is "any" we do not have to check any
|
||||
|
@ -397,21 +401,25 @@ class NodeTypesClass implements INodeTypes {
|
|||
{
|
||||
name: 'Append',
|
||||
value: 'append',
|
||||
description: 'Combines data of both inputs. The output will contain items of input 1 and input 2.',
|
||||
description:
|
||||
'Combines data of both inputs. The output will contain items of input 1 and input 2.',
|
||||
},
|
||||
{
|
||||
name: 'Pass-through',
|
||||
value: 'passThrough',
|
||||
description: 'Passes through data of one input. The output will conain only items of the defined input.',
|
||||
description:
|
||||
'Passes through data of one input. The output will conain only items of the defined input.',
|
||||
},
|
||||
{
|
||||
name: 'Wait',
|
||||
value: 'wait',
|
||||
description: 'Waits till data of both inputs is available and will then output a single empty item.',
|
||||
description:
|
||||
'Waits till data of both inputs is available and will then output a single empty item.',
|
||||
},
|
||||
],
|
||||
default: 'append',
|
||||
description: 'How data should be merged. If it should simply<br />be appended or merged depending on a property.',
|
||||
description:
|
||||
'How data should be merged. If it should simply<br />be appended or merged depending on a property.',
|
||||
},
|
||||
{
|
||||
displayName: 'Output Data',
|
||||
|
@ -419,9 +427,7 @@ class NodeTypesClass implements INodeTypes {
|
|||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: [
|
||||
'passThrough',
|
||||
],
|
||||
mode: ['passThrough'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
|
@ -512,7 +518,8 @@ class NodeTypesClass implements INodeTypes {
|
|||
name: 'keepOnlySet',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'If only the values set on this node should be<br />kept and all others removed.',
|
||||
description:
|
||||
'If only the values set on this node should be<br />kept and all others removed.',
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Set',
|
||||
|
@ -534,7 +541,8 @@ class NodeTypesClass implements INodeTypes {
|
|||
name: 'name',
|
||||
type: 'string',
|
||||
default: 'propertyName',
|
||||
description: 'Name of the property to write data to.<br />Supports dot-notation.<br />Example: "data.person[0].name"',
|
||||
description:
|
||||
'Name of the property to write data to.<br />Supports dot-notation.<br />Example: "data.person[0].name"',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
|
@ -554,7 +562,8 @@ class NodeTypesClass implements INodeTypes {
|
|||
name: 'name',
|
||||
type: 'string',
|
||||
default: 'propertyName',
|
||||
description: 'Name of the property to write data to.<br />Supports dot-notation.<br />Example: "data.person[0].name"',
|
||||
description:
|
||||
'Name of the property to write data to.<br />Supports dot-notation.<br />Example: "data.person[0].name"',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
|
@ -574,7 +583,8 @@ class NodeTypesClass implements INodeTypes {
|
|||
name: 'name',
|
||||
type: 'string',
|
||||
default: 'propertyName',
|
||||
description: 'Name of the property to write data to.<br />Supports dot-notation.<br />Example: "data.person[0].name"',
|
||||
description:
|
||||
'Name of the property to write data to.<br />Supports dot-notation.<br />Example: "data.person[0].name"',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
|
@ -610,7 +620,6 @@ class NodeTypesClass implements INodeTypes {
|
|||
],
|
||||
},
|
||||
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
if (items.length === 0) {
|
||||
|
@ -643,31 +652,37 @@ class NodeTypesClass implements INodeTypes {
|
|||
}
|
||||
|
||||
// Add boolean values
|
||||
(this.getNodeParameter('values.boolean', itemIndex, []) as INodeParameters[]).forEach((setItem) => {
|
||||
(this.getNodeParameter('values.boolean', itemIndex, []) as INodeParameters[]).forEach(
|
||||
(setItem) => {
|
||||
if (options.dotNotation === false) {
|
||||
newItem.json[setItem.name as string] = !!setItem.value;
|
||||
} else {
|
||||
set(newItem.json, setItem.name as string, !!setItem.value);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Add number values
|
||||
(this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach((setItem) => {
|
||||
(this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach(
|
||||
(setItem) => {
|
||||
if (options.dotNotation === false) {
|
||||
newItem.json[setItem.name as string] = setItem.value;
|
||||
} else {
|
||||
set(newItem.json, setItem.name as string, setItem.value);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Add string values
|
||||
(this.getNodeParameter('values.string', itemIndex, []) as INodeParameters[]).forEach((setItem) => {
|
||||
(this.getNodeParameter('values.string', itemIndex, []) as INodeParameters[]).forEach(
|
||||
(setItem) => {
|
||||
if (options.dotNotation === false) {
|
||||
newItem.json[setItem.name as string] = setItem.value;
|
||||
} else {
|
||||
set(newItem.json, setItem.name as string, setItem.value);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
returnData.push(newItem);
|
||||
}
|
||||
|
@ -702,7 +717,7 @@ class NodeTypesClass implements INodeTypes {
|
|||
},
|
||||
};
|
||||
|
||||
async init(nodeTypes: INodeTypeData): Promise<void> { }
|
||||
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
||||
|
||||
getAll(): INodeType[] {
|
||||
return Object.values(this.nodeTypes).map((data) => data.type);
|
||||
|
@ -715,7 +730,6 @@ class NodeTypesClass implements INodeTypes {
|
|||
|
||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||
|
||||
|
||||
export function NodeTypes(): NodeTypesClass {
|
||||
if (nodeTypesInstance === undefined) {
|
||||
nodeTypesInstance = new NodeTypesClass();
|
||||
|
@ -725,8 +739,10 @@ export function NodeTypes(): NodeTypesClass {
|
|||
return nodeTypesInstance;
|
||||
}
|
||||
|
||||
|
||||
export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun>, nodeExecutionOrder: string[]): IWorkflowExecuteAdditionalData {
|
||||
export function WorkflowExecuteAdditionalData(
|
||||
waitPromise: IDeferredPromise<IRun>,
|
||||
nodeExecutionOrder: string[],
|
||||
): IWorkflowExecuteAdditionalData {
|
||||
const hookFunctions = {
|
||||
nodeExecuteAfter: [
|
||||
async (nodeName: string, data: ITaskData): Promise<void> => {
|
||||
|
@ -752,7 +768,7 @@ export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun
|
|||
return {
|
||||
credentialsHelper: new CredentialsHelper(''),
|
||||
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData),
|
||||
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {}, // tslint:disable-line:no-any
|
||||
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {},
|
||||
sendMessageToUI: (message: string) => {},
|
||||
restApiUrl: '',
|
||||
encryptionKey: 'test',
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,11 +20,10 @@
|
|||
"build:storybook": "build-storybook",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"test:unit": "vue-cli-service test:unit --passWithNoTests",
|
||||
"lint": "vue-cli-service lint",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"build:theme": "gulp build:theme",
|
||||
"watch:theme": "gulp watch:theme",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json"
|
||||
"watch:theme": "gulp watch:theme"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "1.x",
|
||||
|
@ -49,27 +48,27 @@
|
|||
"@storybook/addon-links": "^6.3.6",
|
||||
"@storybook/vue": "^6.3.6",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||
"@typescript-eslint/parser": "^4.29.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.6",
|
||||
"@vue/cli-plugin-unit-jest": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-vue": "^7.16.0",
|
||||
"fibers": "^5.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier": "^2.3.2",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"storybook-addon-designs": "^6.0.1",
|
||||
"typescript": "~3.9.7",
|
||||
"typescript": "~4.3.5",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"gulp-autoprefixer": "^4.0.0",
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
|
@ -6,5 +6,8 @@ module.exports = {
|
|||
// transpileDependencies: [
|
||||
// /\/node_modules\/quill/
|
||||
// ]
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
],
|
||||
};
|
||||
// // https://stackoverflow.com/questions/44625868/es6-babel-class-constructor-cannot-be-invoked-without-new
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"scripts": {
|
||||
"build": "cross-env VUE_APP_PUBLIC_PATH=\"/%BASE_PATH%/\" vue-cli-service build",
|
||||
"dev": "npm run serve",
|
||||
"lint": "vue-cli-service lint",
|
||||
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/editor-ui/**/**.ts --write",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"serve": "cross-env VUE_APP_URL_BASE_API=http://localhost:5678/ vue-cli-service serve",
|
||||
"test": "npm run test:unit",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"test:e2e": "vue-cli-service test:e2e",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
|
@ -44,11 +44,11 @@
|
|||
"@types/node": "^14.14.40",
|
||||
"@types/quill": "^2.0.1",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||
"@typescript-eslint/parser": "^4.29.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.6",
|
||||
"@vue/cli-plugin-unit-jest": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-standard": "^5.0.1",
|
||||
|
@ -60,9 +60,9 @@
|
|||
"cross-env": "^7.0.2",
|
||||
"dateformat": "^3.0.3",
|
||||
"element-ui": "~2.13.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-import": "^2.19.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-vue": "^7.16.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"flatted": "^2.0.0",
|
||||
"jquery": "^3.4.1",
|
||||
|
@ -81,7 +81,7 @@
|
|||
"string-template-parser": "^1.2.6",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7",
|
||||
"typescript": "~4.3.5",
|
||||
"uuid": "^8.3.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<template functional>
|
||||
<!-- eslint-disable-next-line vue/no-mutating-props -->
|
||||
<a v-if="props.version" :set="version = props.version" :href="version.documentationUrl" target="_blank" :class="$style.card">
|
||||
<div :class="$style.header">
|
||||
<div>
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import {
|
||||
UserSettings,
|
||||
} from "n8n-core";
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
|
||||
import {
|
||||
buildFiles,
|
||||
IBuildOptions,
|
||||
} from '../src';
|
||||
import { buildFiles, IBuildOptions } from '../src';
|
||||
|
||||
export class Build extends Command {
|
||||
static description = 'Builds credentials and nodes and copies it to n8n custom extension folder';
|
||||
|
@ -24,11 +19,14 @@ export class Build extends Command {
|
|||
description: `The path to copy the compiles files to [default: ${UserSettings.getUserN8nFolderCustomExtensionPath()}]`,
|
||||
}),
|
||||
watch: flags.boolean({
|
||||
description: 'Starts in watch mode and automatically builds and copies file whenever they change',
|
||||
description:
|
||||
'Starts in watch mode and automatically builds and copies file whenever they change',
|
||||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const { flags } = this.parse(Build);
|
||||
|
||||
this.log('\nBuild credentials and nodes');
|
||||
|
@ -47,13 +45,12 @@ export class Build extends Command {
|
|||
const outputDirectory = await buildFiles(options);
|
||||
|
||||
this.log(`The nodes got build and saved into the following folder:\n${outputDirectory}`);
|
||||
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
this.log(`\nGOT ERROR: "${error.message}"`);
|
||||
this.log('====================================');
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
this.log(error.stack);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import * as changeCase from 'change-case';
|
||||
import * as fs from 'fs';
|
||||
import * as inquirer from 'inquirer';
|
||||
import { Command } from '@oclif/command';
|
||||
import { join } from 'path';
|
||||
|
||||
const { promisify } = require('util');
|
||||
const fsAccess = promisify(fs.access);
|
||||
import { createTemplate } from '../src';
|
||||
|
||||
import {
|
||||
createTemplate
|
||||
} from '../src';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { promisify } = require('util');
|
||||
|
||||
const fsAccess = promisify(fs.access);
|
||||
|
||||
export class New extends Command {
|
||||
static description = 'Create new credentials/node';
|
||||
|
||||
static examples = [
|
||||
`$ n8n-node-dev new`,
|
||||
];
|
||||
static examples = [`$ n8n-node-dev new`];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async run() {
|
||||
|
||||
try {
|
||||
this.log('\nCreate new credentials/node');
|
||||
this.log('=========================');
|
||||
|
@ -30,10 +31,7 @@ export class New extends Command {
|
|||
type: 'list',
|
||||
default: 'Node',
|
||||
message: 'What do you want to create?',
|
||||
choices: [
|
||||
'Credentials',
|
||||
'Node',
|
||||
],
|
||||
choices: ['Credentials', 'Node'],
|
||||
};
|
||||
|
||||
const typeAnswers = await inquirer.prompt(typeQuestion);
|
||||
|
@ -43,7 +41,6 @@ export class New extends Command {
|
|||
let defaultName = '';
|
||||
let getDescription = false;
|
||||
|
||||
|
||||
if (typeAnswers.type === 'Node') {
|
||||
// Create new node
|
||||
|
||||
|
@ -54,11 +51,7 @@ export class New extends Command {
|
|||
type: 'list',
|
||||
default: 'Execute',
|
||||
message: 'What kind of node do you want to create?',
|
||||
choices: [
|
||||
'Execute',
|
||||
'Trigger',
|
||||
'Webhook',
|
||||
],
|
||||
choices: ['Execute', 'Trigger', 'Webhook'],
|
||||
};
|
||||
|
||||
const nodeTypeAnswers = await inquirer.prompt(nodeTypeQuestion);
|
||||
|
@ -91,7 +84,7 @@ export class New extends Command {
|
|||
},
|
||||
];
|
||||
|
||||
if (getDescription === true) {
|
||||
if (getDescription) {
|
||||
// Get also a node description
|
||||
additionalQuestions.push({
|
||||
name: 'description',
|
||||
|
@ -101,13 +94,19 @@ export class New extends Command {
|
|||
});
|
||||
}
|
||||
|
||||
const additionalAnswers = await inquirer.prompt(additionalQuestions as inquirer.QuestionCollection);
|
||||
const additionalAnswers = await inquirer.prompt(
|
||||
additionalQuestions as inquirer.QuestionCollection,
|
||||
);
|
||||
|
||||
const nodeName = additionalAnswers.name;
|
||||
|
||||
// Define the source file to be used and the location and name of the new
|
||||
// node file
|
||||
const destinationFilePath = join(process.cwd(), `${changeCase.pascalCase(nodeName)}.${typeAnswers.type.toLowerCase()}.ts`);
|
||||
const destinationFilePath = join(
|
||||
process.cwd(),
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
`${changeCase.pascalCase(nodeName)}.${typeAnswers.type.toLowerCase()}.ts`,
|
||||
);
|
||||
|
||||
const sourceFilePath = join(__dirname, '../../templates', sourceFolder, sourceFileName);
|
||||
|
||||
|
@ -150,12 +149,13 @@ export class New extends Command {
|
|||
this.log('\nExecution was successfull:');
|
||||
this.log('====================================');
|
||||
|
||||
this.log('Node got created: ' + destinationFilePath);
|
||||
this.log(`Node got created: ${destinationFilePath}`);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
this.log(`\nGOT ERROR: "${error.message}"`);
|
||||
this.log('====================================');
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
this.log(error.stack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@
|
|||
"scripts": {
|
||||
"dev": "npm run watch",
|
||||
"build": "tsc",
|
||||
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/node-dev/**/**.ts --write",
|
||||
"lint": "cd ../.. && node_modules/eslint/bin/eslint.js packages/node-dev",
|
||||
"lintfix": "cd ../.. && node_modules/eslint/bin/eslint.js packages/node-dev --fix",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -64,7 +65,7 @@
|
|||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
"tmp-promise": "^2.0.2",
|
||||
"typescript": "~3.9.7"
|
||||
"tmp-promise": "^3.0.2",
|
||||
"typescript": "~4.3.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { ChildProcess, spawn } from 'child_process';
|
||||
const copyfiles = require('copyfiles');
|
||||
|
||||
import {
|
||||
readFile as fsReadFile,
|
||||
} from 'fs/promises';
|
||||
import {
|
||||
write as fsWrite,
|
||||
} from 'fs';
|
||||
import { readFile as fsReadFile } from 'fs/promises';
|
||||
import { write as fsWrite } from 'fs';
|
||||
|
||||
import { join } from 'path';
|
||||
import { file } from 'tmp-promise';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const fsReadFileAsync = promisify(fsReadFile);
|
||||
const fsWriteAsync = promisify(fsWrite);
|
||||
|
||||
import { UserSettings } from 'n8n-core';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { IBuildOptions } from '.';
|
||||
|
||||
import {
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
|
||||
const copyfiles = require('copyfiles');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const fsReadFileAsync = promisify(fsReadFile);
|
||||
const fsWriteAsync = promisify(fsWrite);
|
||||
|
||||
/**
|
||||
* Create a custom tsconfig file as tsc currently has no way to define a base
|
||||
|
@ -30,23 +27,26 @@ import {
|
|||
* @export
|
||||
* @returns
|
||||
*/
|
||||
export async function createCustomTsconfig () {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export async function createCustomTsconfig() {
|
||||
// Get path to simple tsconfig file which should be used for build
|
||||
const tsconfigPath = join(__dirname, '../../src/tsconfig-build.json');
|
||||
|
||||
// Read the tsconfi file
|
||||
const tsConfigString = await fsReadFile(tsconfigPath, { encoding: 'utf8'}) as string;
|
||||
const tsConfigString = await fsReadFile(tsconfigPath, { encoding: 'utf8' });
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const tsConfig = JSON.parse(tsConfigString);
|
||||
|
||||
// Set absolute include paths
|
||||
const newIncludeFiles = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const includeFile of tsConfig.include) {
|
||||
newIncludeFiles.push(join(process.cwd(), includeFile));
|
||||
}
|
||||
tsConfig.include = newIncludeFiles;
|
||||
|
||||
// Write new custom tsconfig file
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { fd, path, cleanup } = await file({ dir: process.cwd() });
|
||||
await fsWriteAsync(fd, Buffer.from(JSON.stringify(tsConfig, null, 2), 'utf8'));
|
||||
|
||||
|
@ -56,7 +56,6 @@ export async function createCustomTsconfig () {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds and copies credentials and nodes
|
||||
*
|
||||
|
@ -64,7 +63,8 @@ export async function createCustomTsconfig () {
|
|||
* @param {IBuildOptions} [options] Options to overwrite default behaviour
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function buildFiles (options?: IBuildOptions): Promise<string> {
|
||||
export async function buildFiles(options?: IBuildOptions): Promise<string> {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing, no-param-reassign
|
||||
options = options || {};
|
||||
|
||||
let typescriptPath;
|
||||
|
@ -79,24 +79,31 @@ export async function buildFiles (options?: IBuildOptions): Promise<string> {
|
|||
|
||||
const tsconfigData = await createCustomTsconfig();
|
||||
|
||||
const outputDirectory = options.destinationFolder || UserSettings.getUserN8nFolderCustomExtensionPath();
|
||||
const outputDirectory =
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
options.destinationFolder || UserSettings.getUserN8nFolderCustomExtensionPath();
|
||||
|
||||
// Supply a node base path so that it finds n8n-core and n8n-workflow
|
||||
const nodeModulesPath = join(__dirname, '../../node_modules/');
|
||||
let buildCommand = `${tscPath} --p ${tsconfigData.path} --outDir ${outputDirectory} --rootDir ${process.cwd()} --baseUrl ${nodeModulesPath}`;
|
||||
let buildCommand = `${tscPath} --p ${
|
||||
tsconfigData.path
|
||||
} --outDir ${outputDirectory} --rootDir ${process.cwd()} --baseUrl ${nodeModulesPath}`;
|
||||
if (options.watch === true) {
|
||||
buildCommand += ' --watch';
|
||||
}
|
||||
|
||||
let buildProcess: ChildProcess;
|
||||
try {
|
||||
buildProcess = spawn('node', buildCommand.split(' '), { windowsVerbatimArguments: true, cwd: process.cwd() });
|
||||
buildProcess = spawn('node', buildCommand.split(' '), {
|
||||
windowsVerbatimArguments: true,
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
// Forward the output of the child process to the main one
|
||||
// that the user can see what is happening
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
buildProcess.stdout.pipe(process.stdout);
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
buildProcess.stderr.pipe(process.stderr);
|
||||
|
||||
// Make sure that the child process gets also always terminated
|
||||
|
@ -105,27 +112,33 @@ export async function buildFiles (options?: IBuildOptions): Promise<string> {
|
|||
buildProcess.kill();
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
let errorMessage = error.message;
|
||||
|
||||
if (error.stdout !== undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
errorMessage = `${errorMessage}\nGot following output:\n${error.stdout}`;
|
||||
}
|
||||
|
||||
// Remove the tmp tsconfig file
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
tsconfigData.cleanup();
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return new Promise((resolve, reject) => {
|
||||
['*.png', '*.node.json'].forEach(filenamePattern => {
|
||||
copyfiles(
|
||||
[join(process.cwd(), `./${filenamePattern}`), outputDirectory],
|
||||
{ up: true },
|
||||
() => resolve(outputDirectory));
|
||||
['*.png', '*.node.json'].forEach((filenamePattern) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
copyfiles([join(process.cwd(), `./${filenamePattern}`), outputDirectory], { up: true }, () =>
|
||||
resolve(outputDirectory),
|
||||
);
|
||||
});
|
||||
buildProcess.on('exit', code => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
buildProcess.on('exit', (code) => {
|
||||
// Remove the tmp tsconfig file
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
tsconfigData.cleanup();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
|
||||
import * as fs from 'fs';
|
||||
import {replaceInFile, ReplaceInFileConfig } from 'replace-in-file';
|
||||
import { replaceInFile, ReplaceInFileConfig } from 'replace-in-file';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
|
||||
const { promisify } = require('util');
|
||||
const fsCopyFile = promisify(fs.copyFile);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
const fsCopyFile = promisify(fs.copyFile);
|
||||
|
||||
/**
|
||||
* Creates a new credentials or node
|
||||
|
@ -15,16 +16,18 @@ const fsCopyFile = promisify(fs.copyFile);
|
|||
* @param {object} replaceValues The values to replace in the template file
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function createTemplate(sourceFilePath: string, destinationFilePath: string, replaceValues: object): Promise<void> {
|
||||
|
||||
export async function createTemplate(
|
||||
sourceFilePath: string,
|
||||
destinationFilePath: string,
|
||||
replaceValues: object,
|
||||
): Promise<void> {
|
||||
// Copy the file to then replace the values in it
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
await fsCopyFile(sourceFilePath, destinationFilePath);
|
||||
|
||||
// Replace the variables in the template file
|
||||
const options: ReplaceInFileConfig = {
|
||||
files: [
|
||||
destinationFilePath,
|
||||
],
|
||||
files: [destinationFilePath],
|
||||
from: [],
|
||||
to: [],
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// eslint-disable-next-line import/no-cycle
|
||||
export * from './Build';
|
||||
export * from './Create';
|
||||
export * from './Interfaces';
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { ICredentialType, NodePropertyTypes } from 'n8n-workflow';
|
||||
|
||||
export class ClassNameReplace implements ICredentialType {
|
||||
name = 'N8nNameReplace';
|
||||
|
||||
displayName = 'DisplayNameReplace';
|
||||
|
||||
properties = [
|
||||
// The credentials to get from user and save encrypted.
|
||||
// Properties can be defined exactly in the same way
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { IExecuteFunctions } from 'n8n-core';
|
||||
import {
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
export class ClassNameReplace implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -29,13 +24,11 @@ export class ClassNameReplace implements INodeType {
|
|||
default: '',
|
||||
placeholder: 'Placeholder value',
|
||||
description: 'The description text',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
let item: INodeExecutionData;
|
||||
|
@ -48,10 +41,9 @@ export class ClassNameReplace implements INodeType {
|
|||
myString = this.getNodeParameter('myString', itemIndex, '') as string;
|
||||
item = items[itemIndex];
|
||||
|
||||
item.json['myString'] = myString;
|
||||
item.json.myString = myString;
|
||||
}
|
||||
|
||||
return this.prepareOutputData(items);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { ITriggerFunctions } from 'n8n-core';
|
||||
import {
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ITriggerResponse,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { INodeType, INodeTypeDescription, ITriggerResponse } from 'n8n-workflow';
|
||||
|
||||
export class ClassNameReplace implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -32,12 +27,10 @@ export class ClassNameReplace implements INodeType {
|
|||
default: 1,
|
||||
description: 'Every how many minutes the workflow should be triggered.',
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||
|
||||
const interval = this.getNodeParameter('interval', 1) as number;
|
||||
|
||||
if (interval <= 0) {
|
||||
|
@ -48,7 +41,7 @@ export class ClassNameReplace implements INodeType {
|
|||
// Every time the emit function gets called a new workflow
|
||||
// executions gets started with the provided entries.
|
||||
const entry = {
|
||||
'exampleKey': 'exampleData'
|
||||
exampleKey: 'exampleData',
|
||||
};
|
||||
this.emit([this.helpers.returnJsonArray([entry])]);
|
||||
};
|
||||
|
@ -78,6 +71,5 @@ export class ClassNameReplace implements INodeType {
|
|||
closeFunction,
|
||||
manualTriggerFunction,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import {
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
INodeType,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
import { IWebhookFunctions } from 'n8n-core';
|
||||
|
||||
import { IDataObject, INodeTypeDescription, INodeType, IWebhookResponseData } from 'n8n-workflow';
|
||||
|
||||
export class ClassNameReplace implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -47,25 +39,18 @@ export class ClassNameReplace implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
|
||||
// The data to return and so start the workflow with
|
||||
const returnData: IDataObject[] = [];
|
||||
returnData.push(
|
||||
{
|
||||
returnData.push({
|
||||
headers: this.getHeaderData(),
|
||||
params: this.getParamsData(),
|
||||
query: this.getQueryData(),
|
||||
body: this.getBodyData(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(returnData)
|
||||
],
|
||||
workflowData: [this.helpers.returnJsonArray(returnData)],
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ export class Discord implements INodeType {
|
|||
// Waiting rating limit
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
// @ts-ignore
|
||||
resolve();
|
||||
}, get(error, 'response.body.retry_after', 150));
|
||||
});
|
||||
|
|
|
@ -552,6 +552,7 @@ export class Slack implements INodeType {
|
|||
attachment.fields = attachment.fields.item;
|
||||
} else {
|
||||
// If it does not have any items set remove it
|
||||
// @ts-ignore
|
||||
delete attachment.fields;
|
||||
}
|
||||
}
|
||||
|
@ -786,6 +787,7 @@ export class Slack implements INodeType {
|
|||
attachment.fields = attachment.fields.item;
|
||||
} else {
|
||||
// If it does not have any items set remove it
|
||||
// @ts-ignore
|
||||
delete attachment.fields;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ export function connect(conn: snowflake.Connection) {
|
|||
return new Promise((resolve, reject) => {
|
||||
conn.connect((err, conn) => {
|
||||
if (!err) {
|
||||
// @ts-ignore
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
|
@ -21,6 +22,7 @@ export function destroy(conn: snowflake.Connection) {
|
|||
return new Promise((resolve, reject) => {
|
||||
conn.destroy((err, conn) => {
|
||||
if (!err) {
|
||||
// @ts-ignore
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
|
|
|
@ -167,6 +167,7 @@ export async function uploadAttachments(this: IExecuteFunctions, binaryPropertie
|
|||
const { check_after_secs } = (response.processing_info as IDataObject);
|
||||
await new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
resolve();
|
||||
}, (check_after_secs as number) * 1000);
|
||||
});
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
"scripts": {
|
||||
"dev": "npm run watch",
|
||||
"build": "tsc && gulp",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/nodes-base/**/**.ts --write",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"nodelinter": "nodelinter",
|
||||
"watch": "tsc --watch",
|
||||
"test": "jest"
|
||||
|
@ -644,7 +645,7 @@
|
|||
"nodelinter": "^0.1.9",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lossless-json": "^1.0.0",
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
"scripts": {
|
||||
"dev": "npm run watch",
|
||||
"build": "tsc",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/workflow/**/**.ts --write",
|
||||
"lint": "cd ../.. && node_modules/eslint/bin/eslint.js packages/workflow",
|
||||
"lintfix": "cd ../.. && node_modules/eslint/bin/eslint.js packages/workflow --fix",
|
||||
"watch": "tsc --watch",
|
||||
"test": "jest"
|
||||
},
|
||||
|
@ -31,10 +32,18 @@
|
|||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/node": "^14.14.40",
|
||||
"@types/xml2js": "^0.4.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||
"@typescript-eslint/parser": "^4.29.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb-typescript": "^12.3.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"jest": "^26.4.2",
|
||||
"prettier": "^2.3.2",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash.get": "^4.4.2",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
// @ts-ignore
|
||||
import * as tmpl from 'riot-tmpl';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
|
@ -9,28 +11,26 @@ import {
|
|||
Workflow,
|
||||
WorkflowDataProxy,
|
||||
WorkflowExecuteMode,
|
||||
} from './';
|
||||
} from '.';
|
||||
|
||||
// @ts-ignore
|
||||
import * as tmpl from 'riot-tmpl';
|
||||
|
||||
// Set it to use double curly brackets instead of single ones
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
tmpl.brackets.set('{{ }}');
|
||||
|
||||
// Make sure that it does not always print an error when it could not resolve
|
||||
// a variable
|
||||
tmpl.tmpl.errorHandler = () => { };
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
tmpl.tmpl.errorHandler = () => {};
|
||||
|
||||
export class Expression {
|
||||
|
||||
workflow: Workflow;
|
||||
|
||||
constructor(workflow: Workflow) {
|
||||
this.workflow = workflow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an object to a string in a way to make it clear that
|
||||
* the value comes from an object
|
||||
|
@ -44,8 +44,6 @@ export class Expression {
|
|||
return `[${typeName}: ${JSON.stringify(value)}]`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resolves the paramter value. If it is an expression it will execute it and
|
||||
* return the result. For everything simply the supplied value will be returned.
|
||||
|
@ -60,7 +58,19 @@ export class Expression {
|
|||
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
resolveSimpleParameterValue(parameterValue: NodeParameterValue, siblingParameters: INodeParameters, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], mode: WorkflowExecuteMode, additionalKeys: IWorkflowDataProxyAdditionalKeys, returnObjectAsString = false, selfData = {}): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||
resolveSimpleParameterValue(
|
||||
parameterValue: NodeParameterValue,
|
||||
siblingParameters: INodeParameters,
|
||||
runExecutionData: IRunExecutionData | null,
|
||||
runIndex: number,
|
||||
itemIndex: number,
|
||||
activeNodeName: string,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
mode: WorkflowExecuteMode,
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||
returnObjectAsString = false,
|
||||
selfData = {},
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||
// Check if it is an expression
|
||||
if (typeof parameterValue !== 'string' || parameterValue.charAt(0) !== '=') {
|
||||
// Is no expression so return value
|
||||
|
@ -70,30 +80,44 @@ export class Expression {
|
|||
// Is an expression
|
||||
|
||||
// Remove the equal sign
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
parameterValue = parameterValue.substr(1);
|
||||
|
||||
// Generate a data proxy which allows to query workflow data
|
||||
const dataProxy = new WorkflowDataProxy(this.workflow, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, siblingParameters, mode, additionalKeys, -1, selfData);
|
||||
const dataProxy = new WorkflowDataProxy(
|
||||
this.workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
activeNodeName,
|
||||
connectionInputData,
|
||||
siblingParameters,
|
||||
mode,
|
||||
additionalKeys,
|
||||
-1,
|
||||
selfData,
|
||||
);
|
||||
const data = dataProxy.getDataProxy();
|
||||
|
||||
// Execute the expression
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
const returnValue = tmpl.tmpl(parameterValue, data);
|
||||
if (typeof returnValue === 'function') {
|
||||
throw new Error('Expression resolved to a function. Please add "()"');
|
||||
} else if (returnValue !== null && typeof returnValue === 'object') {
|
||||
if (returnObjectAsString === true) {
|
||||
if (returnObjectAsString) {
|
||||
return this.convertObjectValueToString(returnValue);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return returnValue;
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||
throw new Error(`Expression is not valid: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resolves value of parameter. But does not work for workflow-data.
|
||||
*
|
||||
|
@ -103,7 +127,13 @@ export class Expression {
|
|||
* @returns {(string | undefined)}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getSimpleParameterValue(node: INode, parameterValue: string | boolean | undefined, mode: WorkflowExecuteMode, additionalKeys: IWorkflowDataProxyAdditionalKeys, defaultValue?: boolean | number | string): boolean | number | string | undefined {
|
||||
getSimpleParameterValue(
|
||||
node: INode,
|
||||
parameterValue: string | boolean | undefined,
|
||||
mode: WorkflowExecuteMode,
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||
defaultValue?: boolean | number | string,
|
||||
): boolean | number | string | undefined {
|
||||
if (parameterValue === undefined) {
|
||||
// Value is not set so return the default
|
||||
return defaultValue;
|
||||
|
@ -119,11 +149,18 @@ export class Expression {
|
|||
},
|
||||
};
|
||||
|
||||
return this.getParameterValue(parameterValue, runData, runIndex, itemIndex, node.name, connectionInputData, mode, additionalKeys) as boolean | number | string | undefined;
|
||||
return this.getParameterValue(
|
||||
parameterValue,
|
||||
runData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
) as boolean | number | string | undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resolves value of complex parameter. But does not work for workflow-data.
|
||||
*
|
||||
|
@ -133,7 +170,19 @@ export class Expression {
|
|||
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | undefined)}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getComplexParameterValue(node: INode, parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], mode: WorkflowExecuteMode, additionalKeys: IWorkflowDataProxyAdditionalKeys, defaultValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | undefined = undefined, selfData = {}): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | undefined {
|
||||
getComplexParameterValue(
|
||||
node: INode,
|
||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
mode: WorkflowExecuteMode,
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||
defaultValue:
|
||||
| NodeParameterValue
|
||||
| INodeParameters
|
||||
| NodeParameterValue[]
|
||||
| INodeParameters[]
|
||||
| undefined = undefined,
|
||||
selfData = {},
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | undefined {
|
||||
if (parameterValue === undefined) {
|
||||
// Value is not set so return the default
|
||||
return defaultValue;
|
||||
|
@ -150,14 +199,34 @@ export class Expression {
|
|||
};
|
||||
|
||||
// Resolve the "outer" main values
|
||||
const returnData = this.getParameterValue(parameterValue, runData, runIndex, itemIndex, node.name, connectionInputData, mode, additionalKeys, false, selfData);
|
||||
const returnData = this.getParameterValue(
|
||||
parameterValue,
|
||||
runData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
false,
|
||||
selfData,
|
||||
);
|
||||
|
||||
// Resolve the "inner" values
|
||||
return this.getParameterValue(returnData, runData, runIndex, itemIndex, node.name, connectionInputData, mode, additionalKeys, false, selfData);
|
||||
return this.getParameterValue(
|
||||
returnData,
|
||||
runData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
false,
|
||||
selfData,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the resolved node parameter value. If it is an expression it will execute it and
|
||||
* return the result. If the value to resolve is an array or object it will do the same
|
||||
|
@ -173,24 +242,74 @@ export class Expression {
|
|||
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getParameterValue(parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], mode: WorkflowExecuteMode, additionalKeys: IWorkflowDataProxyAdditionalKeys, returnObjectAsString = false, selfData = {}): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||
getParameterValue(
|
||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
runExecutionData: IRunExecutionData | null,
|
||||
runIndex: number,
|
||||
itemIndex: number,
|
||||
activeNodeName: string,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
mode: WorkflowExecuteMode,
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||
returnObjectAsString = false,
|
||||
selfData = {},
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||
// Helper function which returns true when the parameter is a complex one or array
|
||||
const isComplexParameter = (value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[]) => {
|
||||
const isComplexParameter = (
|
||||
value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
) => {
|
||||
return typeof value === 'object';
|
||||
};
|
||||
|
||||
// Helper function which resolves a parameter value depending on if it is simply or not
|
||||
const resolveParameterValue = (value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], siblingParameters: INodeParameters) => {
|
||||
const resolveParameterValue = (
|
||||
value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
siblingParameters: INodeParameters,
|
||||
) => {
|
||||
if (isComplexParameter(value)) {
|
||||
return this.getParameterValue(value, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, additionalKeys, returnObjectAsString, selfData);
|
||||
} else {
|
||||
return this.resolveSimpleParameterValue(value as NodeParameterValue, siblingParameters, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, additionalKeys, returnObjectAsString, selfData);
|
||||
return this.getParameterValue(
|
||||
value,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
activeNodeName,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
returnObjectAsString,
|
||||
selfData,
|
||||
);
|
||||
}
|
||||
return this.resolveSimpleParameterValue(
|
||||
value as NodeParameterValue,
|
||||
siblingParameters,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
activeNodeName,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
returnObjectAsString,
|
||||
selfData,
|
||||
);
|
||||
};
|
||||
|
||||
// Check if it value is a simple one that we can get it resolved directly
|
||||
if (!isComplexParameter(parameterValue)) {
|
||||
return this.resolveSimpleParameterValue(parameterValue as NodeParameterValue, {}, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, additionalKeys, returnObjectAsString, selfData);
|
||||
return this.resolveSimpleParameterValue(
|
||||
parameterValue as NodeParameterValue,
|
||||
{},
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
activeNodeName,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
returnObjectAsString,
|
||||
selfData,
|
||||
);
|
||||
}
|
||||
|
||||
// The parameter value is complex so resolve depending on type
|
||||
|
@ -198,28 +317,33 @@ export class Expression {
|
|||
if (Array.isArray(parameterValue)) {
|
||||
// Data is an array
|
||||
const returnData = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const item of parameterValue) {
|
||||
returnData.push(resolveParameterValue(item, {}));
|
||||
}
|
||||
|
||||
if (returnObjectAsString === true && typeof returnData === 'object') {
|
||||
if (returnObjectAsString && typeof returnData === 'object') {
|
||||
return this.convertObjectValueToString(returnData);
|
||||
}
|
||||
|
||||
return returnData as NodeParameterValue[] | INodeParameters[];
|
||||
} else if (parameterValue === null || parameterValue === undefined) {
|
||||
}
|
||||
if (parameterValue === null || parameterValue === undefined) {
|
||||
return parameterValue;
|
||||
} else {
|
||||
}
|
||||
// Data is an object
|
||||
const returnData: INodeParameters = {};
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of Object.keys(parameterValue)) {
|
||||
returnData[key] = resolveParameterValue((parameterValue as INodeParameters)[key], parameterValue as INodeParameters);
|
||||
returnData[key] = resolveParameterValue(
|
||||
(parameterValue as INodeParameters)[key],
|
||||
parameterValue as INodeParameters,
|
||||
);
|
||||
}
|
||||
|
||||
if (returnObjectAsString === true && typeof returnData === 'object') {
|
||||
if (returnObjectAsString && typeof returnData === 'object') {
|
||||
return this.convertObjectValueToString(returnData);
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable import/no-cycle */
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
import * as express from 'express';
|
||||
import { Workflow } from './Workflow';
|
||||
import { WorkflowHooks } from './WorkflowHooks';
|
||||
import { WorkflowOperationError } from './WorkflowErrors';
|
||||
import { NodeApiError, NodeOperationError } from './NodeErrors';
|
||||
import * as express from 'express';
|
||||
|
||||
export type IAllExecuteFunctions = IExecuteFunctions | IExecuteSingleFunctions | IHookFunctions | ILoadOptionsFunctions | IPollFunctions | ITriggerFunctions | IWebhookFunctions;
|
||||
export type IAllExecuteFunctions =
|
||||
| IExecuteFunctions
|
||||
| IExecuteSingleFunctions
|
||||
| IHookFunctions
|
||||
| ILoadOptionsFunctions
|
||||
| IPollFunctions
|
||||
| ITriggerFunctions
|
||||
| IWebhookFunctions;
|
||||
|
||||
export interface IBinaryData {
|
||||
[key: string]: string | undefined;
|
||||
|
@ -43,8 +55,11 @@ export interface IGetCredentials {
|
|||
|
||||
export abstract class ICredentials {
|
||||
name: string;
|
||||
|
||||
type: string;
|
||||
|
||||
data: string | undefined;
|
||||
|
||||
nodesAccess: ICredentialNodeAccess[];
|
||||
|
||||
constructor(name: string, type: string, nodesAccess: ICredentialNodeAccess[], data?: string) {
|
||||
|
@ -55,10 +70,15 @@ export abstract class ICredentials {
|
|||
}
|
||||
|
||||
abstract getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject;
|
||||
|
||||
abstract getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation;
|
||||
|
||||
abstract getDataToSave(): ICredentialsEncrypted;
|
||||
|
||||
abstract hasNodeAccess(nodeType: string): boolean;
|
||||
|
||||
abstract setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void;
|
||||
|
||||
abstract setDataKey(key: string, data: CredentialInformation, encryptionKey: string): void;
|
||||
}
|
||||
|
||||
|
@ -101,8 +121,20 @@ export abstract class ICredentialsHelper {
|
|||
}
|
||||
|
||||
abstract getCredentials(name: string, type: string): Promise<ICredentials>;
|
||||
abstract getDecrypted(name: string, type: string, mode: WorkflowExecuteMode, raw?: boolean, expressionResolveValues?: ICredentialsExpressionResolveValues): Promise<ICredentialDataDecryptedObject>;
|
||||
abstract updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void>;
|
||||
|
||||
abstract getDecrypted(
|
||||
name: string,
|
||||
type: string,
|
||||
mode: WorkflowExecuteMode,
|
||||
raw?: boolean,
|
||||
expressionResolveValues?: ICredentialsExpressionResolveValues,
|
||||
): Promise<ICredentialDataDecryptedObject>;
|
||||
|
||||
abstract updateCredentials(
|
||||
name: string,
|
||||
type: string,
|
||||
data: ICredentialDataDecryptedObject,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ICredentialType {
|
||||
|
@ -116,7 +148,7 @@ export interface ICredentialType {
|
|||
|
||||
export interface ICredentialTypes {
|
||||
credentialTypes?: {
|
||||
[key: string]: ICredentialType
|
||||
[key: string]: ICredentialType;
|
||||
};
|
||||
init(credentialTypes?: { [key: string]: ICredentialType }): Promise<void>;
|
||||
getAll(): ICredentialType[];
|
||||
|
@ -133,7 +165,6 @@ export interface ICredentialData {
|
|||
// The encrypted credentials which the nodes can access
|
||||
export type CredentialInformation = string | number | boolean | IDataObject;
|
||||
|
||||
|
||||
// The encrypted credentials which the nodes can access
|
||||
export interface ICredentialDataDecryptedObject {
|
||||
[key: string]: CredentialInformation;
|
||||
|
@ -159,92 +190,150 @@ export interface IDataObject {
|
|||
[key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[];
|
||||
}
|
||||
|
||||
|
||||
export interface IGetExecutePollFunctions {
|
||||
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IPollFunctions;
|
||||
(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): IPollFunctions;
|
||||
}
|
||||
|
||||
export interface IGetExecuteTriggerFunctions {
|
||||
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): ITriggerFunctions;
|
||||
(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): ITriggerFunctions;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetExecuteFunctions {
|
||||
(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteFunctions;
|
||||
(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
inputData: ITaskDataConnections,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
): IExecuteFunctions;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetExecuteSingleFunctions {
|
||||
(workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, connectionInputData: INodeExecutionData[], inputData: ITaskDataConnections, node: INode, itemIndex: number, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IExecuteSingleFunctions;
|
||||
(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
inputData: ITaskDataConnections,
|
||||
node: INode,
|
||||
itemIndex: number,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
): IExecuteSingleFunctions;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetExecuteHookFunctions {
|
||||
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, isTest?: boolean, webhookData?: IWebhookData): IHookFunctions;
|
||||
(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
isTest?: boolean,
|
||||
webhookData?: IWebhookData,
|
||||
): IHookFunctions;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetExecuteWebhookFunctions {
|
||||
(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, webhookData: IWebhookData): IWebhookFunctions;
|
||||
(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
webhookData: IWebhookData,
|
||||
): IWebhookFunctions;
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteData {
|
||||
data: ITaskDataConnections;
|
||||
node: INode;
|
||||
}
|
||||
|
||||
export type IContextObject = {
|
||||
[key: string]: any; // tslint:disable-line:no-any
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
|
||||
export interface IExecuteContextData {
|
||||
// Keys are: "flow" | "node:<NODE_NAME>"
|
||||
[key: string]: IContextObject;
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteFunctions {
|
||||
continueOnFail(): boolean;
|
||||
evaluateExpression(expression: string, itemIndex: number): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
||||
executeWorkflow(workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[]): Promise<any>; // tslint:disable-line:no-any
|
||||
evaluateExpression(
|
||||
expression: string,
|
||||
itemIndex: number,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
||||
executeWorkflow(
|
||||
workflowInfo: IExecuteWorkflowInfo,
|
||||
inputData?: INodeExecutionData[],
|
||||
): Promise<any>;
|
||||
getContext(type: string): IContextObject;
|
||||
getCredentials(type: string, itemIndex?: number): Promise<ICredentialDataDecryptedObject | undefined>;
|
||||
getCredentials(
|
||||
type: string,
|
||||
itemIndex?: number,
|
||||
): Promise<ICredentialDataDecryptedObject | undefined>;
|
||||
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[];
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNode(): INode;
|
||||
getNodeParameter(parameterName: string, itemIndex: number, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
itemIndex: number,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||
getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
||||
prepareOutputData(
|
||||
outputData: INodeExecutionData[],
|
||||
outputIndex?: number,
|
||||
): Promise<INodeExecutionData[][]>;
|
||||
putExecutionToWait(waitTill: Date): Promise<void>;
|
||||
sendMessageToUI(message: any): void; // tslint:disable-line:no-any
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IExecuteSingleFunctions {
|
||||
continueOnFail(): boolean;
|
||||
evaluateExpression(expression: string, itemIndex: number | undefined): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
||||
evaluateExpression(
|
||||
expression: string,
|
||||
itemIndex: number | undefined,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
||||
getContext(type: string): IContextObject;
|
||||
getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined>;
|
||||
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData;
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNode(): INode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -256,13 +345,24 @@ export interface IExecuteWorkflowInfo {
|
|||
export interface ILoadOptionsFunctions {
|
||||
getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined>;
|
||||
getNode(): INode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getCurrentNodeParameter(parameterName: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined;
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||
getCurrentNodeParameter(
|
||||
parameterName: string,
|
||||
):
|
||||
| NodeParameterValue
|
||||
| INodeParameters
|
||||
| NodeParameterValue[]
|
||||
| INodeParameters[]
|
||||
| object
|
||||
| undefined;
|
||||
getCurrentNodeParameters(): INodeParameters | undefined;
|
||||
getTimezone(): string;
|
||||
getRestApiUrl(): string;
|
||||
helpers: {
|
||||
[key: string]: ((...args: any[]) => any) | undefined; //tslint:disable-line:no-any
|
||||
[key: string]: ((...args: any[]) => any) | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -272,14 +372,17 @@ export interface IHookFunctions {
|
|||
getActivationMode(): WorkflowActivateMode;
|
||||
getNode(): INode;
|
||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||
getTimezone(): string;
|
||||
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
||||
getWebhookName(): string;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -289,13 +392,16 @@ export interface IPollFunctions {
|
|||
getMode(): WorkflowExecuteMode;
|
||||
getActivationMode(): WorkflowActivateMode;
|
||||
getNode(): INode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -305,13 +411,16 @@ export interface ITriggerFunctions {
|
|||
getMode(): WorkflowExecuteMode;
|
||||
getActivationMode(): WorkflowActivateMode;
|
||||
getNode(): INode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -321,7 +430,10 @@ export interface IWebhookFunctions {
|
|||
getHeaderData(): object;
|
||||
getMode(): WorkflowExecuteMode;
|
||||
getNode(): INode;
|
||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
||||
getParamsData(): object;
|
||||
getQueryData(): object;
|
||||
|
@ -331,9 +443,12 @@ export interface IWebhookFunctions {
|
|||
getWebhookName(): string;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
||||
prepareOutputData(
|
||||
outputData: INodeExecutionData[],
|
||||
outputIndex?: number,
|
||||
): Promise<INodeExecutionData[][]>;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -360,18 +475,15 @@ export interface INode {
|
|||
webhookId?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface INodes {
|
||||
[key: string]: INode;
|
||||
}
|
||||
|
||||
|
||||
export interface IObservableObject {
|
||||
[key: string]: any; // tslint:disable-line:no-any
|
||||
[key: string]: any;
|
||||
__dataChanged: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface IBinaryKeyData {
|
||||
[key: string]: IBinaryData;
|
||||
}
|
||||
|
@ -385,7 +497,6 @@ export interface INodeExecutionData {
|
|||
binary?: IBinaryKeyData;
|
||||
}
|
||||
|
||||
|
||||
export interface INodeExecuteFunctions {
|
||||
getExecutePollFunctions: IGetExecutePollFunctions;
|
||||
getExecuteTriggerFunctions: IGetExecuteTriggerFunctions;
|
||||
|
@ -395,7 +506,6 @@ export interface INodeExecuteFunctions {
|
|||
getExecuteWebhookFunctions: IGetExecuteWebhookFunctions;
|
||||
}
|
||||
|
||||
|
||||
// The values a node property can have
|
||||
export type NodeParameterValue = string | number | boolean | undefined | null;
|
||||
|
||||
|
@ -404,7 +514,19 @@ export interface INodeParameters {
|
|||
[key: string]: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
||||
}
|
||||
|
||||
export type NodePropertyTypes = 'boolean' | 'collection' | 'color' | 'dateTime' | 'fixedCollection' | 'hidden' | 'json' | 'notice' | 'multiOptions' | 'number' | 'options' | 'string';
|
||||
export type NodePropertyTypes =
|
||||
| 'boolean'
|
||||
| 'collection'
|
||||
| 'color'
|
||||
| 'dateTime'
|
||||
| 'fixedCollection'
|
||||
| 'hidden'
|
||||
| 'json'
|
||||
| 'notice'
|
||||
| 'multiOptions'
|
||||
| 'number'
|
||||
| 'options'
|
||||
| 'string';
|
||||
|
||||
export type EditorTypes = 'code';
|
||||
|
||||
|
@ -435,7 +557,6 @@ export interface IDisplayOptions {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export interface INodeProperties {
|
||||
displayName: string;
|
||||
name: string;
|
||||
|
@ -492,7 +613,7 @@ export interface INodeType {
|
|||
methods?: {
|
||||
loadOptions?: {
|
||||
[key: string]: (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>;
|
||||
}
|
||||
};
|
||||
};
|
||||
webhookMethods?: {
|
||||
[key: string]: IWebhookSetupMethods;
|
||||
|
@ -501,7 +622,6 @@ export interface INodeType {
|
|||
|
||||
export type WebhookSetupMethodNames = 'checkExists' | 'create' | 'delete';
|
||||
|
||||
|
||||
export interface IWebhookSetupMethods {
|
||||
[key: string]: ((this: IHookFunctions) => Promise<boolean>) | undefined;
|
||||
checkExists?: (this: IHookFunctions) => Promise<boolean>;
|
||||
|
@ -509,7 +629,6 @@ export interface IWebhookSetupMethods {
|
|||
delete?: (this: IHookFunctions) => Promise<boolean>;
|
||||
}
|
||||
|
||||
|
||||
export interface INodeCredentialDescription {
|
||||
name: string;
|
||||
required?: boolean;
|
||||
|
@ -596,17 +715,17 @@ export interface IWebhookDescription {
|
|||
}
|
||||
|
||||
export interface IWorkflowDataProxyData {
|
||||
$binary: any; // tslint:disable-line:no-any
|
||||
$data: any; // tslint:disable-line:no-any
|
||||
$env: any; // tslint:disable-line:no-any
|
||||
$evaluateExpression: any; // tslint:disable-line:no-any
|
||||
$item: any; // tslint:disable-line:no-any
|
||||
$items: any; // tslint:disable-line:no-any
|
||||
$json: any; // tslint:disable-line:no-any
|
||||
$node: any; // tslint:disable-line:no-any
|
||||
$parameter: any; // tslint:disable-line:no-any
|
||||
$position: any; // tslint:disable-line:no-any
|
||||
$workflow: any; // tslint:disable-line:no-any
|
||||
$binary: any;
|
||||
$data: any;
|
||||
$env: any;
|
||||
$evaluateExpression: any;
|
||||
$item: any;
|
||||
$items: any;
|
||||
$json: any;
|
||||
$node: any;
|
||||
$parameter: any;
|
||||
$position: any;
|
||||
$workflow: any;
|
||||
}
|
||||
|
||||
export interface IWorkflowDataProxyAdditionalKeys {
|
||||
|
@ -623,7 +742,7 @@ export type WebhookHttpMethod = 'GET' | 'POST' | 'HEAD' | 'OPTIONS';
|
|||
|
||||
export interface IWebhookResponseData {
|
||||
workflowData?: INodeExecutionData[][];
|
||||
webhookResponse?: any; // tslint:disable-line:no-any
|
||||
webhookResponse?: any;
|
||||
noWebhookResponse?: boolean;
|
||||
}
|
||||
|
||||
|
@ -637,7 +756,6 @@ export interface INodeTypes {
|
|||
getByName(nodeType: string): INodeType | undefined;
|
||||
}
|
||||
|
||||
|
||||
export interface INodeTypeData {
|
||||
[key: string]: {
|
||||
type: INodeType;
|
||||
|
@ -654,7 +772,6 @@ export interface IRun {
|
|||
stoppedAt?: Date;
|
||||
}
|
||||
|
||||
|
||||
// Contains all the data which is needed to execute a workflow and so also to
|
||||
// start restart it again after it did fail.
|
||||
// The RunData, ExecuteData and WaitForExecution contain often the same data.
|
||||
|
@ -676,13 +793,11 @@ export interface IRunExecutionData {
|
|||
waitTill?: Date;
|
||||
}
|
||||
|
||||
|
||||
export interface IRunData {
|
||||
// node-name: result-data
|
||||
[key: string]: ITaskData[];
|
||||
}
|
||||
|
||||
|
||||
// The data that gets returned when a node runs
|
||||
export interface ITaskData {
|
||||
startTime: number;
|
||||
|
@ -691,7 +806,6 @@ export interface ITaskData {
|
|||
error?: ExecutionError;
|
||||
}
|
||||
|
||||
|
||||
// The data for al the different kind of connectons (like main) and all the indexes
|
||||
export interface ITaskDataConnections {
|
||||
// Key for each input type and because there can be multiple inputs of the same type it is an array
|
||||
|
@ -700,20 +814,17 @@ export interface ITaskDataConnections {
|
|||
[key: string]: Array<INodeExecutionData[] | null>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Keeps data while workflow gets executed and allows when provided to restart execution
|
||||
export interface IWaitingForExecution {
|
||||
// Node name
|
||||
[key: string]: {
|
||||
// Run index
|
||||
[key: number]: ITaskDataConnections
|
||||
[key: number]: ITaskDataConnections;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IWorkflowBase {
|
||||
id?: number | string | any; // tslint:disable-line:no-any
|
||||
id?: number | string | any;
|
||||
name: string;
|
||||
active: boolean;
|
||||
createdAt: Date;
|
||||
|
@ -732,26 +843,34 @@ export interface IWorkflowCredentials {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IWorkflowExecuteHooks {
|
||||
[key: string]: Array<((...args: any[]) => Promise<void>)> | undefined; // tslint:disable-line:no-any
|
||||
nodeExecuteAfter?: Array<((nodeName: string, data: ITaskData, executionData: IRunExecutionData) => Promise<void>)>;
|
||||
nodeExecuteBefore?: Array<((nodeName: string) => Promise<void>)>;
|
||||
workflowExecuteAfter?: Array<((data: IRun, newStaticData: IDataObject) => Promise<void>)>;
|
||||
workflowExecuteBefore?: Array<((workflow: Workflow, data: IRunExecutionData) => Promise<void>)>;
|
||||
[key: string]: Array<(...args: any[]) => Promise<void>> | undefined;
|
||||
nodeExecuteAfter?: Array<
|
||||
(nodeName: string, data: ITaskData, executionData: IRunExecutionData) => Promise<void>
|
||||
>;
|
||||
nodeExecuteBefore?: Array<(nodeName: string) => Promise<void>>;
|
||||
workflowExecuteAfter?: Array<(data: IRun, newStaticData: IDataObject) => Promise<void>>;
|
||||
workflowExecuteBefore?: Array<(workflow: Workflow, data: IRunExecutionData) => Promise<void>>;
|
||||
}
|
||||
|
||||
export interface IWorkflowExecuteAdditionalData {
|
||||
credentialsHelper: ICredentialsHelper;
|
||||
encryptionKey: string;
|
||||
executeWorkflow: (workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[], parentExecutionId?: string, loadedWorkflowData?: IWorkflowBase, loadedRunData?: any) => Promise<any>; // tslint:disable-line:no-any
|
||||
executeWorkflow: (
|
||||
workflowInfo: IExecuteWorkflowInfo,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
inputData?: INodeExecutionData[],
|
||||
parentExecutionId?: string,
|
||||
loadedWorkflowData?: IWorkflowBase,
|
||||
loadedRunData?: any,
|
||||
) => Promise<any>;
|
||||
// hooks?: IWorkflowExecuteHooks;
|
||||
executionId?: string;
|
||||
hooks?: WorkflowHooks;
|
||||
httpResponse?: express.Response;
|
||||
httpRequest?: express.Request;
|
||||
restApiUrl: string;
|
||||
sendMessageToUI?: (source: string, message: any) => void; // tslint:disable-line:no-any
|
||||
sendMessageToUI?: (source: string, message: any) => void;
|
||||
timezone: string;
|
||||
webhookBaseUrl: string;
|
||||
webhookWaitingBaseUrl: string;
|
||||
|
@ -760,7 +879,15 @@ export interface IWorkflowExecuteAdditionalData {
|
|||
executionTimeoutTimestamp?: number;
|
||||
}
|
||||
|
||||
export type WorkflowExecuteMode = 'cli' | 'error' | 'integrated' | 'internal' | 'manual' | 'retry' | 'trigger' | 'webhook';
|
||||
export type WorkflowExecuteMode =
|
||||
| 'cli'
|
||||
| 'error'
|
||||
| 'integrated'
|
||||
| 'internal'
|
||||
| 'manual'
|
||||
| 'retry'
|
||||
| 'trigger'
|
||||
| 'webhook';
|
||||
export type WorkflowActivateMode = 'init' | 'create' | 'update' | 'activate' | 'manual';
|
||||
|
||||
export interface IWorkflowHooksOptionalParameters {
|
||||
|
@ -790,7 +917,7 @@ export interface IStatusCodeMessages {
|
|||
|
||||
export type CodexData = {
|
||||
categories?: string[];
|
||||
subcategories?: {[category: string]: string[]};
|
||||
subcategories?: { [category: string]: string[] };
|
||||
alias?: string[];
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import {
|
||||
ILogger,
|
||||
LogTypes,
|
||||
} from './Interfaces';
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { ILogger, LogTypes } from './Interfaces';
|
||||
|
||||
let logger: ILogger | undefined;
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { INode, IStatusCodeMessages, JsonObject} from '.';
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
import { parseString } from 'xml2js';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { INode, IStatusCodeMessages, JsonObject } from '.';
|
||||
|
||||
/**
|
||||
* Top-level properties where an error message can be found in an API response.
|
||||
|
@ -33,7 +41,14 @@ const ERROR_MESSAGE_PROPERTIES = [
|
|||
/**
|
||||
* Top-level properties where an HTTP error code can be found in an API response.
|
||||
*/
|
||||
const ERROR_STATUS_PROPERTIES = ['statusCode', 'status', 'code', 'status_code', 'errorCode', 'error_code'];
|
||||
const ERROR_STATUS_PROPERTIES = [
|
||||
'statusCode',
|
||||
'status',
|
||||
'code',
|
||||
'status_code',
|
||||
'errorCode',
|
||||
'error_code',
|
||||
];
|
||||
|
||||
/**
|
||||
* Properties where a nested object can be found in an API response.
|
||||
|
@ -46,8 +61,11 @@ const ERROR_NESTING_PROPERTIES = ['error', 'err', 'response', 'body', 'data'];
|
|||
*/
|
||||
abstract class NodeError extends Error {
|
||||
description: string | null | undefined;
|
||||
|
||||
cause: Error | JsonObject;
|
||||
|
||||
node: INode;
|
||||
|
||||
timestamp: number;
|
||||
|
||||
constructor(node: INode, error: Error | JsonObject) {
|
||||
|
@ -95,13 +113,17 @@ abstract class NodeError extends Error {
|
|||
potentialKeys: string[],
|
||||
traversalKeys: string[] = [],
|
||||
): string | null {
|
||||
for(const key of potentialKeys) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of potentialKeys) {
|
||||
if (error[key]) {
|
||||
if (typeof error[key] === 'string') return error[key] as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (typeof error[key] === 'number') return error[key]!.toString();
|
||||
if (Array.isArray(error[key])) {
|
||||
// @ts-ignore
|
||||
const resolvedErrors: string[] = error[key].map((error) => {
|
||||
const resolvedErrors: string[] = error[key]
|
||||
// @ts-ignore
|
||||
.map((error) => {
|
||||
if (typeof error === 'string') return error;
|
||||
if (typeof error === 'number') return error.toString();
|
||||
if (this.isTraversableObject(error)) {
|
||||
|
@ -125,6 +147,7 @@ abstract class NodeError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of traversalKeys) {
|
||||
if (this.isTraversableObject(error[key])) {
|
||||
const property = this.findProperty(error[key] as JsonObject, potentialKeys, traversalKeys);
|
||||
|
@ -140,8 +163,11 @@ abstract class NodeError extends Error {
|
|||
/**
|
||||
* Check if a value is an object with at least one key, i.e. it can be traversed.
|
||||
*/
|
||||
protected isTraversableObject(value: any): value is JsonObject { // tslint:disable-line:no-any
|
||||
return value && typeof value === 'object' && !Array.isArray(value) && !!Object.keys(value).length;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected isTraversableObject(value: any): value is JsonObject {
|
||||
return (
|
||||
value && typeof value === 'object' && !Array.isArray(value) && !!Object.keys(value).length
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,7 +177,10 @@ abstract class NodeError extends Error {
|
|||
seen.add(obj);
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (this.isTraversableObject(value)) {
|
||||
seen.has(value) ? obj[key] = { circularReference: true } : this.removeCircularRefs(value, seen);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
seen.has(value)
|
||||
? (obj[key] = { circularReference: true })
|
||||
: this.removeCircularRefs(value, seen);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
|
@ -173,7 +202,6 @@ abstract class NodeError extends Error {
|
|||
* Class for instantiating an operational error, e.g. an invalid credentials error.
|
||||
*/
|
||||
export class NodeOperationError extends NodeError {
|
||||
|
||||
constructor(node: INode, error: Error | string) {
|
||||
if (typeof error === 'string') {
|
||||
error = new Error(error);
|
||||
|
@ -211,10 +239,16 @@ export class NodeApiError extends NodeError {
|
|||
constructor(
|
||||
node: INode,
|
||||
error: JsonObject,
|
||||
{ message, description, httpCode, parseXml }: { message?: string, description?: string, httpCode?: string, parseXml?: boolean } = {},
|
||||
{
|
||||
message,
|
||||
description,
|
||||
httpCode,
|
||||
parseXml,
|
||||
}: { message?: string; description?: string; httpCode?: string; parseXml?: boolean } = {},
|
||||
) {
|
||||
super(node, error);
|
||||
if (error.error) { // only for request library error
|
||||
if (error.error) {
|
||||
// only for request library error
|
||||
this.removeCircularRefs(error.error as JsonObject);
|
||||
}
|
||||
if (message) {
|
||||
|
@ -236,11 +270,17 @@ export class NodeApiError extends NodeError {
|
|||
}
|
||||
|
||||
private setDescriptionFromXml(xml: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
parseString(xml, { explicitArray: false }, (_, result) => {
|
||||
if (!result) return;
|
||||
|
||||
const topLevelKey = Object.keys(result)[0];
|
||||
this.description = this.findProperty(result[topLevelKey], ERROR_MESSAGE_PROPERTIES, ['Error'].concat(ERROR_NESTING_PROPERTIES));
|
||||
this.description = this.findProperty(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
result[topLevelKey],
|
||||
ERROR_MESSAGE_PROPERTIES,
|
||||
['Error'].concat(ERROR_NESTING_PROPERTIES),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable prefer-spread */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable import/no-cycle */
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { get, isEqual } from 'lodash';
|
||||
|
||||
import {
|
||||
IContextObject,
|
||||
INode,
|
||||
|
@ -17,13 +31,7 @@ import {
|
|||
WebhookHttpMethod,
|
||||
} from './Interfaces';
|
||||
|
||||
import {
|
||||
Workflow
|
||||
} from './Workflow';
|
||||
|
||||
import { get, isEqual } from 'lodash';
|
||||
|
||||
|
||||
import { Workflow } from './Workflow';
|
||||
|
||||
/**
|
||||
* Gets special parameters which should be added to nodeTypes depending
|
||||
|
@ -99,12 +107,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
},
|
||||
displayOptions: {
|
||||
hide: {
|
||||
mode: [
|
||||
'custom',
|
||||
'everyHour',
|
||||
'everyMinute',
|
||||
'everyX',
|
||||
],
|
||||
mode: ['custom', 'everyHour', 'everyMinute', 'everyX'],
|
||||
},
|
||||
},
|
||||
default: 14,
|
||||
|
@ -120,11 +123,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
},
|
||||
displayOptions: {
|
||||
hide: {
|
||||
mode: [
|
||||
'custom',
|
||||
'everyMinute',
|
||||
'everyX',
|
||||
],
|
||||
mode: ['custom', 'everyMinute', 'everyX'],
|
||||
},
|
||||
},
|
||||
default: 0,
|
||||
|
@ -136,9 +135,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: [
|
||||
'everyMonth',
|
||||
],
|
||||
mode: ['everyMonth'],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
|
@ -154,9 +151,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: [
|
||||
'everyWeek',
|
||||
],
|
||||
mode: ['everyWeek'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
|
@ -198,13 +193,12 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: [
|
||||
'custom',
|
||||
],
|
||||
mode: ['custom'],
|
||||
},
|
||||
},
|
||||
default: '* * * * * *',
|
||||
description: 'Use custom cron expression. Values and ranges as follows:<ul><li>Seconds: 0-59</li><li>Minutes: 0 - 59</li><li>Hours: 0 - 23</li><li>Day of Month: 1 - 31</li><li>Months: 0 - 11 (Jan - Dec)</li><li>Day of Week: 0 - 6 (Sun - Sat)</li></ul>',
|
||||
description:
|
||||
'Use custom cron expression. Values and ranges as follows:<ul><li>Seconds: 0-59</li><li>Minutes: 0 - 59</li><li>Hours: 0 - 23</li><li>Day of Month: 1 - 31</li><li>Months: 0 - 11 (Jan - Dec)</li><li>Day of Week: 0 - 6 (Sun - Sat)</li></ul>',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
|
@ -216,9 +210,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: [
|
||||
'everyX',
|
||||
],
|
||||
mode: ['everyX'],
|
||||
},
|
||||
},
|
||||
default: 2,
|
||||
|
@ -230,9 +222,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: [
|
||||
'everyX',
|
||||
],
|
||||
mode: ['everyX'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
|
@ -258,7 +248,6 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
return [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the parameter should be displayed or not
|
||||
*
|
||||
|
@ -269,7 +258,11 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
|||
* @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
|
||||
* @returns
|
||||
*/
|
||||
export function displayParameter(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, nodeValuesRoot?: INodeParameters) {
|
||||
export function displayParameter(
|
||||
nodeValues: INodeParameters,
|
||||
parameter: INodeProperties | INodeCredentialDescription,
|
||||
nodeValuesRoot?: INodeParameters,
|
||||
) {
|
||||
if (!parameter.displayOptions) {
|
||||
return true;
|
||||
}
|
||||
|
@ -277,7 +270,8 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
|||
nodeValuesRoot = nodeValuesRoot || nodeValues;
|
||||
|
||||
let value;
|
||||
const values: any[] = []; // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const values: any[] = [];
|
||||
if (parameter.displayOptions.show) {
|
||||
// All the defined rules have to match to display parameter
|
||||
for (const propertyName of Object.keys(parameter.displayOptions.show)) {
|
||||
|
@ -296,11 +290,14 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
|||
values.push.apply(values, value);
|
||||
}
|
||||
|
||||
if (values.some(v => (typeof v) === 'string' && (v as string).charAt(0) === '=')) {
|
||||
if (values.some((v) => typeof v === 'string' && v.charAt(0) === '=')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (values.length === 0 || !parameter.displayOptions.show[propertyName].some(v => values.includes(v))) {
|
||||
if (
|
||||
values.length === 0 ||
|
||||
!parameter.displayOptions.show[propertyName].some((v) => values.includes(v))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -324,7 +321,10 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
|||
values.push.apply(values, value);
|
||||
}
|
||||
|
||||
if (values.length !== 0 && parameter.displayOptions.hide[propertyName].some(v => values.includes(v))) {
|
||||
if (
|
||||
values.length !== 0 &&
|
||||
parameter.displayOptions.hide[propertyName].some((v) => values.includes(v))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -333,7 +333,6 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given parameter should be displayed or not considering the path
|
||||
* to the properties
|
||||
|
@ -345,28 +344,25 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
|||
* @param {string} path The path to the property
|
||||
* @returns
|
||||
*/
|
||||
export function displayParameterPath(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, path: string) {
|
||||
export function displayParameterPath(
|
||||
nodeValues: INodeParameters,
|
||||
parameter: INodeProperties | INodeCredentialDescription,
|
||||
path: string,
|
||||
) {
|
||||
let resolvedNodeValues = nodeValues;
|
||||
if (path !== '') {
|
||||
resolvedNodeValues = get(
|
||||
nodeValues,
|
||||
path,
|
||||
) as INodeParameters;
|
||||
resolvedNodeValues = get(nodeValues, path) as INodeParameters;
|
||||
}
|
||||
|
||||
// Get the root parameter data
|
||||
let nodeValuesRoot = nodeValues;
|
||||
if (path && path.split('.').indexOf('parameters') === 0) {
|
||||
nodeValuesRoot = get(
|
||||
nodeValues,
|
||||
'parameters',
|
||||
) as INodeParameters;
|
||||
nodeValuesRoot = get(nodeValues, 'parameters') as INodeParameters;
|
||||
}
|
||||
|
||||
return displayParameter(resolvedNodeValues, parameter, nodeValuesRoot);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the context data
|
||||
*
|
||||
|
@ -376,7 +372,11 @@ export function displayParameterPath(nodeValues: INodeParameters, parameter: INo
|
|||
* @param {INode} [node] If type "node" is set the node to return the context of has to be supplied
|
||||
* @returns {IContextObject}
|
||||
*/
|
||||
export function getContext(runExecutionData: IRunExecutionData, type: string, node?: INode): IContextObject {
|
||||
export function getContext(
|
||||
runExecutionData: IRunExecutionData,
|
||||
type: string,
|
||||
node?: INode,
|
||||
): IContextObject {
|
||||
if (runExecutionData.executionData === undefined) {
|
||||
// TODO: Should not happen leave it for test now
|
||||
throw new Error('The "executionData" is not initialized!');
|
||||
|
@ -395,13 +395,13 @@ export function getContext(runExecutionData: IRunExecutionData, type: string, no
|
|||
}
|
||||
|
||||
if (runExecutionData.executionData.contextData[key] === undefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
runExecutionData.executionData.contextData[key] = {};
|
||||
}
|
||||
|
||||
return runExecutionData.executionData.contextData[key];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns which parameters are dependent on which
|
||||
*
|
||||
|
@ -409,7 +409,9 @@ export function getContext(runExecutionData: IRunExecutionData, type: string, no
|
|||
* @param {INodeProperties[]} nodePropertiesArray
|
||||
* @returns {IParameterDependencies}
|
||||
*/
|
||||
export function getParamterDependencies(nodePropertiesArray: INodeProperties[]): IParameterDependencies {
|
||||
export function getParamterDependencies(
|
||||
nodePropertiesArray: INodeProperties[],
|
||||
): IParameterDependencies {
|
||||
const dependencies: IParameterDependencies = {};
|
||||
|
||||
let displayRule: string;
|
||||
|
@ -436,7 +438,6 @@ export function getParamterDependencies(nodePropertiesArray: INodeProperties[]):
|
|||
return dependencies;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns in which order the parameters should be resolved
|
||||
* to have the parameters available they depend on
|
||||
|
@ -446,7 +447,10 @@ export function getParamterDependencies(nodePropertiesArray: INodeProperties[]):
|
|||
* @param {IParameterDependencies} parameterDependencies
|
||||
* @returns {number[]}
|
||||
*/
|
||||
export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[], parameterDependencies: IParameterDependencies): number[] {
|
||||
export function getParamterResolveOrder(
|
||||
nodePropertiesArray: INodeProperties[],
|
||||
parameterDependencies: IParameterDependencies,
|
||||
): number[] {
|
||||
const executionOrder: number[] = [];
|
||||
const indexToResolve = Array.from({ length: nodePropertiesArray.length }, (v, k) => k);
|
||||
const resolvedParamters: string[] = [];
|
||||
|
@ -457,7 +461,7 @@ export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[],
|
|||
let lastIndexLength = indexToResolve.length;
|
||||
let lastIndexReduction = -1;
|
||||
|
||||
let iterations = 0 ;
|
||||
let iterations = 0;
|
||||
|
||||
while (indexToResolve.length !== 0) {
|
||||
iterations += 1;
|
||||
|
@ -495,7 +499,9 @@ export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[],
|
|||
}
|
||||
|
||||
if (iterations > lastIndexReduction + nodePropertiesArray.length) {
|
||||
throw new Error('Could not resolve parameter depenencies. Max iterations reached! Hint: If `displayOptions` are specified in any child parameter of a parent `collection` or `fixedCollection`, remove the `displayOptions` from the child parameter.');
|
||||
throw new Error(
|
||||
'Could not resolve parameter depenencies. Max iterations reached! Hint: If `displayOptions` are specified in any child parameter of a parent `collection` or `fixedCollection`, remove the `displayOptions` from the child parameter.',
|
||||
);
|
||||
}
|
||||
lastIndexLength = indexToResolve.length;
|
||||
}
|
||||
|
@ -503,7 +509,6 @@ export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[],
|
|||
return executionOrder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the node parameter values. Depending on the settings it either just returns the none
|
||||
* default values or it applies all the default values.
|
||||
|
@ -518,7 +523,17 @@ export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[],
|
|||
* @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
|
||||
* @returns {(INodeParameters | null)}
|
||||
*/
|
||||
export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeValues: INodeParameters, returnDefaults: boolean, returnNoneDisplayed: boolean, onlySimpleTypes = false, dataIsResolved = false, nodeValuesRoot?: INodeParameters, parentType?: string, parameterDependencies?: IParameterDependencies): INodeParameters | null {
|
||||
export function getNodeParameters(
|
||||
nodePropertiesArray: INodeProperties[],
|
||||
nodeValues: INodeParameters,
|
||||
returnDefaults: boolean,
|
||||
returnNoneDisplayed: boolean,
|
||||
onlySimpleTypes = false,
|
||||
dataIsResolved = false,
|
||||
nodeValuesRoot?: INodeParameters,
|
||||
parentType?: string,
|
||||
parameterDependencies?: IParameterDependencies,
|
||||
): INodeParameters | null {
|
||||
if (parameterDependencies === undefined) {
|
||||
parameterDependencies = getParamterDependencies(nodePropertiesArray);
|
||||
}
|
||||
|
@ -541,27 +556,43 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
const nodeParametersFull: INodeParameters = {};
|
||||
|
||||
let nodeValuesDisplayCheck = nodeParametersFull;
|
||||
if (dataIsResolved !== true && returnNoneDisplayed === false) {
|
||||
nodeValuesDisplayCheck = getNodeParameters(nodePropertiesArray, nodeValues, true, true, true, true, nodeValuesRoot, parentType, parameterDependencies) as INodeParameters;
|
||||
if (!dataIsResolved && !returnNoneDisplayed) {
|
||||
nodeValuesDisplayCheck = getNodeParameters(
|
||||
nodePropertiesArray,
|
||||
nodeValues,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
nodeValuesRoot,
|
||||
parentType,
|
||||
parameterDependencies,
|
||||
) as INodeParameters;
|
||||
}
|
||||
|
||||
nodeValuesRoot = nodeValuesRoot || nodeValuesDisplayCheck;
|
||||
|
||||
// Go through the parameters in order of their dependencies
|
||||
const parameterItterationOrderIndex = getParameterResolveOrder(nodePropertiesArray, parameterDependencies);
|
||||
const parameterItterationOrderIndex = getParamterResolveOrder(
|
||||
nodePropertiesArray,
|
||||
parameterDependencies,
|
||||
);
|
||||
|
||||
for (const parameterIndex of parameterItterationOrderIndex) {
|
||||
const nodeProperties = nodePropertiesArray[parameterIndex];
|
||||
if (nodeValues[nodeProperties.name] === undefined && (returnDefaults === false || parentType === 'collection')) {
|
||||
if (
|
||||
nodeValues[nodeProperties.name] === undefined &&
|
||||
(!returnDefaults || parentType === 'collection')
|
||||
) {
|
||||
// The value is not defined so go to the next
|
||||
continue;
|
||||
}
|
||||
|
||||
if (returnNoneDisplayed === false && !displayParameter(nodeValuesDisplayCheck, nodeProperties, nodeValuesRoot)) {
|
||||
if (returnNoneDisplayed === false) {
|
||||
continue;
|
||||
}
|
||||
if (returnDefaults === false) {
|
||||
if (
|
||||
!returnNoneDisplayed &&
|
||||
!displayParameter(nodeValuesDisplayCheck, nodeProperties, nodeValuesRoot)
|
||||
) {
|
||||
if (!returnNoneDisplayed || !returnDefaults) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -575,19 +606,27 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
}
|
||||
}
|
||||
|
||||
if (returnDefaults === true) {
|
||||
if (returnDefaults) {
|
||||
// Set also when it has the default value
|
||||
if (['boolean', 'number', 'options'].includes(nodeProperties.type)) {
|
||||
// Boolean, numbers and options are special as false and 0 are valid values
|
||||
// and should not be replaced with default value
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name] !== undefined ? nodeValues[nodeProperties.name] : nodeProperties.default;
|
||||
nodeParameters[nodeProperties.name] =
|
||||
nodeValues[nodeProperties.name] !== undefined
|
||||
? nodeValues[nodeProperties.name]
|
||||
: nodeProperties.default;
|
||||
} else {
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name] || nodeProperties.default;
|
||||
nodeParameters[nodeProperties.name] =
|
||||
nodeValues[nodeProperties.name] || nodeProperties.default;
|
||||
}
|
||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||
} else if ((nodeValues[nodeProperties.name] !== nodeProperties.default && typeof nodeValues[nodeProperties.name] !== 'object') ||
|
||||
(typeof nodeValues[nodeProperties.name] === 'object' && !isEqual(nodeValues[nodeProperties.name], nodeProperties.default)) ||
|
||||
(nodeValues[nodeProperties.name] !== undefined && parentType === 'collection')) {
|
||||
} else if (
|
||||
(nodeValues[nodeProperties.name] !== nodeProperties.default &&
|
||||
typeof nodeValues[nodeProperties.name] !== 'object') ||
|
||||
(typeof nodeValues[nodeProperties.name] === 'object' &&
|
||||
!isEqual(nodeValues[nodeProperties.name], nodeProperties.default)) ||
|
||||
(nodeValues[nodeProperties.name] !== undefined && parentType === 'collection')
|
||||
) {
|
||||
// Set only if it is different to the default value
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||
|
@ -595,7 +634,7 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
}
|
||||
}
|
||||
|
||||
if (onlySimpleTypes === true) {
|
||||
if (onlySimpleTypes) {
|
||||
// It is only supposed to resolve the simple types. So continue.
|
||||
continue;
|
||||
}
|
||||
|
@ -605,16 +644,21 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
if (nodeProperties.type === 'collection') {
|
||||
// Is collection
|
||||
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues === true) {
|
||||
if (
|
||||
nodeProperties.typeOptions !== undefined &&
|
||||
nodeProperties.typeOptions.multipleValues === true
|
||||
) {
|
||||
// Multiple can be set so will be an array
|
||||
|
||||
// Return directly the values like they are
|
||||
if (nodeValues[nodeProperties.name] !== undefined) {
|
||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
||||
} else if (returnDefaults === true) {
|
||||
} else if (returnDefaults) {
|
||||
// Does not have values defined but defaults should be returned
|
||||
if (Array.isArray(nodeProperties.default)) {
|
||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||
nodeParameters[nodeProperties.name] = JSON.parse(
|
||||
JSON.stringify(nodeProperties.default),
|
||||
);
|
||||
} else {
|
||||
// As it is probably wrong for many nodes, do we keep on returning an empty array if
|
||||
// anything else than an array is set as default
|
||||
|
@ -622,21 +666,28 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
}
|
||||
}
|
||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||
} else {
|
||||
if (nodeValues[nodeProperties.name] !== undefined) {
|
||||
} else if (nodeValues[nodeProperties.name] !== undefined) {
|
||||
// Has values defined so get them
|
||||
const tempNodeParameters = getNodeParameters(nodeProperties.options as INodeProperties[], nodeValues[nodeProperties.name] as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
|
||||
const tempNodeParameters = getNodeParameters(
|
||||
nodeProperties.options as INodeProperties[],
|
||||
nodeValues[nodeProperties.name] as INodeParameters,
|
||||
returnDefaults,
|
||||
returnNoneDisplayed,
|
||||
false,
|
||||
false,
|
||||
nodeValuesRoot,
|
||||
nodeProperties.type,
|
||||
);
|
||||
|
||||
if (tempNodeParameters !== null) {
|
||||
nodeParameters[nodeProperties.name] = tempNodeParameters;
|
||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||
}
|
||||
} else if (returnDefaults === true) {
|
||||
} else if (returnDefaults) {
|
||||
// Does not have values defined but defaults should be returned
|
||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||
}
|
||||
}
|
||||
} else if (nodeProperties.type === 'fixedCollection') {
|
||||
// Is fixedCollection
|
||||
|
||||
|
@ -646,7 +697,7 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
let nodePropertyOptions: INodePropertyCollection | undefined;
|
||||
|
||||
let propertyValues = nodeValues[nodeProperties.name];
|
||||
if (returnDefaults === true) {
|
||||
if (returnDefaults) {
|
||||
if (propertyValues === undefined) {
|
||||
propertyValues = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||
}
|
||||
|
@ -654,20 +705,39 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
|
||||
// Iterate over all collections
|
||||
for (const itemName of Object.keys(propertyValues || {})) {
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues === true) {
|
||||
if (
|
||||
nodeProperties.typeOptions !== undefined &&
|
||||
nodeProperties.typeOptions.multipleValues === true
|
||||
) {
|
||||
// Multiple can be set so will be an array
|
||||
|
||||
const tempArrayValue: INodeParameters[] = [];
|
||||
// Iterate over all items as it contains multiple ones
|
||||
for (const nodeValue of (propertyValues as INodeParameters)[itemName] as INodeParameters[]) {
|
||||
nodePropertyOptions = nodeProperties!.options!.find((nodePropertyOptions) => nodePropertyOptions.name === itemName) as INodePropertyCollection;
|
||||
for (const nodeValue of (propertyValues as INodeParameters)[
|
||||
itemName
|
||||
] as INodeParameters[]) {
|
||||
nodePropertyOptions = nodeProperties.options!.find(
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
(nodePropertyOptions) => nodePropertyOptions.name === itemName,
|
||||
) as INodePropertyCollection;
|
||||
|
||||
if (nodePropertyOptions === undefined) {
|
||||
throw new Error(`Could not find property option "${itemName}" for "${nodeProperties.name}"`);
|
||||
throw new Error(
|
||||
`Could not find property option "${itemName}" for "${nodeProperties.name}"`,
|
||||
);
|
||||
}
|
||||
|
||||
tempNodePropertiesArray = (nodePropertyOptions as INodePropertyCollection).values!;
|
||||
tempValue = getNodeParameters(tempNodePropertiesArray, nodeValue as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
|
||||
tempNodePropertiesArray = nodePropertyOptions.values!;
|
||||
tempValue = getNodeParameters(
|
||||
tempNodePropertiesArray,
|
||||
nodeValue,
|
||||
returnDefaults,
|
||||
returnNoneDisplayed,
|
||||
false,
|
||||
false,
|
||||
nodeValuesRoot,
|
||||
nodeProperties.type,
|
||||
);
|
||||
if (tempValue !== null) {
|
||||
tempArrayValue.push(tempValue);
|
||||
}
|
||||
|
@ -678,11 +748,23 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
tempNodeParameters = {};
|
||||
|
||||
// Get the options of the current item
|
||||
const nodePropertyOptions = nodeProperties!.options!.find((data) => data.name === itemName);
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const nodePropertyOptions = nodeProperties.options!.find(
|
||||
(data) => data.name === itemName,
|
||||
);
|
||||
|
||||
if (nodePropertyOptions !== undefined) {
|
||||
tempNodePropertiesArray = (nodePropertyOptions as INodePropertyCollection).values!;
|
||||
tempValue = getNodeParameters(tempNodePropertiesArray, (nodeValues[nodeProperties.name] as INodeParameters)[itemName] as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
|
||||
tempValue = getNodeParameters(
|
||||
tempNodePropertiesArray,
|
||||
(nodeValues[nodeProperties.name] as INodeParameters)[itemName] as INodeParameters,
|
||||
returnDefaults,
|
||||
returnNoneDisplayed,
|
||||
false,
|
||||
false,
|
||||
nodeValuesRoot,
|
||||
nodeProperties.type,
|
||||
);
|
||||
if (tempValue !== null) {
|
||||
Object.assign(tempNodeParameters, tempValue);
|
||||
}
|
||||
|
@ -694,13 +776,15 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
}
|
||||
}
|
||||
|
||||
if (Object.keys(collectionValues).length !== 0 || returnDefaults === true) {
|
||||
if (Object.keys(collectionValues).length !== 0 || returnDefaults) {
|
||||
// Set only if value got found
|
||||
|
||||
if (returnDefaults === true) {
|
||||
if (returnDefaults) {
|
||||
// Set also when it has the default value
|
||||
if (collectionValues === undefined) {
|
||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||
nodeParameters[nodeProperties.name] = JSON.parse(
|
||||
JSON.stringify(nodeProperties.default),
|
||||
);
|
||||
} else {
|
||||
nodeParameters[nodeProperties.name] = collectionValues;
|
||||
}
|
||||
|
@ -717,7 +801,6 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
return nodeParameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Brings the output data in a format that can be returned from a node
|
||||
*
|
||||
|
@ -726,7 +809,10 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
|||
* @param {number} [outputIndex=0]
|
||||
* @returns {Promise<INodeExecutionData[][]>}
|
||||
*/
|
||||
export async function prepareOutputData(outputData: INodeExecutionData[], outputIndex = 0): Promise<INodeExecutionData[][]> {
|
||||
export async function prepareOutputData(
|
||||
outputData: INodeExecutionData[],
|
||||
outputIndex = 0,
|
||||
): Promise<INodeExecutionData[][]> {
|
||||
// TODO: Check if node has output with that index
|
||||
const returnData = [];
|
||||
|
||||
|
@ -739,8 +825,6 @@ export async function prepareOutputData(outputData: INodeExecutionData[], output
|
|||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the webhooks which should be created for the give node
|
||||
*
|
||||
|
@ -749,7 +833,12 @@ export async function prepareOutputData(outputData: INodeExecutionData[], output
|
|||
* @param {INode} node
|
||||
* @returns {IWebhookData[]}
|
||||
*/
|
||||
export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, ignoreRestartWehbooks = false): IWebhookData[] {
|
||||
export function getNodeWebhooks(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
ignoreRestartWehbooks = false,
|
||||
): IWebhookData[] {
|
||||
if (node.disabled === true) {
|
||||
// Node is disabled so webhooks will also not be enabled
|
||||
return [];
|
||||
|
@ -767,15 +856,21 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData:
|
|||
|
||||
const returnData: IWebhookData[] = [];
|
||||
for (const webhookDescription of nodeType.description.webhooks) {
|
||||
|
||||
if (ignoreRestartWehbooks === true && webhookDescription.restartWebhook === true) {
|
||||
if (ignoreRestartWehbooks && webhookDescription.restartWebhook === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let nodeWebhookPath = workflow.expression.getSimpleParameterValue(node, webhookDescription['path'], mode, {});
|
||||
let nodeWebhookPath = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
webhookDescription.path,
|
||||
mode,
|
||||
{},
|
||||
);
|
||||
if (nodeWebhookPath === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`);
|
||||
console.error(
|
||||
`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -788,15 +883,35 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData:
|
|||
nodeWebhookPath = nodeWebhookPath.slice(0, -1);
|
||||
}
|
||||
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['isFullPath'], 'internal', {}, false) as boolean;
|
||||
const restartWebhook: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['restartWebhook'], 'internal', {}, false) as boolean;
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
webhookDescription.isFullPath,
|
||||
'internal',
|
||||
{},
|
||||
false,
|
||||
) as boolean;
|
||||
const restartWebhook: boolean = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
webhookDescription.restartWebhook,
|
||||
'internal',
|
||||
{},
|
||||
false,
|
||||
) as boolean;
|
||||
const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath, restartWebhook);
|
||||
|
||||
const httpMethod = workflow.expression.getSimpleParameterValue(node, webhookDescription['httpMethod'], mode, {}, 'GET');
|
||||
const httpMethod = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
webhookDescription.httpMethod,
|
||||
mode,
|
||||
{},
|
||||
'GET',
|
||||
);
|
||||
|
||||
if (httpMethod === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`);
|
||||
console.error(
|
||||
`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -838,10 +953,17 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
|||
|
||||
const returnData: IWebhookData[] = [];
|
||||
for (const webhookDescription of nodeType.description.webhooks) {
|
||||
let nodeWebhookPath = workflow.expression.getSimpleParameterValue(node, webhookDescription['path'], mode, {});
|
||||
let nodeWebhookPath = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
webhookDescription.path,
|
||||
mode,
|
||||
{},
|
||||
);
|
||||
if (nodeWebhookPath === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`);
|
||||
console.error(
|
||||
`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -854,19 +976,32 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
|||
nodeWebhookPath = nodeWebhookPath.slice(0, -1);
|
||||
}
|
||||
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['isFullPath'], mode, {}, false) as boolean;
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
webhookDescription.isFullPath,
|
||||
mode,
|
||||
{},
|
||||
false,
|
||||
) as boolean;
|
||||
|
||||
const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath);
|
||||
|
||||
const httpMethod = workflow.expression.getSimpleParameterValue(node, webhookDescription['httpMethod'], mode, {});
|
||||
const httpMethod = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
webhookDescription.httpMethod,
|
||||
mode,
|
||||
{},
|
||||
);
|
||||
|
||||
if (httpMethod === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`);
|
||||
console.error(
|
||||
`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
returnData.push({
|
||||
httpMethod: httpMethod.toString() as WebhookHttpMethod,
|
||||
node: node.name,
|
||||
|
@ -879,7 +1014,6 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
|||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the webhook path
|
||||
*
|
||||
|
@ -889,11 +1023,18 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
|||
* @param {string} path
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getNodeWebhookPath(workflowId: string, node: INode, path: string, isFullPath?: boolean, restartWebhook?: boolean): string {
|
||||
export function getNodeWebhookPath(
|
||||
workflowId: string,
|
||||
node: INode,
|
||||
path: string,
|
||||
isFullPath?: boolean,
|
||||
restartWebhook?: boolean,
|
||||
): string {
|
||||
let webhookPath = '';
|
||||
if (restartWebhook === true) {
|
||||
return path;
|
||||
} else if (node.webhookId === undefined) {
|
||||
}
|
||||
if (node.webhookId === undefined) {
|
||||
webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`;
|
||||
} else {
|
||||
if (isFullPath === true) {
|
||||
|
@ -904,7 +1045,6 @@ export function getNodeWebhookPath(workflowId: string, node: INode, path: string
|
|||
return webhookPath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the webhook URL
|
||||
*
|
||||
|
@ -916,7 +1056,13 @@ export function getNodeWebhookPath(workflowId: string, node: INode, path: string
|
|||
* @param {boolean} isFullPath
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INode, path: string, isFullPath?: boolean): string {
|
||||
export function getNodeWebhookUrl(
|
||||
baseUrl: string,
|
||||
workflowId: string,
|
||||
node: INode,
|
||||
path: string,
|
||||
isFullPath?: boolean,
|
||||
): string {
|
||||
if ((path.startsWith(':') || path.includes('/:')) && node.webhookId) {
|
||||
// setting this to false to prefix the webhookId
|
||||
isFullPath = false;
|
||||
|
@ -927,7 +1073,6 @@ export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INo
|
|||
return `${baseUrl}/${getNodeWebhookPath(workflowId, node, path, isFullPath)}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the parameter-issues of the node
|
||||
*
|
||||
|
@ -936,7 +1081,10 @@ export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INo
|
|||
* @param {INode} node The data of the node
|
||||
* @returns {(INodeIssues | null)}
|
||||
*/
|
||||
export function getNodeParametersIssues(nodePropertiesArray: INodeProperties[], node: INode): INodeIssues | null {
|
||||
export function getNodeParametersIssues(
|
||||
nodePropertiesArray: INodeProperties[],
|
||||
node: INode,
|
||||
): INodeIssues | null {
|
||||
const foundIssues: INodeIssues = {};
|
||||
let propertyIssues: INodeIssues;
|
||||
|
||||
|
@ -957,7 +1105,6 @@ export function getNodeParametersIssues(nodePropertiesArray: INodeProperties[],
|
|||
return foundIssues;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the issues of the node as string
|
||||
*
|
||||
|
@ -973,12 +1120,10 @@ export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[]
|
|||
nodeIssues.push(`Execution Error.`);
|
||||
}
|
||||
|
||||
const objectProperties = [
|
||||
'parameters',
|
||||
'credentials',
|
||||
];
|
||||
const objectProperties = ['parameters', 'credentials'];
|
||||
|
||||
let issueText: string, parameterName: string;
|
||||
let issueText: string;
|
||||
let parameterName: string;
|
||||
for (const propertyName of objectProperties) {
|
||||
if (issues[propertyName] !== undefined) {
|
||||
for (parameterName of Object.keys(issues[propertyName] as object)) {
|
||||
|
@ -1000,7 +1145,6 @@ export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[]
|
|||
return nodeIssues;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an issue if the parameter is not defined
|
||||
*
|
||||
|
@ -1009,11 +1153,17 @@ export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[]
|
|||
* @param {INodeProperties} nodeProperties The properties of the node
|
||||
* @param {NodeParameterValue} value The value of the parameter
|
||||
*/
|
||||
export function addToIssuesIfMissing(foundIssues: INodeIssues, nodeProperties: INodeProperties, value: NodeParameterValue) {
|
||||
export function addToIssuesIfMissing(
|
||||
foundIssues: INodeIssues,
|
||||
nodeProperties: INodeProperties,
|
||||
value: NodeParameterValue,
|
||||
) {
|
||||
// TODO: Check what it really has when undefined
|
||||
if ((nodeProperties.type === 'string' && (value === '' || value === undefined)) ||
|
||||
if (
|
||||
(nodeProperties.type === 'string' && (value === '' || value === undefined)) ||
|
||||
(nodeProperties.type === 'multiOptions' && Array.isArray(value) && value.length === 0) ||
|
||||
(nodeProperties.type === 'dateTime' && value === undefined)) {
|
||||
(nodeProperties.type === 'dateTime' && value === undefined)
|
||||
) {
|
||||
// Parameter is requried but empty
|
||||
if (foundIssues.parameters === undefined) {
|
||||
foundIssues.parameters = {};
|
||||
|
@ -1022,11 +1172,12 @@ export function addToIssuesIfMissing(foundIssues: INodeIssues, nodeProperties: I
|
|||
foundIssues.parameters[nodeProperties.name] = [];
|
||||
}
|
||||
|
||||
foundIssues.parameters[nodeProperties.name].push(`Parameter "${nodeProperties.displayName}" is required.`);
|
||||
foundIssues.parameters[nodeProperties.name].push(
|
||||
`Parameter "${nodeProperties.displayName}" is required.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the parameter value
|
||||
*
|
||||
|
@ -1036,15 +1187,14 @@ export function addToIssuesIfMissing(foundIssues: INodeIssues, nodeProperties: I
|
|||
* @param {string} path The path to the properties
|
||||
* @returns
|
||||
*/
|
||||
export function getParameterValueByPath(nodeValues: INodeParameters, parameterName: string, path: string) {
|
||||
return get(
|
||||
nodeValues,
|
||||
path ? path + '.' + parameterName : parameterName,
|
||||
);
|
||||
export function getParameterValueByPath(
|
||||
nodeValues: INodeParameters,
|
||||
parameterName: string,
|
||||
path: string,
|
||||
) {
|
||||
return get(nodeValues, path ? `${path}.${parameterName}` : parameterName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the issues with the given node-values
|
||||
*
|
||||
|
@ -1054,7 +1204,11 @@ export function getParameterValueByPath(nodeValues: INodeParameters, parameterNa
|
|||
* @param {string} path The path to the properties
|
||||
* @returns {INodeIssues}
|
||||
*/
|
||||
export function getParameterIssues(nodeProperties: INodeProperties, nodeValues: INodeParameters, path: string): INodeIssues {
|
||||
export function getParameterIssues(
|
||||
nodeProperties: INodeProperties,
|
||||
nodeValues: INodeParameters,
|
||||
path: string,
|
||||
): INodeIssues {
|
||||
const foundIssues: INodeIssues = {};
|
||||
let value;
|
||||
|
||||
|
@ -1062,11 +1216,15 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
|||
if (displayParameterPath(nodeValues, nodeProperties, path)) {
|
||||
value = getParameterValueByPath(nodeValues, nodeProperties.name, path);
|
||||
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues !== undefined) {
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
nodeProperties.typeOptions !== undefined &&
|
||||
nodeProperties.typeOptions.multipleValues !== undefined
|
||||
) {
|
||||
// Multiple can be set so will be an array
|
||||
if (Array.isArray(value)) {
|
||||
for (const singleValue of value as NodeParameterValue[]) {
|
||||
addToIssuesIfMissing(foundIssues, nodeProperties, singleValue as NodeParameterValue);
|
||||
addToIssuesIfMissing(foundIssues, nodeProperties, singleValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1106,7 +1264,7 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
|||
});
|
||||
}
|
||||
} else if (nodeProperties.type === 'fixedCollection') {
|
||||
basePath = basePath ? `${basePath}.` : '' + nodeProperties.name + '.';
|
||||
basePath = basePath ? `${basePath}.` : `${nodeProperties.name}.`;
|
||||
|
||||
let propertyOptions: INodePropertyCollection;
|
||||
for (propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
||||
|
@ -1116,14 +1274,18 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
|||
continue;
|
||||
}
|
||||
|
||||
if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues !== undefined) {
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
nodeProperties.typeOptions !== undefined &&
|
||||
nodeProperties.typeOptions.multipleValues !== undefined
|
||||
) {
|
||||
// Multiple can be set so will be an array of objects
|
||||
if (Array.isArray(value)) {
|
||||
for (let i = 0; i < (value as INodeParameters[]).length; i++) {
|
||||
for (const option of propertyOptions.values) {
|
||||
checkChildNodeProperties.push({
|
||||
basePath: `${basePath}${propertyOptions.name}[${i}]`,
|
||||
data: option as INodeProperties,
|
||||
data: option,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1133,7 +1295,7 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
|||
for (const option of propertyOptions.values) {
|
||||
checkChildNodeProperties.push({
|
||||
basePath: basePath + propertyOptions.name,
|
||||
data: option as INodeProperties,
|
||||
data: option,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1146,14 +1308,13 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
|||
let propertyIssues;
|
||||
|
||||
for (const optionData of checkChildNodeProperties) {
|
||||
propertyIssues = getParameterIssues(optionData.data as INodeProperties, nodeValues, optionData.basePath);
|
||||
propertyIssues = getParameterIssues(optionData.data, nodeValues, optionData.basePath);
|
||||
mergeIssues(foundIssues, propertyIssues);
|
||||
}
|
||||
|
||||
return foundIssues;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Merges multiple NodeIssues together
|
||||
*
|
||||
|
@ -1172,10 +1333,7 @@ export function mergeIssues(destination: INodeIssues, source: INodeIssues | null
|
|||
destination.execution = true;
|
||||
}
|
||||
|
||||
const objectProperties = [
|
||||
'parameters',
|
||||
'credentials',
|
||||
];
|
||||
const objectProperties = ['parameters', 'credentials'];
|
||||
|
||||
let destinationProperty: INodeIssueObjectProperty;
|
||||
for (const propertyName of objectProperties) {
|
||||
|
@ -1190,7 +1348,10 @@ export function mergeIssues(destination: INodeIssues, source: INodeIssues | null
|
|||
if (destinationProperty[parameterName] === undefined) {
|
||||
destinationProperty[parameterName] = [];
|
||||
}
|
||||
destinationProperty[parameterName].push.apply(destinationProperty[parameterName], (source[propertyName] as INodeIssueObjectProperty)[parameterName]);
|
||||
destinationProperty[parameterName].push.apply(
|
||||
destinationProperty[parameterName],
|
||||
(source[propertyName] as INodeIssueObjectProperty)[parameterName],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1200,8 +1361,6 @@ export function mergeIssues(destination: INodeIssues, source: INodeIssues | null
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Merges the given node properties
|
||||
*
|
||||
|
@ -1209,10 +1368,13 @@ export function mergeIssues(destination: INodeIssues, source: INodeIssues | null
|
|||
* @param {INodeProperties[]} mainProperties
|
||||
* @param {INodeProperties[]} addProperties
|
||||
*/
|
||||
export function mergeNodeProperties(mainProperties: INodeProperties[], addProperties: INodeProperties[]): void {
|
||||
export function mergeNodeProperties(
|
||||
mainProperties: INodeProperties[],
|
||||
addProperties: INodeProperties[],
|
||||
): void {
|
||||
let existingIndex: number;
|
||||
for (const property of addProperties) {
|
||||
existingIndex = mainProperties.findIndex(element => element.name === property.name);
|
||||
existingIndex = mainProperties.findIndex((element) => element.name === property.name);
|
||||
|
||||
if (existingIndex === -1) {
|
||||
// Property does not exist yet, so add
|
||||
|
|
|
@ -1,20 +1,35 @@
|
|||
|
||||
import {
|
||||
IDataObject,
|
||||
IObservableObject,
|
||||
} from './';
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { IDataObject, IObservableObject } from '.';
|
||||
|
||||
export interface IObservableOptions {
|
||||
ignoreEmptyOnFirstChild?: boolean;
|
||||
}
|
||||
|
||||
export function create(target: IDataObject, parent?: IObservableObject, option?: IObservableOptions, depth?: number): IDataObject {
|
||||
export function create(
|
||||
target: IDataObject,
|
||||
parent?: IObservableObject,
|
||||
option?: IObservableOptions,
|
||||
depth?: number,
|
||||
): IDataObject {
|
||||
// eslint-disable-next-line no-param-reassign, @typescript-eslint/prefer-nullish-coalescing
|
||||
depth = depth || 0;
|
||||
|
||||
// Make all the children of target also observeable
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key in target) {
|
||||
if (typeof target[key] === 'object' && target[key] !== null) {
|
||||
target[key] = create(target[key] as IDataObject, (parent || target) as IObservableObject, option, depth + 1);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
target[key] = create(
|
||||
target[key] as IDataObject,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
(parent || target) as IObservableObject,
|
||||
option,
|
||||
depth + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +38,7 @@ export function create(target: IDataObject, parent?: IObservableObject, option?:
|
|||
writable: true,
|
||||
});
|
||||
return new Proxy(target, {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
deleteProperty(target, name) {
|
||||
if (parent === undefined) {
|
||||
// If no parent is given mark current data as changed
|
||||
|
@ -39,8 +55,15 @@ export function create(target: IDataObject, parent?: IObservableObject, option?:
|
|||
set(target, name, value) {
|
||||
if (parent === undefined) {
|
||||
// If no parent is given mark current data as changed
|
||||
if (option !== undefined && option.ignoreEmptyOnFirstChild === true && depth === 0
|
||||
&& target[name.toString()] === undefined && typeof value === 'object' && Object.keys(value).length === 0) {
|
||||
if (
|
||||
option !== undefined &&
|
||||
option.ignoreEmptyOnFirstChild === true &&
|
||||
depth === 0 &&
|
||||
target[name.toString()] === undefined &&
|
||||
typeof value === 'object' &&
|
||||
Object.keys(value).length === 0
|
||||
// eslint-disable-next-line no-empty
|
||||
) {
|
||||
} else {
|
||||
(target as IObservableObject).__dataChanged = true;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-for-in-array */
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable import/no-cycle */
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
Expression,
|
||||
IConnections,
|
||||
|
@ -26,20 +37,27 @@ import {
|
|||
WebhookSetupMethodNames,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from './';
|
||||
} from '.';
|
||||
|
||||
import { IConnection, IDataObject, IObservableObject } from './Interfaces';
|
||||
|
||||
|
||||
export class Workflow {
|
||||
id: string | undefined;
|
||||
|
||||
name: string | undefined;
|
||||
|
||||
nodes: INodes = {};
|
||||
|
||||
connectionsBySourceNode: IConnections;
|
||||
|
||||
connectionsByDestinationNode: IConnections;
|
||||
|
||||
nodeTypes: INodeTypes;
|
||||
|
||||
expression: Expression;
|
||||
|
||||
active: boolean;
|
||||
|
||||
settings: IWorkflowSettings;
|
||||
|
||||
// To save workflow specific static data like for example
|
||||
|
@ -47,7 +65,16 @@ export class Workflow {
|
|||
staticData: IDataObject;
|
||||
|
||||
// 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}) {
|
||||
constructor(parameters: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
nodes: INode[];
|
||||
connections: IConnections;
|
||||
active: boolean;
|
||||
nodeTypes: INodeTypes;
|
||||
staticData?: IDataObject;
|
||||
settings?: IWorkflowSettings;
|
||||
}) {
|
||||
this.id = parameters.id;
|
||||
this.name = parameters.name;
|
||||
this.nodeTypes = parameters.nodeTypes;
|
||||
|
@ -70,7 +97,12 @@ export class Workflow {
|
|||
}
|
||||
|
||||
// Add default values
|
||||
const nodeParameters = NodeHelpers.getNodeParameters(nodeType.description.properties, node.parameters, true, false);
|
||||
const nodeParameters = NodeHelpers.getNodeParameters(
|
||||
nodeType.description.properties,
|
||||
node.parameters,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
node.parameters = nodeParameters !== null ? nodeParameters : {};
|
||||
}
|
||||
this.connectionsBySourceNode = parameters.connections;
|
||||
|
@ -80,15 +112,15 @@ export class Workflow {
|
|||
|
||||
this.active = parameters.active || false;
|
||||
|
||||
this.staticData = ObservableObject.create(parameters.staticData || {}, undefined, { ignoreEmptyOnFirstChild: true });
|
||||
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.
|
||||
|
@ -140,8 +172,6 @@ export class Workflow {
|
|||
return returnConnection;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A workflow can only be activated if it has a node which has either triggers
|
||||
* or webhooks defined.
|
||||
|
@ -162,6 +192,7 @@ export class Workflow {
|
|||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
if (ignoreNodeTypes !== undefined && ignoreNodeTypes.includes(node.type)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -173,7 +204,11 @@ export class Workflow {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (nodeType.poll !== undefined || nodeType.trigger !== undefined || nodeType.webhook !== undefined) {
|
||||
if (
|
||||
nodeType.poll !== undefined ||
|
||||
nodeType.trigger !== undefined ||
|
||||
nodeType.webhook !== undefined
|
||||
) {
|
||||
// Is a trigger node. So workflow can be activated.
|
||||
return true;
|
||||
}
|
||||
|
@ -182,8 +217,6 @@ export class Workflow {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks if everything in the workflow is complete
|
||||
* and ready to be executed. If it returns null everything
|
||||
|
@ -216,7 +249,7 @@ export class Workflow {
|
|||
typeUnknown: true,
|
||||
};
|
||||
} else {
|
||||
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.description.properties!, node);
|
||||
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node);
|
||||
}
|
||||
|
||||
if (nodeIssues !== null) {
|
||||
|
@ -231,8 +264,6 @@ export class Workflow {
|
|||
return workflowIssues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the static data of the workflow.
|
||||
* It gets saved with the workflow and will be the same for
|
||||
|
@ -249,11 +280,15 @@ export class Workflow {
|
|||
key = 'global';
|
||||
} else if (type === 'node') {
|
||||
if (node === undefined) {
|
||||
throw new Error(`The request data of context type "node" the node parameter has to be set!`);
|
||||
throw new Error(
|
||||
`The request data of context type "node" the node parameter has to be set!`,
|
||||
);
|
||||
}
|
||||
key = `node:${node.name}`;
|
||||
} else {
|
||||
throw new Error(`The context type "${type}" is not know. Only "global" and node" are supported!`);
|
||||
throw new Error(
|
||||
`The context type "${type}" is not know. Only "global" and node" are supported!`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.staticData[key] === undefined) {
|
||||
|
@ -265,8 +300,6 @@ export class Workflow {
|
|||
return this.staticData[key] as IDataObject;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the trigger nodes in the workflow.
|
||||
*
|
||||
|
@ -274,10 +307,9 @@ export class Workflow {
|
|||
* @memberof Workflow
|
||||
*/
|
||||
getTriggerNodes(): INode[] {
|
||||
return this.queryNodes((nodeType: INodeType) => !!nodeType.trigger );
|
||||
return this.queryNodes((nodeType: INodeType) => !!nodeType.trigger);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the poll nodes in the workflow
|
||||
*
|
||||
|
@ -285,10 +317,9 @@ export class Workflow {
|
|||
* @memberof Workflow
|
||||
*/
|
||||
getPollNodes(): INode[] {
|
||||
return this.queryNodes((nodeType: INodeType) => !!nodeType.poll );
|
||||
return this.queryNodes((nodeType: INodeType) => !!nodeType.poll);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the nodes in the workflow for which the given
|
||||
* checkFunction return true
|
||||
|
@ -321,8 +352,6 @@ export class Workflow {
|
|||
return returnNodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the node with the given name if it exists else null
|
||||
*
|
||||
|
@ -338,7 +367,6 @@ export class Workflow {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renames nodes in expressions
|
||||
*
|
||||
|
@ -348,7 +376,11 @@ export class Workflow {
|
|||
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
renameNodeInExpressions(parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], currentName: string, newName: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||
renameNodeInExpressions(
|
||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
currentName: string,
|
||||
newName: string,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||
if (typeof parameterValue !== 'object') {
|
||||
// Reached the actual value
|
||||
if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
|
||||
|
@ -362,7 +394,11 @@ export class Workflow {
|
|||
// In case some special characters are used in name escape them
|
||||
const currentNameEscaped = currentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
parameterValue = parameterValue.replace(new RegExp(`(\\$node(\.|\\["|\\[\'))${currentNameEscaped}((\.|"\\]|\'\\]))`, 'g'), `$1${newName}$3`);
|
||||
parameterValue = parameterValue.replace(
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
new RegExp(`(\\$node(\.|\\["|\\[\'))${currentNameEscaped}((\.|"\\]|\'\\]))`, 'g'),
|
||||
`$1${newName}$3`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,7 +406,8 @@ export class Workflow {
|
|||
}
|
||||
|
||||
if (Array.isArray(parameterValue)) {
|
||||
const returnArray: any[] = []; // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const returnArray: any[] = [];
|
||||
|
||||
for (const currentValue of parameterValue) {
|
||||
returnArray.push(this.renameNodeInExpressions(currentValue, currentName, newName));
|
||||
|
@ -379,17 +416,21 @@ export class Workflow {
|
|||
return returnArray;
|
||||
}
|
||||
|
||||
const returnData: any = {}; // tslint:disable-line:no-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const returnData: any = {};
|
||||
|
||||
for (const parameterName of Object.keys(parameterValue || {})) {
|
||||
returnData[parameterName] = this.renameNodeInExpressions(parameterValue![parameterName], currentName, newName);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
returnData[parameterName] = this.renameNodeInExpressions(
|
||||
parameterValue![parameterName],
|
||||
currentName,
|
||||
newName,
|
||||
);
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Rename a node in the workflow
|
||||
*
|
||||
|
@ -398,7 +439,6 @@ export class Workflow {
|
|||
* @memberof Workflow
|
||||
*/
|
||||
renameNode(currentName: string, newName: string) {
|
||||
|
||||
// Rename the node itself
|
||||
if (this.nodes[currentName] !== undefined) {
|
||||
this.nodes[newName] = this.nodes[currentName];
|
||||
|
@ -409,7 +449,11 @@ export class Workflow {
|
|||
// Update the expressions which reference the node
|
||||
// with its old name
|
||||
for (const node of Object.values(this.nodes)) {
|
||||
node.parameters = this.renameNodeInExpressions(node.parameters, currentName, newName) as INodeParameters;
|
||||
node.parameters = this.renameNodeInExpressions(
|
||||
node.parameters,
|
||||
currentName,
|
||||
newName,
|
||||
) as INodeParameters;
|
||||
}
|
||||
|
||||
// Change all source connections
|
||||
|
@ -419,12 +463,21 @@ export class Workflow {
|
|||
}
|
||||
|
||||
// Change all destination connections
|
||||
let sourceNode: string, type: string, sourceIndex: string, connectionIndex: string, connectionData: IConnection;
|
||||
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 (connectionIndex of Object.keys(
|
||||
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)],
|
||||
)) {
|
||||
connectionData =
|
||||
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)][
|
||||
parseInt(connectionIndex, 10)
|
||||
];
|
||||
if (connectionData.node === currentName) {
|
||||
connectionData.node = newName;
|
||||
}
|
||||
|
@ -434,11 +487,11 @@ export class Workflow {
|
|||
}
|
||||
|
||||
// Use the updated connections to create updated connections by destionation nodes
|
||||
this.connectionsByDestinationNode = this.__getConnectionsByDestination(this.connectionsBySourceNode);
|
||||
this.connectionsByDestinationNode = this.__getConnectionsByDestination(
|
||||
this.connectionsBySourceNode,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Finds the highest parent nodes of the node with the given name
|
||||
*
|
||||
|
@ -448,7 +501,12 @@ export class Workflow {
|
|||
* @returns {string[]}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getHighestNode(nodeName: string, type = 'main', nodeConnectionIndex?:number, checkedNodes?: string[]): string[] {
|
||||
getHighestNode(
|
||||
nodeName: string,
|
||||
type = 'main',
|
||||
nodeConnectionIndex?: number,
|
||||
checkedNodes?: string[],
|
||||
): string[] {
|
||||
const currentHighest: string[] = [];
|
||||
if (this.nodes[nodeName].disabled === false) {
|
||||
// If the current node is not disabled itself is the highest
|
||||
|
@ -467,23 +525,28 @@ export class Workflow {
|
|||
|
||||
checkedNodes = checkedNodes || [];
|
||||
|
||||
if (checkedNodes!.includes(nodeName)) {
|
||||
if (checkedNodes.includes(nodeName)) {
|
||||
// Node got checked already before
|
||||
return currentHighest;
|
||||
}
|
||||
|
||||
checkedNodes!.push(nodeName);
|
||||
checkedNodes.push(nodeName);
|
||||
|
||||
const returnNodes: string[] = [];
|
||||
let addNodes: string[];
|
||||
|
||||
let connectionsByIndex: IConnection[];
|
||||
for (let connectionIndex = 0; connectionIndex < this.connectionsByDestinationNode[nodeName][type].length; connectionIndex++) {
|
||||
for (
|
||||
let connectionIndex = 0;
|
||||
connectionIndex < this.connectionsByDestinationNode[nodeName][type].length;
|
||||
connectionIndex++
|
||||
) {
|
||||
if (nodeConnectionIndex !== undefined && nodeConnectionIndex !== connectionIndex) {
|
||||
// If a connection-index is given ignore all other ones
|
||||
continue;
|
||||
}
|
||||
connectionsByIndex = this.connectionsByDestinationNode[nodeName][type][connectionIndex];
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
connectionsByIndex.forEach((connection) => {
|
||||
if (checkedNodes!.includes(connection.node)) {
|
||||
// Node got checked already before
|
||||
|
@ -512,8 +575,6 @@ export class Workflow {
|
|||
return returnNodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the after the given one
|
||||
*
|
||||
|
@ -527,8 +588,6 @@ export class Workflow {
|
|||
return this.getConnectedNodes(this.connectionsBySourceNode, nodeName, type, depth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the nodes before the given one
|
||||
*
|
||||
|
@ -542,8 +601,6 @@ export class Workflow {
|
|||
return this.getConnectedNodes(this.connectionsByDestinationNode, nodeName, type, depth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets all the nodes which are connected nodes starting from
|
||||
* the given one
|
||||
|
@ -556,7 +613,13 @@ export class Workflow {
|
|||
* @returns {string[]}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getConnectedNodes(connections: IConnections, nodeName: string, type = 'main', depth = -1, checkedNodes?: string[]): string[] {
|
||||
getConnectedNodes(
|
||||
connections: IConnections,
|
||||
nodeName: string,
|
||||
type = 'main',
|
||||
depth = -1,
|
||||
checkedNodes?: string[],
|
||||
): string[] {
|
||||
depth = depth === -1 ? -1 : depth;
|
||||
const newDepth = depth === -1 ? depth : depth - 1;
|
||||
if (depth === 0) {
|
||||
|
@ -576,12 +639,12 @@ export class Workflow {
|
|||
|
||||
checkedNodes = checkedNodes || [];
|
||||
|
||||
if (checkedNodes!.includes(nodeName)) {
|
||||
if (checkedNodes.includes(nodeName)) {
|
||||
// Node got checked already before
|
||||
return [];
|
||||
}
|
||||
|
||||
checkedNodes!.push(nodeName);
|
||||
checkedNodes.push(nodeName);
|
||||
|
||||
const returnNodes: string[] = [];
|
||||
let addNodes: string[];
|
||||
|
@ -597,7 +660,13 @@ export class Workflow {
|
|||
|
||||
returnNodes.unshift(connection.node);
|
||||
|
||||
addNodes = this.getConnectedNodes(connections, connection.node, type, newDepth, checkedNodes);
|
||||
addNodes = this.getConnectedNodes(
|
||||
connections,
|
||||
connection.node,
|
||||
type,
|
||||
newDepth,
|
||||
checkedNodes,
|
||||
);
|
||||
|
||||
for (i = addNodes.length; i--; i > 0) {
|
||||
// Because nodes can have multiple parents it is possible that
|
||||
|
@ -620,8 +689,6 @@ export class Workflow {
|
|||
return returnNodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns via which output of the parent-node the node
|
||||
* is connected to.
|
||||
|
@ -634,7 +701,13 @@ export class Workflow {
|
|||
* @returns {(number | undefined)}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getNodeConnectionOutputIndex(nodeName: string, parentNodeName: string, type = 'main', depth = -1, checkedNodes?: string[]): number | undefined {
|
||||
getNodeConnectionOutputIndex(
|
||||
nodeName: string,
|
||||
parentNodeName: string,
|
||||
type = 'main',
|
||||
depth = -1,
|
||||
checkedNodes?: string[],
|
||||
): number | undefined {
|
||||
const node = this.getNode(parentNodeName);
|
||||
if (node === null) {
|
||||
return undefined;
|
||||
|
@ -665,12 +738,12 @@ export class Workflow {
|
|||
|
||||
checkedNodes = checkedNodes || [];
|
||||
|
||||
if (checkedNodes!.includes(nodeName)) {
|
||||
if (checkedNodes.includes(nodeName)) {
|
||||
// Node got checked already before
|
||||
return undefined;
|
||||
}
|
||||
|
||||
checkedNodes!.push(nodeName);
|
||||
checkedNodes.push(nodeName);
|
||||
|
||||
let outputIndex: number | undefined;
|
||||
for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
|
||||
|
@ -679,12 +752,18 @@ export class Workflow {
|
|||
return connection.index;
|
||||
}
|
||||
|
||||
if (checkedNodes!.includes(connection.node)) {
|
||||
if (checkedNodes.includes(connection.node)) {
|
||||
// Node got checked already before so continue with the next one
|
||||
continue;
|
||||
}
|
||||
|
||||
outputIndex = this.getNodeConnectionOutputIndex(connection.node, parentNodeName, type, newDepth, checkedNodes);
|
||||
outputIndex = this.getNodeConnectionOutputIndex(
|
||||
connection.node,
|
||||
parentNodeName,
|
||||
type,
|
||||
newDepth,
|
||||
checkedNodes,
|
||||
);
|
||||
|
||||
if (outputIndex !== undefined) {
|
||||
return outputIndex;
|
||||
|
@ -695,9 +774,6 @@ export class Workflow {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns from which of the given nodes the workflow should get started from
|
||||
*
|
||||
|
@ -713,7 +789,6 @@ export class Workflow {
|
|||
node = this.nodes[nodeName];
|
||||
nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
||||
|
||||
|
||||
if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
|
||||
if (node.disabled === true) {
|
||||
continue;
|
||||
|
@ -734,8 +809,6 @@ export class Workflow {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the start node to start the worfklow from
|
||||
*
|
||||
|
@ -744,7 +817,6 @@ export class Workflow {
|
|||
* @memberof Workflow
|
||||
*/
|
||||
getStartNode(destinationNode?: string): INode | undefined {
|
||||
|
||||
if (destinationNode) {
|
||||
// Find the highest parent nodes of the given one
|
||||
const nodeNames = this.getHighestNode(destinationNode);
|
||||
|
@ -769,8 +841,6 @@ export class Workflow {
|
|||
return this.__getStartNode(Object.keys(this.nodes));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Executes the Webhooks method of the node
|
||||
*
|
||||
|
@ -781,11 +851,17 @@ export class Workflow {
|
|||
* @returns {(Promise<boolean | undefined>)}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
async runWebhookMethod(method: WebhookSetupMethodNames, webhookData: IWebhookData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, isTest?: boolean): Promise<boolean | undefined> {
|
||||
async runWebhookMethod(
|
||||
method: WebhookSetupMethodNames,
|
||||
webhookData: IWebhookData,
|
||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
isTest?: boolean,
|
||||
): Promise<boolean | undefined> {
|
||||
const node = this.getNode(webhookData.node) as INode;
|
||||
const nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
||||
|
||||
|
||||
if (nodeType.webhookMethods === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -798,11 +874,19 @@ export class Workflow {
|
|||
return;
|
||||
}
|
||||
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteHookFunctions(this, node, webhookData.workflowExecuteAdditionalData, mode, activation, isTest, webhookData);
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteHookFunctions(
|
||||
this,
|
||||
node,
|
||||
webhookData.workflowExecuteAdditionalData,
|
||||
mode,
|
||||
activation,
|
||||
isTest,
|
||||
webhookData,
|
||||
);
|
||||
// eslint-disable-next-line consistent-return
|
||||
return nodeType.webhookMethods[webhookData.webhookDescription.name][method]!.call(thisArgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the given trigger node so that it can trigger the workflow
|
||||
* when the node has data.
|
||||
|
@ -814,7 +898,13 @@ export class Workflow {
|
|||
* @returns {(Promise<ITriggerResponse | undefined>)}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
async runTrigger(node: INode, getTriggerFunctions: IGetExecuteTriggerFunctions, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<ITriggerResponse | undefined> {
|
||||
async runTrigger(
|
||||
node: INode,
|
||||
getTriggerFunctions: IGetExecuteTriggerFunctions,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): Promise<ITriggerResponse | undefined> {
|
||||
const triggerFunctions = getTriggerFunctions(this, node, additionalData, mode, activation);
|
||||
|
||||
const nodeType = this.nodeTypes.getByName(node.type);
|
||||
|
@ -824,28 +914,29 @@ export class Workflow {
|
|||
}
|
||||
|
||||
if (!nodeType.trigger) {
|
||||
throw new Error(`The node type "${node.type}" of node "${node.name}" does not have a trigger function defined.`);
|
||||
throw new Error(
|
||||
`The node type "${node.type}" of node "${node.name}" does not have a trigger function defined.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === 'manual') {
|
||||
// In manual mode we do not just start the trigger function we also
|
||||
// want to be able to get informed as soon as the first data got emitted
|
||||
const triggerResponse = await nodeType.trigger!.call(triggerFunctions);
|
||||
const triggerResponse = await nodeType.trigger.call(triggerFunctions);
|
||||
|
||||
// Add the manual trigger response which resolves when the first time data got emitted
|
||||
triggerResponse!.manualTriggerResponse = new Promise((resolve) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
triggerFunctions.emit = ((resolve) => (data: INodeExecutionData[][]) => {
|
||||
resolve(data);
|
||||
})(resolve);
|
||||
});
|
||||
|
||||
return triggerResponse;
|
||||
} else {
|
||||
}
|
||||
// In all other modes simply start the trigger
|
||||
return nodeType.trigger!.call(triggerFunctions);
|
||||
return nodeType.trigger.call(triggerFunctions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the given trigger node so that it can trigger the workflow
|
||||
|
@ -856,7 +947,10 @@ export class Workflow {
|
|||
* @returns
|
||||
* @memberof Workflow
|
||||
*/
|
||||
async runPoll(node: INode, pollFunctions: IPollFunctions): Promise<INodeExecutionData[][] | null> {
|
||||
async runPoll(
|
||||
node: INode,
|
||||
pollFunctions: IPollFunctions,
|
||||
): Promise<INodeExecutionData[][] | null> {
|
||||
const nodeType = this.nodeTypes.getByName(node.type);
|
||||
|
||||
if (nodeType === undefined) {
|
||||
|
@ -864,13 +958,14 @@ export class Workflow {
|
|||
}
|
||||
|
||||
if (!nodeType.poll) {
|
||||
throw new Error(`The node type "${node.type}" of node "${node.name}" does not have a poll function defined.`);
|
||||
throw new Error(
|
||||
`The node type "${node.type}" of node "${node.name}" does not have a poll function defined.`,
|
||||
);
|
||||
}
|
||||
|
||||
return nodeType.poll!.call(pollFunctions);
|
||||
return nodeType.poll.call(pollFunctions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the webhook data to see what it should return and if the
|
||||
* workflow should be started or not
|
||||
|
@ -882,7 +977,13 @@ export class Workflow {
|
|||
* @returns {Promise<IWebhookResponseData>}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
async runWebhook(webhookData: IWebhookData, node: INode, additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode): Promise<IWebhookResponseData> {
|
||||
async runWebhook(
|
||||
webhookData: IWebhookData,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||
mode: WorkflowExecuteMode,
|
||||
): Promise<IWebhookResponseData> {
|
||||
const nodeType = this.nodeTypes.getByName(node.type);
|
||||
if (nodeType === undefined) {
|
||||
throw new Error(`The type of the webhook node "${node.name}" is not known.`);
|
||||
|
@ -890,11 +991,16 @@ export class Workflow {
|
|||
throw new Error(`The node "${node.name}" does not have any webhooks defined.`);
|
||||
}
|
||||
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteWebhookFunctions(this, node, additionalData, mode, webhookData);
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteWebhookFunctions(
|
||||
this,
|
||||
node,
|
||||
additionalData,
|
||||
mode,
|
||||
webhookData,
|
||||
);
|
||||
return nodeType.webhook.call(thisArgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given node.
|
||||
*
|
||||
|
@ -908,7 +1014,15 @@ export class Workflow {
|
|||
* @returns {(Promise<INodeExecutionData[][] | null>)}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
async runNode(node: INode, inputData: ITaskDataConnections, runExecutionData: IRunExecutionData, runIndex: number, additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode): Promise<INodeExecutionData[][] | null | undefined> {
|
||||
async runNode(
|
||||
node: INode,
|
||||
inputData: ITaskDataConnections,
|
||||
runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||
mode: WorkflowExecuteMode,
|
||||
): Promise<INodeExecutionData[][] | null | undefined> {
|
||||
if (node.disabled === true) {
|
||||
// If node is disabled simply pass the data through
|
||||
// return NodeRunHelpers.
|
||||
|
@ -917,7 +1031,7 @@ export class Workflow {
|
|||
if (inputData.main[0] === null) {
|
||||
return undefined;
|
||||
}
|
||||
return [(inputData.main[0] as INodeExecutionData[])];
|
||||
return [inputData.main[0]];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -935,7 +1049,7 @@ export class Workflow {
|
|||
|
||||
if (inputData.hasOwnProperty('main') && inputData.main.length > 0) {
|
||||
// We always use the data of main input and the first input for executeSingle
|
||||
connectionInputData = (inputData.main[0] as INodeExecutionData[]);
|
||||
connectionInputData = inputData.main[0] as INodeExecutionData[];
|
||||
}
|
||||
|
||||
if (connectionInputData.length === 0) {
|
||||
|
@ -944,7 +1058,10 @@ export class Workflow {
|
|||
}
|
||||
}
|
||||
|
||||
if (runExecutionData.resultData.lastNodeExecuted === node.name && runExecutionData.resultData.error !== undefined) {
|
||||
if (
|
||||
runExecutionData.resultData.lastNodeExecuted === node.name &&
|
||||
runExecutionData.resultData.error !== undefined
|
||||
) {
|
||||
// The node did already fail. So throw an error here that it displays and logs it correctly.
|
||||
// Does get used by webhook and trigger nodes in case they throw an error that it is possible
|
||||
// to log the error and display in Editor-UI.
|
||||
|
@ -959,7 +1076,8 @@ export class Workflow {
|
|||
connectionInputData = connectionInputData.slice(0, 1);
|
||||
const newInputData: ITaskDataConnections = {};
|
||||
for (const inputName of Object.keys(inputData)) {
|
||||
newInputData[inputName] = inputData[inputName].map(input => {
|
||||
newInputData[inputName] = inputData[inputName].map((input) => {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
return input && input.slice(0, 1);
|
||||
});
|
||||
}
|
||||
|
@ -970,9 +1088,19 @@ export class Workflow {
|
|||
const returnPromises: Array<Promise<INodeExecutionData>> = [];
|
||||
|
||||
for (let itemIndex = 0; itemIndex < connectionInputData.length; itemIndex++) {
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, itemIndex, additionalData, mode);
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
|
||||
this,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
inputData,
|
||||
node,
|
||||
itemIndex,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
returnPromises.push(nodeType.executeSingle!.call(thisArgs));
|
||||
returnPromises.push(nodeType.executeSingle.call(thisArgs));
|
||||
}
|
||||
|
||||
if (returnPromises.length === 0) {
|
||||
|
@ -990,21 +1118,41 @@ export class Workflow {
|
|||
return [promiseResults];
|
||||
}
|
||||
} else if (nodeType.execute) {
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, additionalData, mode);
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteFunctions(
|
||||
this,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
inputData,
|
||||
node,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
return nodeType.execute.call(thisArgs);
|
||||
} else if (nodeType.poll) {
|
||||
if (mode === 'manual') {
|
||||
// In manual mode run the poll function
|
||||
const thisArgs = nodeExecuteFunctions.getExecutePollFunctions(this, node, additionalData, mode, 'manual');
|
||||
const thisArgs = nodeExecuteFunctions.getExecutePollFunctions(
|
||||
this,
|
||||
node,
|
||||
additionalData,
|
||||
mode,
|
||||
'manual',
|
||||
);
|
||||
return nodeType.poll.call(thisArgs);
|
||||
} else {
|
||||
}
|
||||
// In any other mode pass data through as it already contains the result of the poll
|
||||
return inputData.main as INodeExecutionData[][];
|
||||
}
|
||||
} else if (nodeType.trigger) {
|
||||
if (mode === 'manual') {
|
||||
// In manual mode start the trigger
|
||||
const triggerResponse = await this.runTrigger(node, nodeExecuteFunctions.getExecuteTriggerFunctions, additionalData, mode, 'manual');
|
||||
const triggerResponse = await this.runTrigger(
|
||||
node,
|
||||
nodeExecuteFunctions.getExecuteTriggerFunctions,
|
||||
additionalData,
|
||||
mode,
|
||||
'manual',
|
||||
);
|
||||
|
||||
if (triggerResponse === undefined) {
|
||||
return null;
|
||||
|
@ -1027,11 +1175,9 @@ export class Workflow {
|
|||
}
|
||||
|
||||
return response;
|
||||
} else {
|
||||
}
|
||||
// For trigger nodes in any mode except "manual" do we simply pass the data through
|
||||
return inputData.main as INodeExecutionData[][];
|
||||
}
|
||||
|
||||
} else if (nodeType.webhook) {
|
||||
// For webhook nodes always simply pass the data through
|
||||
return inputData.main as INodeExecutionData[][];
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue