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_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.ts]
|
|
||||||
quote_type = single
|
|
||||||
|
|
||||||
[*.yml]
|
[*.yml]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
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 bootstrap
|
||||||
npm run build --if-present
|
npm run build --if-present
|
||||||
npm test
|
npm test
|
||||||
npm run tslint
|
npm run lint
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -10,8 +10,8 @@ yarn.lock
|
||||||
google-generated-credentials.json
|
google-generated-credentials.json
|
||||||
_START_PACKAGE
|
_START_PACKAGE
|
||||||
.env
|
.env
|
||||||
.vscode
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
.idea
|
.idea
|
||||||
.prettierrc.js
|
|
||||||
vetur.config.js
|
vetur.config.js
|
||||||
nodelinter.config.json
|
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",
|
"build": "lerna exec npm run build",
|
||||||
"dev": "lerna exec npm run dev --parallel",
|
"dev": "lerna exec npm run dev --parallel",
|
||||||
"clean:dist": "lerna exec -- rimraf ./dist",
|
"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",
|
"optimize-svg": "find ./packages -name '*.svg' ! -name 'pipedrive.svg' -print0 | xargs -0 -P16 -L20 npx svgo",
|
||||||
"start": "run-script-os",
|
"start": "run-script-os",
|
||||||
"start:default": "cd packages/cli/bin && ./n8n",
|
"start:default": "cd packages/cli/bin && ./n8n",
|
||||||
"start:windows": "cd packages/cli/bin && n8n",
|
"start:windows": "cd packages/cli/bin && n8n",
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"tslint": "lerna exec npm run tslint",
|
|
||||||
"watch": "lerna run --parallel watch",
|
"watch": "lerna run --parallel watch",
|
||||||
"webhook": "./packages/cli/bin/n8n webhook",
|
"webhook": "./packages/cli/bin/n8n webhook",
|
||||||
"worker": "./packages/cli/bin/n8n worker"
|
"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 {
|
interface IResult {
|
||||||
totalWorkflows: number;
|
totalWorkflows: number;
|
||||||
summary: {
|
summary: {
|
||||||
failedExecutions: number,
|
failedExecutions: number;
|
||||||
successfulExecutions: number,
|
successfulExecutions: number;
|
||||||
warningExecutions: number,
|
warningExecutions: number;
|
||||||
errors: IExecutionError[],
|
errors: IExecutionError[];
|
||||||
warnings: IExecutionError[],
|
warnings: IExecutionError[];
|
||||||
};
|
};
|
||||||
coveredNodes: {
|
coveredNodes: {
|
||||||
[nodeType: string]: number
|
[nodeType: string]: number;
|
||||||
};
|
};
|
||||||
executions: IExecutionResult[];
|
executions: IExecutionResult[];
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ interface IExecutionResult {
|
||||||
error?: string;
|
error?: string;
|
||||||
changes?: string;
|
changes?: string;
|
||||||
coveredNodes: {
|
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 { promises as fs } from 'fs';
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
import {
|
import { UserSettings } from 'n8n-core';
|
||||||
UserSettings,
|
import { INode, LoggerProxy } from 'n8n-workflow';
|
||||||
} from 'n8n-core';
|
|
||||||
import {
|
|
||||||
INode,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
|
@ -17,26 +15,18 @@ import {
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
WorkflowCredentials,
|
WorkflowCredentials,
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
WorkflowRunner,
|
WorkflowRunner,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
import {
|
import { getLogger } from '../src/Logger';
|
||||||
getLogger,
|
|
||||||
} from '../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
export class Execute extends Command {
|
export class Execute extends Command {
|
||||||
static description = '\nExecutes a given workflow';
|
static description = '\nExecutes a given workflow';
|
||||||
|
|
||||||
static examples = [
|
static examples = [`$ n8n execute --id=5`, `$ n8n execute --file=workflow.json`];
|
||||||
`$ n8n execute --id=5`,
|
|
||||||
`$ n8n execute --file=workflow.json`,
|
|
||||||
];
|
|
||||||
|
|
||||||
static flags = {
|
static flags = {
|
||||||
help: flags.help({ char: 'h' }),
|
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() {
|
async run() {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(Execute);
|
const { flags } = this.parse(Execute);
|
||||||
|
|
||||||
// Start directly with the init of the database to improve startup time
|
// 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 workflowId: string | undefined;
|
||||||
let workflowData: IWorkflowBase | undefined = undefined;
|
let workflowData: IWorkflowBase | undefined;
|
||||||
if (flags.file) {
|
if (flags.file) {
|
||||||
// Path to workflow is given
|
// Path to workflow is given
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
workflowData = JSON.parse(await fs.readFile(flags.file, 'utf8'));
|
workflowData = JSON.parse(await fs.readFile(flags.file, 'utf8'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
console.info(`The file "${flags.file}" could not be found.`);
|
console.info(`The file "${flags.file}" could not be found.`);
|
||||||
return;
|
return;
|
||||||
|
@ -92,10 +85,15 @@ export class Execute extends Command {
|
||||||
|
|
||||||
// Do a basic check if the data in the file looks right
|
// 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
|
// 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.`);
|
console.info(`The file "${flags.file}" does not contain valid workflow data.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
workflowId = workflowData.id!.toString();
|
workflowId = workflowData.id!.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +103,8 @@ export class Execute extends Command {
|
||||||
if (flags.id) {
|
if (flags.id) {
|
||||||
// Id of workflow is given
|
// Id of workflow is given
|
||||||
workflowId = flags.id;
|
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) {
|
if (workflowData === undefined) {
|
||||||
console.info(`The workflow with the id "${workflowId}" does not exist.`);
|
console.info(`The workflow with the id "${workflowId}" does not exist.`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -139,7 +138,8 @@ export class Execute extends Command {
|
||||||
// Check if the workflow contains the required "Start" node
|
// Check if the workflow contains the required "Start" node
|
||||||
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
||||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
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) {
|
for (const node of workflowData!.nodes) {
|
||||||
if (requiredNodeTypes.includes(node.type)) {
|
if (requiredNodeTypes.includes(node.type)) {
|
||||||
startNode = node;
|
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
|
// If the workflow does not contain a start-node we can not know what
|
||||||
// should be executed and with which data to start.
|
// 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.`);
|
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();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,6 +159,7 @@ export class Execute extends Command {
|
||||||
const runData: IWorkflowExecutionDataProcess = {
|
const runData: IWorkflowExecutionDataProcess = {
|
||||||
executionMode: 'cli',
|
executionMode: 'cli',
|
||||||
startNodes: [startNode.name],
|
startNodes: [startNode.name],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
workflowData: workflowData!,
|
workflowData: workflowData!,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -178,6 +180,7 @@ export class Execute extends Command {
|
||||||
logger.info(JSON.stringify(data, null, 2));
|
logger.info(JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
const { error } = data.data.resultData;
|
const { error } = data.data.resultData;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||||
throw {
|
throw {
|
||||||
...error,
|
...error,
|
||||||
stack: error.stack,
|
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 * as fs from 'fs';
|
||||||
import {
|
import { Command, flags } from '@oclif/command';
|
||||||
Command,
|
|
||||||
flags,
|
|
||||||
} from '@oclif/command';
|
|
||||||
|
|
||||||
import {
|
import { UserSettings } from 'n8n-core';
|
||||||
UserSettings,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
INode,
|
import { INode, INodeExecutionData, ITaskData, LoggerProxy } from 'n8n-workflow';
|
||||||
INodeExecutionData,
|
|
||||||
ITaskData,
|
import { sep } from 'path';
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
import { diff } from 'json-diff';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
import { getLogger } from '../src/Logger';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
|
@ -20,35 +28,17 @@ import {
|
||||||
CredentialTypes,
|
CredentialTypes,
|
||||||
Db,
|
Db,
|
||||||
ExternalHooks,
|
ExternalHooks,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
IExecutionsCurrentSummary,
|
IExecutionsCurrentSummary,
|
||||||
IWorkflowDb,
|
IWorkflowDb,
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
WorkflowCredentials,
|
WorkflowCredentials,
|
||||||
WorkflowRunner,
|
WorkflowRunner,
|
||||||
} from '../src';
|
} 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 {
|
export class ExecuteBatch extends Command {
|
||||||
static description = '\nExecutes multiple workflows once';
|
static description = '\nExecutes multiple workflows once';
|
||||||
|
|
||||||
|
@ -87,19 +77,24 @@ export class ExecuteBatch extends Command {
|
||||||
}),
|
}),
|
||||||
concurrency: flags.integer({
|
concurrency: flags.integer({
|
||||||
default: 1,
|
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({
|
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({
|
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({
|
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({
|
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({
|
skipList: flags.string({
|
||||||
description: 'File containing a comma separated list of workflow IDs to skip.',
|
description: 'File containing a comma separated list of workflow IDs to skip.',
|
||||||
|
@ -117,15 +112,16 @@ export class ExecuteBatch extends Command {
|
||||||
* Gracefully handles exit.
|
* Gracefully handles exit.
|
||||||
* @param {boolean} skipExit Whether to skip exit or number according to received signal
|
* @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) {
|
static async stopProcess(skipExit: boolean | number = false) {
|
||||||
|
if (ExecuteBatch.cancelled) {
|
||||||
if (ExecuteBatch.cancelled === true) {
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExecuteBatch.cancelled = true;
|
ExecuteBatch.cancelled = true;
|
||||||
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
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);
|
activeExecutionsInstance.stopExecution(execution.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -135,16 +131,17 @@ export class ExecuteBatch extends Command {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
|
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
while (executingWorkflows.length !== 0) {
|
while (executingWorkflows.length !== 0) {
|
||||||
if (count++ % 4 === 0) {
|
if (count++ % 4 === 0) {
|
||||||
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
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}`);
|
console.log(` - Execution ID ${execution.id}, workflow ID: ${execution.workflowId}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 500);
|
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) {
|
formatJsonOutput(data: object) {
|
||||||
return JSON.stringify(data, null, 2);
|
return JSON.stringify(data, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
shouldBeConsideredAsWarning(errorMessage: string) {
|
shouldBeConsideredAsWarning(errorMessage: string) {
|
||||||
|
|
||||||
const warningStrings = [
|
const warningStrings = [
|
||||||
'refresh token is invalid',
|
'refresh token is invalid',
|
||||||
'unable to connect to',
|
'unable to connect to',
|
||||||
|
@ -174,6 +172,7 @@ export class ExecuteBatch extends Command {
|
||||||
'request timed out',
|
'request timed out',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
errorMessage = errorMessage.toLowerCase();
|
errorMessage = errorMessage.toLowerCase();
|
||||||
|
|
||||||
for (let i = 0; i < warningStrings.length; i++) {
|
for (let i = 0; i < warningStrings.length; i++) {
|
||||||
|
@ -185,18 +184,18 @@ export class ExecuteBatch extends Command {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
async run() {
|
async run() {
|
||||||
|
|
||||||
process.on('SIGTERM', ExecuteBatch.stopProcess);
|
process.on('SIGTERM', ExecuteBatch.stopProcess);
|
||||||
process.on('SIGINT', ExecuteBatch.stopProcess);
|
process.on('SIGINT', ExecuteBatch.stopProcess);
|
||||||
|
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(ExecuteBatch);
|
const { flags } = this.parse(ExecuteBatch);
|
||||||
|
|
||||||
ExecuteBatch.debug = flags.debug === true;
|
ExecuteBatch.debug = flags.debug;
|
||||||
ExecuteBatch.concurrency = flags.concurrency || 1;
|
ExecuteBatch.concurrency = flags.concurrency || 1;
|
||||||
|
|
||||||
const ids: number[] = [];
|
const ids: number[] = [];
|
||||||
|
@ -241,7 +240,7 @@ export class ExecuteBatch extends Command {
|
||||||
if (flags.ids !== undefined) {
|
if (flags.ids !== undefined) {
|
||||||
const paramIds = flags.ids.split(',');
|
const paramIds = flags.ids.split(',');
|
||||||
const re = /\d+/;
|
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) {
|
if (matchedIds.length === 0) {
|
||||||
console.log(`The parameter --ids must be a list of numeric IDs separated by a comma.`);
|
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 (flags.skipList !== undefined) {
|
||||||
if (fs.existsSync(flags.skipList)) {
|
if (fs.existsSync(flags.skipList)) {
|
||||||
const contents = fs.readFileSync(flags.skipList, { encoding: 'utf-8' });
|
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 {
|
} else {
|
||||||
console.log('Skip list file not found. Exiting.');
|
console.log('Skip list file not found. Exiting.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.shallow === true) {
|
if (flags.shallow) {
|
||||||
ExecuteBatch.shallow = true;
|
ExecuteBatch.shallow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Start directly with the init of the database to improve startup time
|
// Start directly with the init of the database to improve startup time
|
||||||
const startDbInitPromise = Db.init();
|
const startDbInitPromise = Db.init();
|
||||||
|
|
||||||
|
@ -281,7 +279,7 @@ export class ExecuteBatch extends Command {
|
||||||
|
|
||||||
let allWorkflows;
|
let allWorkflows;
|
||||||
|
|
||||||
const query = Db.collections!.Workflow!.createQueryBuilder('workflows');
|
const query = Db.collections.Workflow!.createQueryBuilder('workflows');
|
||||||
|
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
query.andWhere(`workflows.id in (:...ids)`, { ids });
|
query.andWhere(`workflows.id in (:...ids)`, { ids });
|
||||||
|
@ -291,9 +289,10 @@ export class ExecuteBatch extends Command {
|
||||||
query.andWhere(`workflows.id not in (:...skipIds)`, { skipIds });
|
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`);
|
process.stdout.write(`Found ${allWorkflows.length} workflows to execute.\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,12 +317,19 @@ export class ExecuteBatch extends Command {
|
||||||
|
|
||||||
let { retries } = flags;
|
let { retries } = flags;
|
||||||
|
|
||||||
while (retries > 0 && (results.summary.warningExecutions + results.summary.failedExecutions > 0) && ExecuteBatch.cancelled === false) {
|
while (
|
||||||
const failedWorkflowIds = results.summary.errors.map(execution => execution.workflowId);
|
retries > 0 &&
|
||||||
failedWorkflowIds.push(...results.summary.warnings.map(execution => execution.workflowId));
|
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);
|
const retryResults = await this.runTests(newWorkflowList);
|
||||||
|
|
||||||
this.mergeResults(results, retryResults);
|
this.mergeResults(results, retryResults);
|
||||||
|
@ -343,13 +349,18 @@ export class ExecuteBatch extends Command {
|
||||||
console.log(`\t${nodeName}: ${nodeCount}`);
|
console.log(`\t${nodeName}: ${nodeCount}`);
|
||||||
});
|
});
|
||||||
console.log('\nCheck the JSON file for more details.');
|
console.log('\nCheck the JSON file for more details.');
|
||||||
} else {
|
} else if (flags.shortOutput) {
|
||||||
if (flags.shortOutput === true) {
|
console.log(
|
||||||
console.log(this.formatJsonOutput({ ...results, executions: results.executions.filter(execution => execution.executionStatus !== 'success') }));
|
this.formatJsonOutput({
|
||||||
|
...results,
|
||||||
|
executions: results.executions.filter(
|
||||||
|
(execution) => execution.executionStatus !== 'success',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(this.formatJsonOutput(results));
|
console.log(this.formatJsonOutput(results));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await ExecuteBatch.stopProcess(true);
|
await ExecuteBatch.stopProcess(true);
|
||||||
|
|
||||||
|
@ -357,23 +368,26 @@ export class ExecuteBatch extends Command {
|
||||||
this.exit(1);
|
this.exit(1);
|
||||||
}
|
}
|
||||||
this.exit(0);
|
this.exit(0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
mergeResults(results: IResult, retryResults: IResult) {
|
mergeResults(results: IResult, retryResults: IResult) {
|
||||||
|
|
||||||
if (retryResults.summary.successfulExecutions === 0) {
|
if (retryResults.summary.successfulExecutions === 0) {
|
||||||
// Nothing to replace.
|
// Nothing to replace.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find successful executions and replace them on previous result.
|
// Find successful executions and replace them on previous result.
|
||||||
retryResults.executions.forEach(newExecution => {
|
retryResults.executions.forEach((newExecution) => {
|
||||||
if (newExecution.executionStatus === 'success') {
|
if (newExecution.executionStatus === 'success') {
|
||||||
// Remove previous execution from list.
|
// 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) {
|
if (errorIndex !== -1) {
|
||||||
// This workflow errored previously. Decrement error count.
|
// This workflow errored previously. Decrement error count.
|
||||||
results.summary.failedExecutions--;
|
results.summary.failedExecutions--;
|
||||||
|
@ -381,7 +395,9 @@ export class ExecuteBatch extends Command {
|
||||||
results.summary.errors.splice(errorIndex, 1);
|
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) {
|
if (warningIndex !== -1) {
|
||||||
// This workflow errored previously. Decrement error count.
|
// This workflow errored previously. Decrement error count.
|
||||||
results.summary.warningExecutions--;
|
results.summary.warningExecutions--;
|
||||||
|
@ -420,7 +436,7 @@ export class ExecuteBatch extends Command {
|
||||||
let workflow: IWorkflowDb | undefined;
|
let workflow: IWorkflowDb | undefined;
|
||||||
while (allWorkflows.length > 0) {
|
while (allWorkflows.length > 0) {
|
||||||
workflow = allWorkflows.shift();
|
workflow = allWorkflows.shift();
|
||||||
if (ExecuteBatch.cancelled === true) {
|
if (ExecuteBatch.cancelled) {
|
||||||
process.stdout.write(`Thread ${i + 1} resolving and quitting.`);
|
process.stdout.write(`Thread ${i + 1} resolving and quitting.`);
|
||||||
resolve(true);
|
resolve(true);
|
||||||
break;
|
break;
|
||||||
|
@ -440,6 +456,7 @@ export class ExecuteBatch extends Command {
|
||||||
this.updateStatus();
|
this.updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
await this.startThread(workflow).then((executionResult) => {
|
await this.startThread(workflow).then((executionResult) => {
|
||||||
if (ExecuteBatch.debug) {
|
if (ExecuteBatch.debug) {
|
||||||
ExecuteBatch.workflowExecutionsProgress[i].pop();
|
ExecuteBatch.workflowExecutionsProgress[i].pop();
|
||||||
|
@ -456,7 +473,7 @@ export class ExecuteBatch extends Command {
|
||||||
result.summary.successfulExecutions++;
|
result.summary.successfulExecutions++;
|
||||||
const nodeNames = Object.keys(executionResult.coveredNodes);
|
const nodeNames = Object.keys(executionResult.coveredNodes);
|
||||||
|
|
||||||
nodeNames.map(nodeName => {
|
nodeNames.map((nodeName) => {
|
||||||
if (result.coveredNodes[nodeName] === undefined) {
|
if (result.coveredNodes[nodeName] === undefined) {
|
||||||
result.coveredNodes[nodeName] = 0;
|
result.coveredNodes[nodeName] = 0;
|
||||||
}
|
}
|
||||||
|
@ -506,19 +523,18 @@ export class ExecuteBatch extends Command {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
updateStatus() {
|
updateStatus() {
|
||||||
|
if (ExecuteBatch.cancelled) {
|
||||||
if (ExecuteBatch.cancelled === true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.stdout.isTTY === true) {
|
if (process.stdout.isTTY) {
|
||||||
process.stdout.moveCursor(0, - (ExecuteBatch.concurrency));
|
process.stdout.moveCursor(0, -ExecuteBatch.concurrency);
|
||||||
process.stdout.cursorTo(0);
|
process.stdout.cursorTo(0);
|
||||||
process.stdout.clearLine(0);
|
process.stdout.clearLine(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ExecuteBatch.workflowExecutionsProgress.map((concurrentThread, index) => {
|
ExecuteBatch.workflowExecutionsProgress.map((concurrentThread, index) => {
|
||||||
let message = `${index + 1}: `;
|
let message = `${index + 1}: `;
|
||||||
concurrentThread.map((executionItem, workflowIndex) => {
|
concurrentThread.map((executionItem, workflowIndex) => {
|
||||||
|
@ -537,16 +553,19 @@ export class ExecuteBatch extends Command {
|
||||||
default:
|
default:
|
||||||
break;
|
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.cursorTo(0);
|
||||||
process.stdout.clearLine(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() {
|
initializeLogs() {
|
||||||
process.stdout.write('**********************************************\n');
|
process.stdout.write('**********************************************\n');
|
||||||
process.stdout.write(' n8n test workflows\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.
|
// This will be the object returned by the promise.
|
||||||
// It will be updated according to execution progress below.
|
// It will be updated according to execution progress below.
|
||||||
const executionResult: IExecutionResult = {
|
const executionResult: IExecutionResult = {
|
||||||
|
@ -572,10 +591,9 @@ export class ExecuteBatch extends Command {
|
||||||
coveredNodes: {},
|
coveredNodes: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
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) {
|
for (const node of workflowData.nodes) {
|
||||||
if (requiredNodeTypes.includes(node.type)) {
|
if (requiredNodeTypes.includes(node.type)) {
|
||||||
startNode = node;
|
startNode = node;
|
||||||
|
@ -593,10 +611,10 @@ export class ExecuteBatch extends Command {
|
||||||
// properties from the JSON object (useful for optional properties that can
|
// properties from the JSON object (useful for optional properties that can
|
||||||
// cause the comparison to detect changes when not true).
|
// cause the comparison to detect changes when not true).
|
||||||
const nodeEdgeCases = {} as INodeSpecialCases;
|
const nodeEdgeCases = {} as INodeSpecialCases;
|
||||||
workflowData.nodes.forEach(node => {
|
workflowData.nodes.forEach((node) => {
|
||||||
executionResult.coveredNodes[node.type] = (executionResult.coveredNodes[node.type] || 0) + 1;
|
executionResult.coveredNodes[node.type] = (executionResult.coveredNodes[node.type] || 0) + 1;
|
||||||
if (node.notes !== undefined && node.notes !== '') {
|
if (node.notes !== undefined && node.notes !== '') {
|
||||||
node.notes.split('\n').forEach(note => {
|
node.notes.split('\n').forEach((note) => {
|
||||||
const parts = note.split('=');
|
const parts = note.split('=');
|
||||||
if (parts.length === 2) {
|
if (parts.length === 2) {
|
||||||
if (nodeEdgeCases[node.name] === undefined) {
|
if (nodeEdgeCases[node.name] === undefined) {
|
||||||
|
@ -605,9 +623,13 @@ export class ExecuteBatch extends Command {
|
||||||
if (parts[0] === 'CAP_RESULTS_LENGTH') {
|
if (parts[0] === 'CAP_RESULTS_LENGTH') {
|
||||||
nodeEdgeCases[node.name].capResults = parseInt(parts[1], 10);
|
nodeEdgeCases[node.name].capResults = parseInt(parts[1], 10);
|
||||||
} else if (parts[0] === 'IGNORED_PROPERTIES') {
|
} 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') {
|
} 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);
|
resolve(executionResult);
|
||||||
}, ExecuteBatch.executionTimeout);
|
}, ExecuteBatch.executionTimeout);
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const runData: IWorkflowExecutionDataProcess = {
|
const runData: IWorkflowExecutionDataProcess = {
|
||||||
executionMode: 'cli',
|
executionMode: 'cli',
|
||||||
startNodes: [startNode!.name],
|
startNodes: [startNode!.name],
|
||||||
workflowData: workflowData!,
|
workflowData,
|
||||||
};
|
};
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunner();
|
const workflowRunner = new WorkflowRunner();
|
||||||
|
@ -647,7 +667,7 @@ export class ExecuteBatch extends Command {
|
||||||
|
|
||||||
const activeExecutions = ActiveExecutions.getInstance();
|
const activeExecutions = ActiveExecutions.getInstance();
|
||||||
const data = await activeExecutions.getPostExecutePromise(executionId);
|
const data = await activeExecutions.getPostExecutePromise(executionId);
|
||||||
if (gotCancel || ExecuteBatch.cancelled === true) {
|
if (gotCancel || ExecuteBatch.cancelled) {
|
||||||
clearTimeout(timeoutTimer);
|
clearTimeout(timeoutTimer);
|
||||||
// The promise was settled already so we simply ignore.
|
// The promise was settled already so we simply ignore.
|
||||||
return;
|
return;
|
||||||
|
@ -657,14 +677,18 @@ export class ExecuteBatch extends Command {
|
||||||
executionResult.error = 'Workflow did not return any data.';
|
executionResult.error = 'Workflow did not return any data.';
|
||||||
executionResult.executionStatus = 'error';
|
executionResult.executionStatus = 'error';
|
||||||
} else {
|
} else {
|
||||||
executionResult.executionTime = (Date.parse(data.stoppedAt as unknown as string) - Date.parse(data.startedAt as unknown as string)) / 1000;
|
executionResult.executionTime =
|
||||||
executionResult.finished = (data?.finished !== undefined) as boolean;
|
(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) {
|
if (data.data.resultData.error) {
|
||||||
executionResult.error =
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-prototype-builtins
|
||||||
data.data.resultData.error.hasOwnProperty('description') ?
|
executionResult.error = data.data.resultData.error.hasOwnProperty('description')
|
||||||
// @ts-ignore
|
? // @ts-ignore
|
||||||
data.data.resultData.error.description : data.data.resultData.error.message;
|
data.data.resultData.error.description
|
||||||
|
: data.data.resultData.error.message;
|
||||||
if (data.data.resultData.lastNodeExecuted !== undefined) {
|
if (data.data.resultData.lastNodeExecuted !== undefined) {
|
||||||
executionResult.error += ` on node ${data.data.resultData.lastNodeExecuted}`;
|
executionResult.error += ` on node ${data.data.resultData.lastNodeExecuted}`;
|
||||||
}
|
}
|
||||||
|
@ -674,7 +698,7 @@ export class ExecuteBatch extends Command {
|
||||||
executionResult.executionStatus = 'warning';
|
executionResult.executionStatus = 'warning';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ExecuteBatch.shallow === true) {
|
if (ExecuteBatch.shallow) {
|
||||||
// What this does is guarantee that top-level attributes
|
// What this does is guarantee that top-level attributes
|
||||||
// from the JSON are kept and the are the same type.
|
// from the JSON are kept and the are the same type.
|
||||||
|
|
||||||
|
@ -688,34 +712,48 @@ export class ExecuteBatch extends Command {
|
||||||
if (taskData.data === undefined) {
|
if (taskData.data === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object.keys(taskData.data).map(connectionName => {
|
Object.keys(taskData.data).map((connectionName) => {
|
||||||
const connection = taskData.data![connectionName] as Array<INodeExecutionData[] | null>;
|
const connection = taskData.data![connectionName];
|
||||||
connection.map(executionDataArray => {
|
connection.map((executionDataArray) => {
|
||||||
if (executionDataArray === null) {
|
if (executionDataArray === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].capResults !== undefined) {
|
if (
|
||||||
|
nodeEdgeCases[nodeName] !== undefined &&
|
||||||
|
nodeEdgeCases[nodeName].capResults !== undefined
|
||||||
|
) {
|
||||||
executionDataArray.splice(nodeEdgeCases[nodeName].capResults!);
|
executionDataArray.splice(nodeEdgeCases[nodeName].capResults!);
|
||||||
}
|
}
|
||||||
|
|
||||||
executionDataArray.map(executionData => {
|
executionDataArray.map((executionData) => {
|
||||||
if (executionData.json === undefined) {
|
if (executionData.json === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].ignoredProperties !== undefined) {
|
if (
|
||||||
nodeEdgeCases[nodeName].ignoredProperties!.forEach(ignoredProperty => delete executionData.json[ignoredProperty]);
|
nodeEdgeCases[nodeName] !== undefined &&
|
||||||
|
nodeEdgeCases[nodeName].ignoredProperties !== undefined
|
||||||
|
) {
|
||||||
|
nodeEdgeCases[nodeName].ignoredProperties!.forEach(
|
||||||
|
(ignoredProperty) => delete executionData.json[ignoredProperty],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let keepOnlyFields = [] as string[];
|
let keepOnlyFields = [] as string[];
|
||||||
if (nodeEdgeCases[nodeName] !== undefined && nodeEdgeCases[nodeName].keepOnlyProperties !== undefined) {
|
if (
|
||||||
|
nodeEdgeCases[nodeName] !== undefined &&
|
||||||
|
nodeEdgeCases[nodeName].keepOnlyProperties !== undefined
|
||||||
|
) {
|
||||||
keepOnlyFields = nodeEdgeCases[nodeName].keepOnlyProperties!;
|
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 jsonProperties = executionData.json;
|
||||||
|
|
||||||
const nodeOutputAttributes = Object.keys(jsonProperties);
|
const nodeOutputAttributes = Object.keys(jsonProperties);
|
||||||
nodeOutputAttributes.map(attributeName => {
|
nodeOutputAttributes.map((attributeName) => {
|
||||||
if (Array.isArray(jsonProperties[attributeName])) {
|
if (Array.isArray(jsonProperties[attributeName])) {
|
||||||
jsonProperties[attributeName] = ['json array'];
|
jsonProperties[attributeName] = ['json array'];
|
||||||
} else if (typeof jsonProperties[attributeName] === 'object') {
|
} 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.
|
// If not using shallow comparison then we only treat nodeEdgeCases.
|
||||||
const specialCases = Object.keys(nodeEdgeCases);
|
const specialCases = Object.keys(nodeEdgeCases);
|
||||||
|
|
||||||
specialCases.forEach(nodeName => {
|
specialCases.forEach((nodeName) => {
|
||||||
data.data.resultData.runData[nodeName].map((taskData: ITaskData) => {
|
data.data.resultData.runData[nodeName].map((taskData: ITaskData) => {
|
||||||
if (taskData.data === undefined) {
|
if (taskData.data === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object.keys(taskData.data).map(connectionName => {
|
Object.keys(taskData.data).map((connectionName) => {
|
||||||
const connection = taskData.data![connectionName] as Array<INodeExecutionData[] | null>;
|
const connection = taskData.data![connectionName];
|
||||||
connection.map(executionDataArray => {
|
connection.map((executionDataArray) => {
|
||||||
if (executionDataArray === null) {
|
if (executionDataArray === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -749,15 +786,16 @@ export class ExecuteBatch extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeEdgeCases[nodeName].ignoredProperties !== undefined) {
|
if (nodeEdgeCases[nodeName].ignoredProperties !== undefined) {
|
||||||
executionDataArray.map(executionData => {
|
executionDataArray.map((executionData) => {
|
||||||
if (executionData.json === undefined) {
|
if (executionData.json === undefined) {
|
||||||
return;
|
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) {
|
if (ExecuteBatch.compare === undefined) {
|
||||||
executionResult.executionStatus = 'success';
|
executionResult.executionStatus = 'success';
|
||||||
} else {
|
} else {
|
||||||
const fileName = (ExecuteBatch.compare.endsWith(sep) ? ExecuteBatch.compare : ExecuteBatch.compare + sep) + `${workflowData.id}-snapshot.json`;
|
const fileName = `${
|
||||||
if (fs.existsSync(fileName) === true) {
|
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 contents = fs.readFileSync(fileName, { encoding: 'utf-8' });
|
||||||
|
|
||||||
const changes = diff(JSON.parse(contents), data, { keysOnly: true });
|
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
|
// Save snapshots only after comparing - this is to make sure we're updating
|
||||||
// After comparing to existing verion.
|
// After comparing to existing verion.
|
||||||
if (ExecuteBatch.snapshot !== undefined) {
|
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);
|
fs.writeFileSync(fileName, serializedData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -803,5 +848,4 @@ export class ExecuteBatch extends Command {
|
||||||
resolve(executionResult);
|
resolve(executionResult);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,16 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
Command,
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
flags,
|
/* eslint-disable no-console */
|
||||||
} from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
|
|
||||||
import {
|
import { Credentials, UserSettings } from 'n8n-core';
|
||||||
Credentials,
|
|
||||||
UserSettings,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||||
IDataObject
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Db,
|
|
||||||
ICredentialsDecryptedDb,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getLogger,
|
|
||||||
} from '../../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { getLogger } from '../../src/Logger';
|
||||||
|
import { Db, ICredentialsDecryptedDb } from '../../src';
|
||||||
|
|
||||||
export class ExportCredentialsCommand extends Command {
|
export class ExportCredentialsCommand extends Command {
|
||||||
static description = 'Export credentials';
|
static description = 'Export credentials';
|
||||||
|
@ -45,7 +29,8 @@ export class ExportCredentialsCommand extends Command {
|
||||||
description: 'Export all credentials',
|
description: 'Export all credentials',
|
||||||
}),
|
}),
|
||||||
backup: flags.boolean({
|
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({
|
id: flags.string({
|
||||||
description: 'The ID of the credential to export',
|
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',
|
description: 'Format the output in an easier to read fashion',
|
||||||
}),
|
}),
|
||||||
separate: flags.boolean({
|
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({
|
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() {
|
async run() {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(ExportCredentialsCommand);
|
const { flags } = this.parse(ExportCredentialsCommand);
|
||||||
|
|
||||||
if (flags.backup) {
|
if (flags.backup) {
|
||||||
|
@ -103,7 +92,9 @@ export class ExportCredentialsCommand extends Command {
|
||||||
fs.mkdirSync(flags.output, { recursive: true });
|
fs.mkdirSync(flags.output, { recursive: true });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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.error('\nFILESYSTEM ERROR');
|
||||||
logger.info('====================================');
|
logger.info('====================================');
|
||||||
logger.error(e.message);
|
logger.error(e.message);
|
||||||
|
@ -127,6 +118,7 @@ export class ExportCredentialsCommand extends Command {
|
||||||
findQuery.id = flags.id;
|
findQuery.id = flags.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const credentials = await Db.collections.Credentials!.find(findQuery);
|
const credentials = await Db.collections.Credentials!.find(findQuery);
|
||||||
|
|
||||||
if (flags.decrypted) {
|
if (flags.decrypted) {
|
||||||
|
@ -148,17 +140,22 @@ export class ExportCredentialsCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.separate) {
|
if (flags.separate) {
|
||||||
let fileContents: string, i: number;
|
let fileContents: string;
|
||||||
|
let i: number;
|
||||||
for (i = 0; i < credentials.length; i++) {
|
for (i = 0; i < credentials.length; i++) {
|
||||||
fileContents = JSON.stringify(credentials[i], null, flags.pretty ? 2 : undefined);
|
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);
|
fs.writeFileSync(filename, fileContents);
|
||||||
}
|
}
|
||||||
console.info(`Successfully exported ${i} credentials.`);
|
console.info(`Successfully exported ${i} credentials.`);
|
||||||
} else {
|
} else {
|
||||||
const fileContents = JSON.stringify(credentials, null, flags.pretty ? 2 : undefined);
|
const fileContents = JSON.stringify(credentials, null, flags.pretty ? 2 : undefined);
|
||||||
if (flags.output) {
|
if (flags.output) {
|
||||||
fs.writeFileSync(flags.output!, fileContents);
|
fs.writeFileSync(flags.output, fileContents);
|
||||||
console.info(`Successfully exported ${credentials.length} credentials.`);
|
console.info(`Successfully exported ${credentials.length} credentials.`);
|
||||||
} else {
|
} else {
|
||||||
console.info(fileContents);
|
console.info(fileContents);
|
||||||
|
|
|
@ -1,26 +1,13 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
Command,
|
/* eslint-disable no-console */
|
||||||
flags,
|
import { Command, flags } from '@oclif/command';
|
||||||
} from '@oclif/command';
|
|
||||||
|
|
||||||
import {
|
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||||
IDataObject
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Db,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getLogger,
|
|
||||||
} from '../../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { getLogger } from '../../src/Logger';
|
||||||
|
import { Db } from '../../src';
|
||||||
|
|
||||||
export class ExportWorkflowsCommand extends Command {
|
export class ExportWorkflowsCommand extends Command {
|
||||||
static description = 'Export workflows';
|
static description = 'Export workflows';
|
||||||
|
@ -38,7 +25,8 @@ export class ExportWorkflowsCommand extends Command {
|
||||||
description: 'Export all workflows',
|
description: 'Export all workflows',
|
||||||
}),
|
}),
|
||||||
backup: flags.boolean({
|
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({
|
id: flags.string({
|
||||||
description: 'The ID of the workflow to export',
|
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',
|
description: 'Format the output in an easier to read fashion',
|
||||||
}),
|
}),
|
||||||
separate: flags.boolean({
|
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() {
|
async run() {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(ExportWorkflowsCommand);
|
const { flags } = this.parse(ExportWorkflowsCommand);
|
||||||
|
|
||||||
if (flags.backup) {
|
if (flags.backup) {
|
||||||
|
@ -93,7 +84,9 @@ export class ExportWorkflowsCommand extends Command {
|
||||||
fs.mkdirSync(flags.output, { recursive: true });
|
fs.mkdirSync(flags.output, { recursive: true });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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.error('\nFILESYSTEM ERROR');
|
||||||
logger.info('====================================');
|
logger.info('====================================');
|
||||||
logger.error(e.message);
|
logger.error(e.message);
|
||||||
|
@ -117,6 +110,7 @@ export class ExportWorkflowsCommand extends Command {
|
||||||
findQuery.id = flags.id;
|
findQuery.id = flags.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const workflows = await Db.collections.Workflow!.find(findQuery);
|
const workflows = await Db.collections.Workflow!.find(findQuery);
|
||||||
|
|
||||||
if (workflows.length === 0) {
|
if (workflows.length === 0) {
|
||||||
|
@ -124,18 +118,27 @@ export class ExportWorkflowsCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.separate) {
|
if (flags.separate) {
|
||||||
let fileContents: string, i: number;
|
let fileContents: string;
|
||||||
|
let i: number;
|
||||||
for (i = 0; i < workflows.length; i++) {
|
for (i = 0; i < workflows.length; i++) {
|
||||||
fileContents = JSON.stringify(workflows[i], null, flags.pretty ? 2 : undefined);
|
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);
|
fs.writeFileSync(filename, fileContents);
|
||||||
}
|
}
|
||||||
console.info(`Successfully exported ${i} workflows.`);
|
console.info(`Successfully exported ${i} workflows.`);
|
||||||
} else {
|
} else {
|
||||||
const fileContents = JSON.stringify(workflows, null, flags.pretty ? 2 : undefined);
|
const fileContents = JSON.stringify(workflows, null, flags.pretty ? 2 : undefined);
|
||||||
if (flags.output) {
|
if (flags.output) {
|
||||||
fs.writeFileSync(flags.output!, fileContents);
|
fs.writeFileSync(flags.output, fileContents);
|
||||||
console.info(`Successfully exported ${workflows.length} ${workflows.length === 1 ? 'workflow.' : 'workflows.'}`);
|
console.info(
|
||||||
|
`Successfully exported ${workflows.length} ${
|
||||||
|
workflows.length === 1 ? 'workflow.' : 'workflows.'
|
||||||
|
}`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.info(fileContents);
|
console.info(fileContents);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,16 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
Command,
|
/* eslint-disable no-console */
|
||||||
flags,
|
import { Command, flags } from '@oclif/command';
|
||||||
} from '@oclif/command';
|
|
||||||
|
|
||||||
import {
|
import { Credentials, UserSettings } from 'n8n-core';
|
||||||
Credentials,
|
|
||||||
UserSettings,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import { LoggerProxy } from 'n8n-workflow';
|
||||||
Db,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getLogger,
|
|
||||||
} from '../../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as glob from 'fast-glob';
|
import * as glob from 'fast-glob';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { getLogger } from '../../src/Logger';
|
||||||
|
import { Db } from '../../src';
|
||||||
|
|
||||||
export class ImportCredentialsCommand extends Command {
|
export class ImportCredentialsCommand extends Command {
|
||||||
static description = 'Import credentials';
|
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() {
|
async run() {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(ImportCredentialsCommand);
|
const { flags } = this.parse(ImportCredentialsCommand);
|
||||||
|
|
||||||
if (!flags.input) {
|
if (!flags.input) {
|
||||||
|
@ -76,18 +66,25 @@ export class ImportCredentialsCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.separate) {
|
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++) {
|
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' }));
|
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') {
|
if (typeof credential.data === 'object') {
|
||||||
// plain data / decrypted input. Should be encrypted first.
|
// 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);
|
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);
|
await Db.collections.Credentials!.save(credential);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const fileContents = JSON.parse(fs.readFileSync(flags.input, { encoding: 'utf8' }));
|
const fileContents = JSON.parse(fs.readFileSync(flags.input, { encoding: 'utf8' }));
|
||||||
|
|
||||||
if (!Array.isArray(fileContents)) {
|
if (!Array.isArray(fileContents)) {
|
||||||
|
@ -97,8 +94,13 @@ export class ImportCredentialsCommand extends Command {
|
||||||
for (i = 0; i < fileContents.length; i++) {
|
for (i = 0; i < fileContents.length; i++) {
|
||||||
if (typeof fileContents[i].data === 'object') {
|
if (typeof fileContents[i].data === 'object') {
|
||||||
// plain data / decrypted input. Should be encrypted first.
|
// 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]);
|
await Db.collections.Credentials!.save(fileContents[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,15 @@
|
||||||
import {
|
/* eslint-disable no-console */
|
||||||
Command,
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
flags,
|
import { Command, flags } from '@oclif/command';
|
||||||
} from '@oclif/command';
|
|
||||||
|
|
||||||
import {
|
import { LoggerProxy } from 'n8n-workflow';
|
||||||
Db,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getLogger,
|
|
||||||
} from '../../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as glob from 'fast-glob';
|
import * as glob from 'fast-glob';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {
|
import { UserSettings } from 'n8n-core';
|
||||||
UserSettings,
|
import { getLogger } from '../../src/Logger';
|
||||||
} from 'n8n-core';
|
import { Db } from '../../src';
|
||||||
|
|
||||||
export class ImportWorkflowsCommand extends Command {
|
export class ImportWorkflowsCommand extends Command {
|
||||||
static description = 'Import workflows';
|
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() {
|
async run() {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(ImportWorkflowsCommand);
|
const { flags } = this.parse(ImportWorkflowsCommand);
|
||||||
|
|
||||||
if (!flags.input) {
|
if (!flags.input) {
|
||||||
|
@ -68,9 +59,12 @@ export class ImportWorkflowsCommand extends Command {
|
||||||
await UserSettings.prepareUserSettings();
|
await UserSettings.prepareUserSettings();
|
||||||
let i;
|
let i;
|
||||||
if (flags.separate) {
|
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++) {
|
for (i = 0; i < files.length; i++) {
|
||||||
const workflow = JSON.parse(fs.readFileSync(files[i], { encoding: 'utf8' }));
|
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);
|
await Db.collections.Workflow!.save(workflow);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,6 +75,7 @@ export class ImportWorkflowsCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < fileContents.length; i++) {
|
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]);
|
await Db.collections.Workflow!.save(fileContents[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +84,7 @@ export class ImportWorkflowsCommand extends Command {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('An error occurred while exporting workflows. See log messages for details.');
|
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);
|
logger.error(error.message);
|
||||||
this.exit(1);
|
this.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
Command,
|
/* eslint-disable no-console */
|
||||||
flags,
|
import { Command, flags } from '@oclif/command';
|
||||||
} from '@oclif/command';
|
|
||||||
|
|
||||||
import {
|
import { IDataObject } from 'n8n-workflow';
|
||||||
IDataObject
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Db,
|
|
||||||
} from "../../src";
|
|
||||||
|
|
||||||
|
import { Db } from '../../src';
|
||||||
|
|
||||||
export class ListWorkflowCommand extends Command {
|
export class ListWorkflowCommand extends Command {
|
||||||
static description = '\nList workflows';
|
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() {
|
async run() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(ListWorkflowCommand);
|
const { flags } = this.parse(ListWorkflowCommand);
|
||||||
|
|
||||||
if (flags.active !== undefined && !['true', 'false'].includes(flags.active)) {
|
if (flags.active !== undefined && !['true', 'false'].includes(flags.active)) {
|
||||||
|
@ -46,14 +42,13 @@ export class ListWorkflowCommand extends Command {
|
||||||
findQuery.active = flags.active === 'true';
|
findQuery.active = flags.active === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const workflows = await Db.collections.Workflow!.find(findQuery);
|
const workflows = await Db.collections.Workflow!.find(findQuery);
|
||||||
if (flags.onlyId) {
|
if (flags.onlyId) {
|
||||||
workflows.forEach(workflow => console.log(workflow.id));
|
workflows.forEach((workflow) => console.log(workflow.id));
|
||||||
} else {
|
} else {
|
||||||
workflows.forEach(workflow => console.log(workflow.id + "|" + workflow.name));
|
workflows.forEach((workflow) => console.log(`${workflow.id}|${workflow.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('\nGOT ERROR');
|
console.error('\nGOT ERROR');
|
||||||
console.log('====================================');
|
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 * as localtunnel from 'localtunnel';
|
||||||
import {
|
import { TUNNEL_SUBDOMAIN_ENV, UserSettings } from 'n8n-core';
|
||||||
TUNNEL_SUBDOMAIN_ENV,
|
|
||||||
UserSettings,
|
|
||||||
} from 'n8n-core';
|
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
const open = require('open');
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
|
|
||||||
|
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
|
@ -17,6 +22,7 @@ import {
|
||||||
Db,
|
Db,
|
||||||
ExternalHooks,
|
ExternalHooks,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
IExecutionsCurrentSummary,
|
IExecutionsCurrentSummary,
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
@ -24,15 +30,11 @@ import {
|
||||||
TestWebhooks,
|
TestWebhooks,
|
||||||
WaitTracker,
|
WaitTracker,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import { IDataObject } from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
import { getLogger } from '../src/Logger';
|
||||||
getLogger,
|
|
||||||
} from '../src/Logger';
|
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
|
||||||
LoggerProxy,
|
const open = require('open');
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
|
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
|
||||||
let processExistCode = 0;
|
let processExistCode = 0;
|
||||||
|
@ -54,29 +56,32 @@ export class Start extends Command {
|
||||||
description: 'opens the UI automatically in browser',
|
description: 'opens the UI automatically in browser',
|
||||||
}),
|
}),
|
||||||
tunnel: flags.boolean({
|
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
|
* Opens the UI in browser
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
static openBrowser() {
|
static openBrowser() {
|
||||||
const editorUrl = GenericHelpers.getBaseUrl();
|
const editorUrl = GenericHelpers.getBaseUrl();
|
||||||
|
|
||||||
open(editorUrl, { wait: true })
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
.catch((error: Error) => {
|
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`);
|
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.
|
* Stoppes the n8n in a graceful way.
|
||||||
* Make for example sure that all the webhooks from third party services
|
* Make for example sure that all the webhooks from third party services
|
||||||
* get removed.
|
* get removed.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
static async stopProcess() {
|
static async stopProcess() {
|
||||||
getLogger().info('\nStopping n8n...');
|
getLogger().info('\nStopping n8n...');
|
||||||
|
|
||||||
|
@ -90,10 +95,12 @@ export class Start extends Command {
|
||||||
process.exit(processExistCode);
|
process.exit(processExistCode);
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
const skipWebhookDeregistration = config.get('endpoints.skipWebhoooksDeregistrationOnShutdown') as boolean;
|
const skipWebhookDeregistration = config.get(
|
||||||
|
'endpoints.skipWebhoooksDeregistrationOnShutdown',
|
||||||
|
) as boolean;
|
||||||
|
|
||||||
const removePromises = [];
|
const removePromises = [];
|
||||||
if (activeWorkflowRunner !== undefined && skipWebhookDeregistration !== true) {
|
if (activeWorkflowRunner !== undefined && !skipWebhookDeregistration) {
|
||||||
removePromises.push(activeWorkflowRunner.removeAll());
|
removePromises.push(activeWorkflowRunner.removeAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,22 +112,23 @@ export class Start extends Command {
|
||||||
|
|
||||||
// Wait for active workflow executions to finish
|
// Wait for active workflow executions to finish
|
||||||
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
|
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
while (executingWorkflows.length !== 0) {
|
while (executingWorkflows.length !== 0) {
|
||||||
if (count++ % 4 === 0) {
|
if (count++ % 4 === 0) {
|
||||||
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
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}`);
|
console.log(` - Execution ID ${execution.id}, workflow ID: ${execution.workflowId}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 500);
|
setTimeout(resolve, 500);
|
||||||
});
|
});
|
||||||
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('There was an error shutting down n8n.', error);
|
console.error('There was an error shutting down n8n.', error);
|
||||||
}
|
}
|
||||||
|
@ -128,12 +136,12 @@ export class Start extends Command {
|
||||||
process.exit(processExistCode);
|
process.exit(processExistCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
// Make sure that n8n shuts down gracefully if possible
|
// Make sure that n8n shuts down gracefully if possible
|
||||||
process.on('SIGTERM', Start.stopProcess);
|
process.on('SIGTERM', Start.stopProcess);
|
||||||
process.on('SIGINT', Start.stopProcess);
|
process.on('SIGINT', Start.stopProcess);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(Start);
|
const { flags } = this.parse(Start);
|
||||||
|
|
||||||
// Wrap that the process does not close but we can still use async
|
// 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');
|
logger.info('Initializing n8n process');
|
||||||
|
|
||||||
// todo remove a few versions after release
|
// 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
|
// Start directly with the init of the database to improve startup time
|
||||||
const startDbInitPromise = Db.init().catch((error: Error) => {
|
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 redisPort = config.get('queue.bull.redis.port');
|
||||||
const redisDB = config.get('queue.bull.redis.db');
|
const redisDB = config.get('queue.bull.redis.db');
|
||||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||||
let lastTimer = 0, cumulativeTimeout = 0;
|
let lastTimer = 0;
|
||||||
|
let cumulativeTimeout = 0;
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
retryStrategy: (times: number): number | null => {
|
retryStrategy: (times: number): number | null => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastTimer > 30000) {
|
if (now - lastTimer > 30000) {
|
||||||
|
@ -199,7 +211,10 @@ export class Start extends Command {
|
||||||
cumulativeTimeout += now - lastTimer;
|
cumulativeTimeout += now - lastTimer;
|
||||||
lastTimer = now;
|
lastTimer = now;
|
||||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
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);
|
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') {
|
if (dbType === 'sqlite') {
|
||||||
const shouldRunVacuum = config.get('database.sqlite.executeVacuumOnStartup') as number;
|
const shouldRunVacuum = config.get('database.sqlite.executeVacuumOnStartup') as number;
|
||||||
if (shouldRunVacuum) {
|
if (shouldRunVacuum) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises, @typescript-eslint/no-non-null-assertion
|
||||||
await Db.collections.Execution!.query('VACUUM;');
|
await Db.collections.Execution!.query('VACUUM;');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.tunnel === true) {
|
if (flags.tunnel) {
|
||||||
this.log('\nWaiting for tunnel ...');
|
this.log('\nWaiting for tunnel ...');
|
||||||
|
|
||||||
let tunnelSubdomain;
|
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];
|
tunnelSubdomain = process.env[TUNNEL_SUBDOMAIN_ENV];
|
||||||
} else if (userSettings.tunnelSubdomain !== undefined) {
|
} else if (userSettings.tunnelSubdomain !== undefined) {
|
||||||
tunnelSubdomain = userSettings.tunnelSubdomain;
|
tunnelSubdomain = userSettings.tunnelSubdomain;
|
||||||
|
@ -257,9 +276,13 @@ export class Start extends Command {
|
||||||
if (tunnelSubdomain === undefined) {
|
if (tunnelSubdomain === undefined) {
|
||||||
// When no tunnel subdomain did exist yet create a new random one
|
// When no tunnel subdomain did exist yet create a new random one
|
||||||
const availableCharacters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
const availableCharacters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
userSettings.tunnelSubdomain = Array.from({ length: 24 }).map(() => {
|
userSettings.tunnelSubdomain = Array.from({ length: 24 })
|
||||||
return availableCharacters.charAt(Math.floor(Math.random() * availableCharacters.length));
|
.map(() => {
|
||||||
}).join('');
|
return availableCharacters.charAt(
|
||||||
|
Math.floor(Math.random() * availableCharacters.length),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
await UserSettings.writeUserSettings(userSettings);
|
await UserSettings.writeUserSettings(userSettings);
|
||||||
}
|
}
|
||||||
|
@ -269,14 +292,16 @@ export class Start extends Command {
|
||||||
subdomain: tunnelSubdomain,
|
subdomain: tunnelSubdomain,
|
||||||
};
|
};
|
||||||
|
|
||||||
const port = config.get('port') as number;
|
const port = config.get('port');
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const webhookTunnel = await localtunnel(port, tunnelSettings);
|
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(`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();
|
await Server.start();
|
||||||
|
@ -285,6 +310,7 @@ export class Start extends Command {
|
||||||
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
||||||
await activeWorkflowRunner.init();
|
await activeWorkflowRunner.init();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const waitTracker = WaitTracker();
|
const waitTracker = WaitTracker();
|
||||||
|
|
||||||
const editorUrl = GenericHelpers.getBaseUrl();
|
const editorUrl = GenericHelpers.getBaseUrl();
|
||||||
|
@ -297,7 +323,7 @@ export class Start extends Command {
|
||||||
process.stdin.setEncoding('utf8');
|
process.stdin.setEncoding('utf8');
|
||||||
let inputText = '';
|
let inputText = '';
|
||||||
|
|
||||||
if (flags.open === true) {
|
if (flags.open) {
|
||||||
Start.openBrowser();
|
Start.openBrowser();
|
||||||
}
|
}
|
||||||
this.log(`\nPress "o" to open in Browser.`);
|
this.log(`\nPress "o" to open in Browser.`);
|
||||||
|
@ -307,15 +333,18 @@ export class Start extends Command {
|
||||||
inputText = '';
|
inputText = '';
|
||||||
} else if (key.charCodeAt(0) === 3) {
|
} else if (key.charCodeAt(0) === 3) {
|
||||||
// Ctrl + c got pressed
|
// Ctrl + c got pressed
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
Start.stopProcess();
|
Start.stopProcess();
|
||||||
} else {
|
} else {
|
||||||
// When anything else got pressed, record it and send it on enter into the child process
|
// 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) {
|
if (key.charCodeAt(0) === 13) {
|
||||||
// send to child process and print in terminal
|
// send to child process and print in terminal
|
||||||
process.stdout.write('\n');
|
process.stdout.write('\n');
|
||||||
inputText = '';
|
inputText = '';
|
||||||
} else {
|
} else {
|
||||||
// record it and write into terminal
|
// record it and write into terminal
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
inputText += key;
|
inputText += key;
|
||||||
process.stdout.write(key);
|
process.stdout.write(key);
|
||||||
}
|
}
|
||||||
|
@ -323,6 +352,7 @@ export class Start extends Command {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
this.error(`There was an error: ${error.message}`);
|
this.error(`There was an error: ${error.message}`);
|
||||||
|
|
||||||
processExistCode = 1;
|
processExistCode = 1;
|
||||||
|
|
|
@ -1,26 +1,16 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
Command, flags,
|
/* eslint-disable no-console */
|
||||||
} from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
|
|
||||||
import {
|
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||||
IDataObject
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
Db,
|
import { Db, GenericHelpers } from '../../src';
|
||||||
GenericHelpers,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
import {
|
import { getLogger } from '../../src/Logger';
|
||||||
getLogger,
|
|
||||||
} from '../../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
export class UpdateWorkflowCommand extends Command {
|
export class UpdateWorkflowCommand extends Command {
|
||||||
static description = '\Update workflows';
|
static description = 'Update workflows';
|
||||||
|
|
||||||
static examples = [
|
static examples = [
|
||||||
`$ n8n update:workflow --all --active=false`,
|
`$ 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() {
|
async run() {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(UpdateWorkflowCommand);
|
const { flags } = this.parse(UpdateWorkflowCommand);
|
||||||
|
|
||||||
if (!flags.all && !flags.id) {
|
if (!flags.all && !flags.id) {
|
||||||
|
@ -52,7 +44,9 @@ export class UpdateWorkflowCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.all && flags.id) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,13 +54,12 @@ export class UpdateWorkflowCommand extends Command {
|
||||||
if (flags.active === undefined) {
|
if (flags.active === undefined) {
|
||||||
console.info(`No update flag like "--active=true" has been set!`);
|
console.info(`No update flag like "--active=true" has been set!`);
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
if (!['false', 'true'].includes(flags.active)) {
|
if (!['false', 'true'].includes(flags.active)) {
|
||||||
console.info(`Valid values for flag "--active" are only "false" or "true"!`);
|
console.info(`Valid values for flag "--active" are only "false" or "true"!`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateQuery.active = flags.active === 'true';
|
updateQuery.active = flags.active === 'true';
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Db.init();
|
await Db.init();
|
||||||
|
@ -80,6 +73,7 @@ export class UpdateWorkflowCommand extends Command {
|
||||||
findQuery.active = true;
|
findQuery.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
await Db.collections.Workflow!.update(findQuery, updateQuery);
|
await Db.collections.Workflow!.update(findQuery, updateQuery);
|
||||||
console.info('Done');
|
console.info('Done');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import {
|
/* eslint-disable no-console */
|
||||||
UserSettings,
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
} from 'n8n-core';
|
/* 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';
|
import { Command, flags } from '@oclif/command';
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
|
|
||||||
|
import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
|
@ -15,29 +20,20 @@ import {
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
TestWebhooks,
|
TestWebhooks,
|
||||||
WebhookServer,
|
WebhookServer,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import { IDataObject } from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
import { getLogger } from '../src/Logger';
|
||||||
getLogger,
|
|
||||||
} from '../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
|
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
|
||||||
let processExistCode = 0;
|
let processExistCode = 0;
|
||||||
|
|
||||||
|
|
||||||
export class Webhook extends Command {
|
export class Webhook extends Command {
|
||||||
static description = 'Starts n8n webhook process. Intercepts only production URLs.';
|
static description = 'Starts n8n webhook process. Intercepts only production URLs.';
|
||||||
|
|
||||||
static examples = [
|
static examples = [`$ n8n webhook`];
|
||||||
`$ n8n webhook`,
|
|
||||||
];
|
|
||||||
|
|
||||||
static flags = {
|
static flags = {
|
||||||
help: flags.help({ char: 'h' }),
|
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
|
* Make for example sure that all the webhooks from third party services
|
||||||
* get removed.
|
* get removed.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
static async stopProcess() {
|
static async stopProcess() {
|
||||||
LoggerProxy.info(`\nStopping n8n...`);
|
LoggerProxy.info(`\nStopping n8n...`);
|
||||||
|
|
||||||
|
@ -68,14 +65,16 @@ export class Webhook extends Command {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
while (executingWorkflows.length !== 0) {
|
while (executingWorkflows.length !== 0) {
|
||||||
if (count++ % 4 === 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) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 500);
|
setTimeout(resolve, 500);
|
||||||
});
|
});
|
||||||
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerProxy.error('There was an error shutting down n8n.', error);
|
LoggerProxy.error('There was an error shutting down n8n.', error);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +82,7 @@ export class Webhook extends Command {
|
||||||
process.exit(processExistCode);
|
process.exit(processExistCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
async run() {
|
async run() {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
@ -92,6 +91,7 @@ export class Webhook extends Command {
|
||||||
process.on('SIGTERM', Webhook.stopProcess);
|
process.on('SIGTERM', Webhook.stopProcess);
|
||||||
process.on('SIGINT', 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);
|
const { flags } = this.parse(Webhook);
|
||||||
|
|
||||||
// Wrap that the process does not close but we can still use async
|
// Wrap that the process does not close but we can still use async
|
||||||
|
@ -114,7 +114,8 @@ export class Webhook extends Command {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Start directly with the init of the database to improve startup time
|
// 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}"`);
|
logger.error(`There was an error initializing DB: "${error.message}"`);
|
||||||
|
|
||||||
processExistCode = 1;
|
processExistCode = 1;
|
||||||
|
@ -124,6 +125,7 @@ export class Webhook extends Command {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure the settings exist
|
// Make sure the settings exist
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const userSettings = await UserSettings.prepareUserSettings();
|
const userSettings = await UserSettings.prepareUserSettings();
|
||||||
|
|
||||||
// Load all node and credential types
|
// Load all node and credential types
|
||||||
|
@ -153,9 +155,11 @@ export class Webhook extends Command {
|
||||||
const redisPort = config.get('queue.bull.redis.port');
|
const redisPort = config.get('queue.bull.redis.port');
|
||||||
const redisDB = config.get('queue.bull.redis.db');
|
const redisDB = config.get('queue.bull.redis.db');
|
||||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||||
let lastTimer = 0, cumulativeTimeout = 0;
|
let lastTimer = 0;
|
||||||
|
let cumulativeTimeout = 0;
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
retryStrategy: (times: number): number | null => {
|
retryStrategy: (times: number): number | null => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastTimer > 30000) {
|
if (now - lastTimer > 30000) {
|
||||||
|
@ -166,7 +170,10 @@ export class Webhook extends Command {
|
||||||
cumulativeTimeout += now - lastTimer;
|
cumulativeTimeout += now - lastTimer;
|
||||||
lastTimer = now;
|
lastTimer = now;
|
||||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,11 +215,12 @@ export class Webhook extends Command {
|
||||||
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
||||||
await activeWorkflowRunner.initWebhooks();
|
await activeWorkflowRunner.initWebhooks();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const editorUrl = GenericHelpers.getBaseUrl();
|
const editorUrl = GenericHelpers.getBaseUrl();
|
||||||
console.info('Webhook listener waiting for requests.');
|
console.info('Webhook listener waiting for requests.');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Exiting due to error. See log message for details.');
|
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}"`);
|
logger.error(`Webhook process cannot continue. "${error.message}"`);
|
||||||
|
|
||||||
processExistCode = 1;
|
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 * as PCancelable from 'p-cancelable';
|
||||||
|
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
import {
|
import { UserSettings, WorkflowExecute } from 'n8n-core';
|
||||||
UserSettings,
|
|
||||||
WorkflowExecute,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
@ -13,12 +19,12 @@ import {
|
||||||
IWorkflowExecuteHooks,
|
IWorkflowExecuteHooks,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowHooks,
|
WorkflowHooks,
|
||||||
|
LoggerProxy,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import { FindOneOptions } from 'typeorm';
|
||||||
FindOneOptions,
|
|
||||||
} from 'typeorm';
|
|
||||||
|
|
||||||
|
import * as Bull from 'bull';
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
CredentialsOverwrites,
|
CredentialsOverwrites,
|
||||||
|
@ -37,24 +43,15 @@ import {
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
import {
|
import { getLogger } from '../src/Logger';
|
||||||
getLogger,
|
|
||||||
} from '../src/Logger';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
import * as Bull from 'bull';
|
|
||||||
import * as Queue from '../src/Queue';
|
import * as Queue from '../src/Queue';
|
||||||
|
|
||||||
export class Worker extends Command {
|
export class Worker extends Command {
|
||||||
static description = '\nStarts a n8n worker';
|
static description = '\nStarts a n8n worker';
|
||||||
|
|
||||||
static examples = [
|
static examples = [`$ n8n worker --concurrency=5`];
|
||||||
`$ n8n worker --concurrency=5`,
|
|
||||||
];
|
|
||||||
|
|
||||||
static flags = {
|
static flags = {
|
||||||
help: flags.help({ char: 'h' }),
|
help: flags.help({ char: 'h' }),
|
||||||
|
@ -82,6 +79,7 @@ export class Worker extends Command {
|
||||||
LoggerProxy.info(`Stopping n8n...`);
|
LoggerProxy.info(`Stopping n8n...`);
|
||||||
|
|
||||||
// Stop accepting new jobs
|
// Stop accepting new jobs
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
Worker.jobQueue.pause(true);
|
Worker.jobQueue.pause(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -103,13 +101,17 @@ export class Worker extends Command {
|
||||||
while (Object.keys(Worker.runningJobs).length !== 0) {
|
while (Object.keys(Worker.runningJobs).length !== 0) {
|
||||||
if (count++ % 4 === 0) {
|
if (count++ % 4 === 0) {
|
||||||
const waitLeft = Math.ceil((stopTime - new Date().getTime()) / 1000);
|
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) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 500);
|
setTimeout(resolve, 500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerProxy.error('There was an error shutting down n8n.', 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> {
|
async runJob(job: Bull.Job, nodeTypes: INodeTypes): Promise<IBullJobResponse> {
|
||||||
const jobData = job.data as IBullJobData;
|
const jobData = job.data as IBullJobData;
|
||||||
const executionDb = await Db.collections.Execution!.findOne(jobData.executionId) as IExecutionFlattedDb;
|
const executionDb = (await Db.collections.Execution!.findOne(
|
||||||
const currentExecutionDb = ResponseHelper.unflattenExecutionData(executionDb) as IExecutionResponse;
|
jobData.executionId,
|
||||||
LoggerProxy.info(`Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${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;
|
let { staticData } = currentExecutionDb.workflowData;
|
||||||
if (jobData.loadStaticData === true) {
|
if (jobData.loadStaticData) {
|
||||||
const findOptions = {
|
const findOptions = {
|
||||||
select: ['id', 'staticData'],
|
select: ['id', 'staticData'],
|
||||||
} as FindOneOptions;
|
} 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) {
|
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;
|
staticData = workflowData.staticData;
|
||||||
}
|
}
|
||||||
|
|
||||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||||
if (currentExecutionDb.workflowData.settings && currentExecutionDb.workflowData.settings.executionTimeout) {
|
if (
|
||||||
workflowTimeout = currentExecutionDb.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
// 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;
|
let executionTimeoutTimestamp: number | undefined;
|
||||||
|
@ -146,16 +161,37 @@ export class Worker extends Command {
|
||||||
executionTimeoutTimestamp = Date.now() + workflowTimeout * 1000;
|
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);
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(
|
||||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(currentExecutionDb.mode, job.data.executionId, currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string });
|
undefined,
|
||||||
|
executionTimeoutTimestamp,
|
||||||
|
);
|
||||||
|
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(
|
||||||
|
currentExecutionDb.mode,
|
||||||
|
job.data.executionId,
|
||||||
|
currentExecutionDb.workflowData,
|
||||||
|
{ retryOf: currentExecutionDb.retryOf as string },
|
||||||
|
);
|
||||||
additionalData.executionId = jobData.executionId;
|
additionalData.executionId = jobData.executionId;
|
||||||
|
|
||||||
let workflowExecute: WorkflowExecute;
|
let workflowExecute: WorkflowExecute;
|
||||||
let workflowRun: PCancelable<IRun>;
|
let workflowRun: PCancelable<IRun>;
|
||||||
if (currentExecutionDb.data !== undefined) {
|
if (currentExecutionDb.data !== undefined) {
|
||||||
workflowExecute = new WorkflowExecute(additionalData, currentExecutionDb.mode, currentExecutionDb.data);
|
workflowExecute = new WorkflowExecute(
|
||||||
|
additionalData,
|
||||||
|
currentExecutionDb.mode,
|
||||||
|
currentExecutionDb.data,
|
||||||
|
);
|
||||||
workflowRun = workflowExecute.processRunExecutionData(workflow);
|
workflowRun = workflowExecute.processRunExecutionData(workflow);
|
||||||
} else {
|
} else {
|
||||||
// Execute all nodes
|
// Execute all nodes
|
||||||
|
@ -180,6 +216,7 @@ export class Worker extends Command {
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
LoggerProxy.init(logger);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.info('Starting n8n worker...');
|
console.info('Starting n8n worker...');
|
||||||
|
|
||||||
// Make sure that n8n shuts down gracefully if possible
|
// Make sure that n8n shuts down gracefully if possible
|
||||||
|
@ -192,7 +229,7 @@ export class Worker extends Command {
|
||||||
const { flags } = this.parse(Worker);
|
const { flags } = this.parse(Worker);
|
||||||
|
|
||||||
// Start directly with the init of the database to improve startup time
|
// 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}"`);
|
logger.error(`There was an error initializing DB: "${error.message}"`);
|
||||||
|
|
||||||
Worker.processExistCode = 1;
|
Worker.processExistCode = 1;
|
||||||
|
@ -225,10 +262,12 @@ export class Worker extends Command {
|
||||||
// Wait till the database is ready
|
// Wait till the database is ready
|
||||||
await startDbInitPromise;
|
await startDbInitPromise;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||||
|
|
||||||
Worker.jobQueue = Queue.getInstance().getBullObjectInstance();
|
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();
|
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) => {
|
Worker.jobQueue.on('error', (error: Error) => {
|
||||||
if (error.toString().includes('ECONNREFUSED') === true) {
|
if (error.toString().includes('ECONNREFUSED')) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastTimer > 30000) {
|
if (now - lastTimer > 30000) {
|
||||||
// Means we had no timeout at all or last timeout was temporary and we recovered
|
// 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;
|
cumulativeTimeout += now - lastTimer;
|
||||||
lastTimer = now;
|
lastTimer = now;
|
||||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.warn('Redis unavailable - trying to reconnect...');
|
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
|
// This is a non-recoverable error
|
||||||
// Happens when worker starts and Redis is unavailable
|
// Happens when worker starts and Redis is unavailable
|
||||||
// Even if Redis comes back online, worker will be zombie
|
// Even if Redis comes back online, worker will be zombie
|
||||||
|
@ -287,6 +329,5 @@ export class Worker extends Command {
|
||||||
process.exit(1);
|
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 convict from 'convict';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
@ -6,7 +9,6 @@ import * as core from 'n8n-core';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const config = convict({
|
const config = convict({
|
||||||
|
|
||||||
database: {
|
database: {
|
||||||
type: {
|
type: {
|
||||||
doc: 'Type of database to use',
|
doc: 'Type of database to use',
|
||||||
|
@ -84,7 +86,6 @@ const config = convict({
|
||||||
env: 'DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED',
|
env: 'DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
mysqldb: {
|
mysqldb: {
|
||||||
database: {
|
database: {
|
||||||
|
@ -159,7 +160,6 @@ const config = convict({
|
||||||
},
|
},
|
||||||
|
|
||||||
executions: {
|
executions: {
|
||||||
|
|
||||||
// By default workflows get always executed in their own process.
|
// By default workflows get always executed in their own process.
|
||||||
// If this option gets set to "main" it will run them in the
|
// If this option gets set to "main" it will run them in the
|
||||||
// main-process instead.
|
// main-process instead.
|
||||||
|
@ -573,7 +573,6 @@ const config = convict({
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TypeError(`The Nodes to exclude is not a valid Array of strings.`);
|
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',
|
env: 'N8N_VERSION_NOTIFICATIONS_INFO_URL',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Overwrite default configuration with settings which got defined in
|
// Overwrite default configuration with settings which got defined in
|
||||||
|
|
|
@ -4,88 +4,72 @@ import { entities } from '../src/databases/entities';
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
"name": "sqlite",
|
name: 'sqlite',
|
||||||
"type": "sqlite",
|
type: 'sqlite',
|
||||||
"logging": true,
|
logging: true,
|
||||||
"entities": Object.values(entities),
|
entities: Object.values(entities),
|
||||||
"database": path.join(UserSettings.getUserN8nFolderPath(), 'database.sqlite'),
|
database: path.join(UserSettings.getUserN8nFolderPath(), 'database.sqlite'),
|
||||||
"migrations": [
|
migrations: ['./src/databases/sqlite/migrations/*.ts'],
|
||||||
"./src/databases/sqlite/migrations/*.ts"
|
subscribers: ['./src/databases/sqlite/subscribers/*.ts'],
|
||||||
],
|
cli: {
|
||||||
"subscribers": [
|
entitiesDir: './src/databases/entities',
|
||||||
"./src/databases/sqlite/subscribers/*.ts"
|
migrationsDir: './src/databases/sqlite/migrations',
|
||||||
],
|
subscribersDir: './src/databases/sqlite/subscribers',
|
||||||
"cli": {
|
},
|
||||||
"entitiesDir": "./src/databases/entities",
|
|
||||||
"migrationsDir": "./src/databases/sqlite/migrations",
|
|
||||||
"subscribersDir": "./src/databases/sqlite/subscribers"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "postgres",
|
name: 'postgres',
|
||||||
"type": "postgres",
|
type: 'postgres',
|
||||||
"logging": false,
|
logging: false,
|
||||||
"host": "localhost",
|
host: 'localhost',
|
||||||
"username": "postgres",
|
username: 'postgres',
|
||||||
"password": "",
|
password: '',
|
||||||
"port": 5432,
|
port: 5432,
|
||||||
"database": "n8n",
|
database: 'n8n',
|
||||||
"schema": "public",
|
schema: 'public',
|
||||||
"entities": Object.values(entities),
|
entities: Object.values(entities),
|
||||||
"migrations": [
|
migrations: ['./src/databases/postgresdb/migrations/*.ts'],
|
||||||
"./src/databases/postgresdb/migrations/*.ts"
|
subscribers: ['src/subscriber/**/*.ts'],
|
||||||
],
|
cli: {
|
||||||
"subscribers": [
|
entitiesDir: './src/databases/entities',
|
||||||
"src/subscriber/**/*.ts"
|
migrationsDir: './src/databases/postgresdb/migrations',
|
||||||
],
|
subscribersDir: './src/databases/postgresdb/subscribers',
|
||||||
"cli": {
|
},
|
||||||
"entitiesDir": "./src/databases/entities",
|
|
||||||
"migrationsDir": "./src/databases/postgresdb/migrations",
|
|
||||||
"subscribersDir": "./src/databases/postgresdb/subscribers"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mysql",
|
name: 'mysql',
|
||||||
"type": "mysql",
|
type: 'mysql',
|
||||||
"database": "n8n",
|
database: 'n8n',
|
||||||
"username": "root",
|
username: 'root',
|
||||||
"password": "password",
|
password: 'password',
|
||||||
"host": "localhost",
|
host: 'localhost',
|
||||||
"port": "3306",
|
port: '3306',
|
||||||
"logging": false,
|
logging: false,
|
||||||
"entities": Object.values(entities),
|
entities: Object.values(entities),
|
||||||
"migrations": [
|
migrations: ['./src/databases/mysqldb/migrations/*.ts'],
|
||||||
"./src/databases/mysqldb/migrations/*.ts"
|
subscribers: ['src/subscriber/**/*.ts'],
|
||||||
],
|
cli: {
|
||||||
"subscribers": [
|
entitiesDir: './src/databases/entities',
|
||||||
"src/subscriber/**/*.ts"
|
migrationsDir: './src/databases/mysqldb/migrations',
|
||||||
],
|
subscribersDir: './src/databases/mysqldb/Subscribers',
|
||||||
"cli": {
|
},
|
||||||
"entitiesDir": "./src/databases/entities",
|
|
||||||
"migrationsDir": "./src/databases/mysqldb/migrations",
|
|
||||||
"subscribersDir": "./src/databases/mysqldb/Subscribers"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mariadb",
|
name: 'mariadb',
|
||||||
"type": "mariadb",
|
type: 'mariadb',
|
||||||
"database": "n8n",
|
database: 'n8n',
|
||||||
"username": "root",
|
username: 'root',
|
||||||
"password": "password",
|
password: 'password',
|
||||||
"host": "localhost",
|
host: 'localhost',
|
||||||
"port": "3306",
|
port: '3306',
|
||||||
"logging": false,
|
logging: false,
|
||||||
"entities": Object.values(entities),
|
entities: Object.values(entities),
|
||||||
"migrations": [
|
migrations: ['./src/databases/mysqldb/migrations/*.ts'],
|
||||||
"./src/databases/mysqldb/migrations/*.ts"
|
subscribers: ['src/subscriber/**/*.ts'],
|
||||||
],
|
cli: {
|
||||||
"subscribers": [
|
entitiesDir: './src/databases/entities',
|
||||||
"src/subscriber/**/*.ts"
|
migrationsDir: './src/databases/mysqldb/migrations',
|
||||||
],
|
subscribersDir: './src/databases/mysqldb/Subscribers',
|
||||||
"cli": {
|
},
|
||||||
"entitiesDir": "./src/databases/entities",
|
|
||||||
"migrationsDir": "./src/databases/mysqldb/migrations",
|
|
||||||
"subscribersDir": "./src/databases/mysqldb/Subscribers"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -21,14 +21,15 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"dev": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon\"",
|
"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",
|
"postpack": "rm -f oclif.manifest.json",
|
||||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||||
"start": "run-script-os",
|
"start": "run-script-os",
|
||||||
"start:default": "cd bin && ./n8n",
|
"start:default": "cd bin && ./n8n",
|
||||||
"start:windows": "cd bin && n8n",
|
"start:windows": "cd bin && n8n",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
|
||||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
|
||||||
"watch": "tsc --watch",
|
"watch": "tsc --watch",
|
||||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
||||||
},
|
},
|
||||||
|
@ -77,7 +78,7 @@
|
||||||
"ts-jest": "^26.3.0",
|
"ts-jest": "^26.3.0",
|
||||||
"ts-node": "^8.9.1",
|
"ts-node": "^8.9.1",
|
||||||
"tslint": "^6.1.2",
|
"tslint": "^6.1.2",
|
||||||
"typescript": "~3.9.7"
|
"typescript": "~4.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oclif/command": "^1.5.18",
|
"@oclif/command": "^1.5.18",
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import {
|
/* eslint-disable prefer-template */
|
||||||
IRun,
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
} from 'n8n-workflow';
|
/* 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 {
|
import { createDeferredPromise } from 'n8n-core';
|
||||||
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 {
|
import {
|
||||||
Db,
|
Db,
|
||||||
IExecutingWorkflowData,
|
IExecutingWorkflowData,
|
||||||
|
@ -17,16 +24,11 @@ import {
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
import { ChildProcess } from 'child_process';
|
|
||||||
import * as PCancelable from 'p-cancelable';
|
|
||||||
|
|
||||||
|
|
||||||
export class ActiveExecutions {
|
export class ActiveExecutions {
|
||||||
private activeExecutions: {
|
private activeExecutions: {
|
||||||
[index: string]: IExecutingWorkflowData;
|
[index: string]: IExecutingWorkflowData;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new active execution
|
* Add a new active execution
|
||||||
*
|
*
|
||||||
|
@ -35,8 +37,11 @@ export class ActiveExecutions {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @memberof ActiveExecutions
|
* @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) {
|
if (executionId === undefined) {
|
||||||
// Is a new execution so save in DB
|
// Is a new execution so save in DB
|
||||||
|
|
||||||
|
@ -52,14 +57,23 @@ export class ActiveExecutions {
|
||||||
fullExecutionData.retryOf = executionData.retryOf.toString();
|
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();
|
fullExecutionData.workflowId = executionData.workflowData.id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||||
|
|
||||||
const executionResult = await Db.collections.Execution!.save(execution as IExecutionFlattedDb);
|
const executionResult = await Db.collections.Execution!.save(
|
||||||
executionId = typeof executionResult.id === "object" ? executionResult.id!.toString() : executionResult.id + "";
|
execution as IExecutionFlattedDb,
|
||||||
|
);
|
||||||
|
executionId =
|
||||||
|
typeof executionResult.id === 'object'
|
||||||
|
? // @ts-ignore
|
||||||
|
executionResult.id!.toString()
|
||||||
|
: executionResult.id + '';
|
||||||
} else {
|
} else {
|
||||||
// Is an existing execution we want to finish so update in DB
|
// 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);
|
await Db.collections.Execution!.update(executionId, execution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
this.activeExecutions[executionId] = {
|
this.activeExecutions[executionId] = {
|
||||||
executionData,
|
executionData,
|
||||||
process,
|
process,
|
||||||
|
@ -79,10 +94,10 @@ export class ActiveExecutions {
|
||||||
postExecutePromises: [],
|
postExecutePromises: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
return executionId;
|
return executionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches an execution
|
* Attaches an execution
|
||||||
*
|
*
|
||||||
|
@ -90,15 +105,17 @@ export class ActiveExecutions {
|
||||||
* @param {PCancelable<IRun>} workflowExecution
|
* @param {PCancelable<IRun>} workflowExecution
|
||||||
* @memberof ActiveExecutions
|
* @memberof ActiveExecutions
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
attachWorkflowExecution(executionId: string, workflowExecution: PCancelable<IRun>) {
|
attachWorkflowExecution(executionId: string, workflowExecution: PCancelable<IRun>) {
|
||||||
if (this.activeExecutions[executionId] === undefined) {
|
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;
|
this.activeExecutions[executionId].workflowExecution = workflowExecution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an active execution
|
* Remove an active execution
|
||||||
*
|
*
|
||||||
|
@ -113,6 +130,7 @@ export class ActiveExecutions {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve all the waiting promises
|
// Resolve all the waiting promises
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const promise of this.activeExecutions[executionId].postExecutePromises) {
|
for (const promise of this.activeExecutions[executionId].postExecutePromises) {
|
||||||
promise.resolve(fullRunData);
|
promise.resolve(fullRunData);
|
||||||
}
|
}
|
||||||
|
@ -121,7 +139,6 @@ export class ActiveExecutions {
|
||||||
delete this.activeExecutions[executionId];
|
delete this.activeExecutions[executionId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forces an execution to stop
|
* Forces an execution to stop
|
||||||
*
|
*
|
||||||
|
@ -144,7 +161,8 @@ export class ActiveExecutions {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// execute on next event loop tick;
|
// execute on next event loop tick;
|
||||||
this.activeExecutions[executionId].process!.send({
|
this.activeExecutions[executionId].process!.send({
|
||||||
type: timeout ? timeout : 'stopExecution',
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
|
type: timeout || 'stopExecution',
|
||||||
});
|
});
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
@ -153,10 +171,10 @@ export class ActiveExecutions {
|
||||||
this.activeExecutions[executionId].workflowExecution!.cancel();
|
this.activeExecutions[executionId].workflowExecution!.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
return this.getPostExecutePromise(executionId);
|
return this.getPostExecutePromise(executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise which will resolve with the data of the execution
|
* Returns a promise which will resolve with the data of the execution
|
||||||
* with the given id
|
* with the given id
|
||||||
|
@ -178,7 +196,6 @@ export class ActiveExecutions {
|
||||||
return waitPromise.promise();
|
return waitPromise.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the currently active executions
|
* Returns all the currently active executions
|
||||||
*
|
*
|
||||||
|
@ -189,25 +206,22 @@ export class ActiveExecutions {
|
||||||
const returnData: IExecutionsCurrentSummary[] = [];
|
const returnData: IExecutionsCurrentSummary[] = [];
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const id of Object.keys(this.activeExecutions)) {
|
for (const id of Object.keys(this.activeExecutions)) {
|
||||||
data = this.activeExecutions[id];
|
data = this.activeExecutions[id];
|
||||||
returnData.push(
|
returnData.push({
|
||||||
{
|
|
||||||
id,
|
id,
|
||||||
retryOf: data.executionData.retryOf as string | undefined,
|
retryOf: data.executionData.retryOf as string | undefined,
|
||||||
startedAt: data.startedAt,
|
startedAt: data.startedAt,
|
||||||
mode: data.executionData.executionMode,
|
mode: data.executionData.executionMode,
|
||||||
workflowId: data.executionData.workflowData.id! as string,
|
workflowId: data.executionData.workflowData.id! as string,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let activeExecutionsInstance: ActiveExecutions | undefined;
|
let activeExecutionsInstance: ActiveExecutions | undefined;
|
||||||
|
|
||||||
export function getInstance(): ActiveExecutions {
|
export function getInstance(): ActiveExecutions {
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
import {
|
/* eslint-disable prefer-spread */
|
||||||
Db,
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
IActivationError,
|
/* eslint-disable no-param-reassign */
|
||||||
IResponseCallbackData,
|
/* eslint-disable no-console */
|
||||||
IWebhookDb,
|
/* eslint-disable no-await-in-loop */
|
||||||
IWorkflowDb,
|
/* eslint-disable no-restricted-syntax */
|
||||||
IWorkflowExecutionDataProcess,
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
NodeTypes,
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
ResponseHelper,
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
WebhookHelpers,
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
WorkflowCredentials,
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
WorkflowExecuteAdditionalData,
|
import { ActiveWorkflows, NodeExecuteFunctions } from 'n8n-core';
|
||||||
WorkflowHelpers,
|
|
||||||
WorkflowRunner,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActiveWorkflows,
|
|
||||||
NodeExecuteFunctions,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IExecuteData,
|
IExecuteData,
|
||||||
|
@ -32,12 +24,28 @@ import {
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowActivateMode,
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
|
LoggerProxy as Logger,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import {
|
import {
|
||||||
LoggerProxy as Logger,
|
Db,
|
||||||
} from 'n8n-workflow';
|
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)`;
|
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;
|
[key: string]: IActivationError;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
async init() {
|
async init() {
|
||||||
|
|
||||||
// Get the active workflows from database
|
// Get the active workflows from database
|
||||||
|
|
||||||
// NOTE
|
// NOTE
|
||||||
// Here I guess we can have a flag on the workflow table like hasTrigger
|
// 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
|
// 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
|
// Clear up active workflow table
|
||||||
await Db.collections.Webhook?.clear();
|
await Db.collections.Webhook?.clear();
|
||||||
|
@ -69,21 +79,32 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
for (const workflowData of workflowsData) {
|
for (const workflowData of workflowsData) {
|
||||||
console.log(` - ${workflowData.name}`);
|
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 {
|
try {
|
||||||
await this.add(workflowData.id.toString(), 'init', workflowData);
|
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`);
|
console.log(` => Started`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(` => ERROR: Workflow could not be activated:`);
|
console.log(` => ERROR: Workflow could not be activated:`);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
console.log(` ${error.message}`);
|
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)');
|
Logger.verbose('Finished initializing active workflows (startup)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
async initWebhooks() {
|
async initWebhooks() {
|
||||||
this.activeWorkflows = new ActiveWorkflows();
|
this.activeWorkflows = new ActiveWorkflows();
|
||||||
}
|
}
|
||||||
|
@ -104,7 +125,10 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeWorkflows = await this.getActiveWorkflows();
|
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 = [];
|
const removePromises = [];
|
||||||
for (const workflowId of activeWorkflowId) {
|
for (const workflowId of activeWorkflowId) {
|
||||||
|
@ -112,7 +136,6 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(removePromises);
|
await Promise.all(removePromises);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,10 +148,19 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {Promise<object>}
|
* @returns {Promise<object>}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @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}"`);
|
Logger.debug(`Received webhoook "${httpMethod}" for path "${path}"`);
|
||||||
if (this.activeWorkflows === null) {
|
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
|
// Reset request parameters
|
||||||
|
@ -139,7 +171,10 @@ export class ActiveWorkflowRunner {
|
||||||
path = path.slice(0, -1);
|
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;
|
let webhookId: string | undefined;
|
||||||
|
|
||||||
// check if path is dynamic
|
// check if path is dynamic
|
||||||
|
@ -147,19 +182,30 @@ export class ActiveWorkflowRunner {
|
||||||
// check if a dynamic webhook path exists
|
// check if a dynamic webhook path exists
|
||||||
const pathElements = path.split('/');
|
const pathElements = path.split('/');
|
||||||
webhookId = pathElements.shift();
|
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) {
|
if (dynamicWebhooks === undefined || dynamicWebhooks.length === 0) {
|
||||||
// The requested webhook is not registered
|
// 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;
|
let maxMatches = 0;
|
||||||
const pathElementsSet = new Set(pathElements);
|
const pathElementsSet = new Set(pathElements);
|
||||||
// check if static elements match in path
|
// check if static elements match in path
|
||||||
// if more results have been returned choose the one with the most static-route matches
|
// if more results have been returned choose the one with the most static-route matches
|
||||||
dynamicWebhooks.forEach(dynamicWebhook => {
|
dynamicWebhooks.forEach((dynamicWebhook) => {
|
||||||
const staticElements = dynamicWebhook.webhookPath.split('/').filter(ele => !ele.startsWith(':'));
|
const staticElements = dynamicWebhook.webhookPath
|
||||||
const allStaticExist = staticElements.every(staticEle => pathElementsSet.has(staticEle));
|
.split('/')
|
||||||
|
.filter((ele) => !ele.startsWith(':'));
|
||||||
|
const allStaticExist = staticElements.every((staticEle) => pathElementsSet.has(staticEle));
|
||||||
|
|
||||||
if (allStaticExist && staticElements.length > maxMatches) {
|
if (allStaticExist && staticElements.length > maxMatches) {
|
||||||
maxMatches = staticElements.length;
|
maxMatches = staticElements.length;
|
||||||
|
@ -171,12 +217,20 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (webhook === undefined) {
|
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
|
// extracting params from path
|
||||||
webhook!.webhookPath.split('/').forEach((ele, index) => {
|
// @ts-ignore
|
||||||
|
webhook.webhookPath.split('/').forEach((ele, index) => {
|
||||||
if (ele.startsWith(':')) {
|
if (ele.startsWith(':')) {
|
||||||
// write params to req.params
|
// write params to req.params
|
||||||
req.params[ele.slice(1)] = pathElements[index];
|
req.params[ele.slice(1)] = pathElements[index];
|
||||||
|
@ -186,16 +240,33 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
const workflowData = await Db.collections.Workflow!.findOne(webhook.workflowId);
|
const workflowData = await Db.collections.Workflow!.findOne(webhook.workflowId);
|
||||||
if (workflowData === undefined) {
|
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 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 additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||||
|
|
||||||
const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(webhook.node as string) as INode, additionalData).filter((webhook) => {
|
const webhookData = NodeHelpers.getNodeWebhooks(
|
||||||
return (webhook.httpMethod === httpMethod && webhook.path === path);
|
workflow,
|
||||||
|
workflow.getNode(webhook.node) as INode,
|
||||||
|
additionalData,
|
||||||
|
).filter((webhook) => {
|
||||||
|
return webhook.httpMethod === httpMethod && webhook.path === path;
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
// Get the node which has the webhook defined to know where to start from and to
|
// 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const executionMode = 'webhook';
|
const executionMode = 'webhook';
|
||||||
//@ts-ignore
|
// @ts-ignore
|
||||||
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, undefined, undefined, req, res, (error: Error | null, data: object) => {
|
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) {
|
if (error !== null) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,10 +310,10 @@ export class ActiveWorkflowRunner {
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
async getWebhookMethods(path: string): Promise<string[]> {
|
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
|
// 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;
|
return webhookMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,11 +324,15 @@ export class ActiveWorkflowRunner {
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
async getActiveWorkflows(): Promise<IWorkflowDb[]> {
|
async getActiveWorkflows(): Promise<IWorkflowDb[]> {
|
||||||
const activeWorkflows = await Db.collections.Workflow?.find({ where: { active: true }, select: ['id'] }) as IWorkflowDb[];
|
const activeWorkflows = (await Db.collections.Workflow?.find({
|
||||||
return activeWorkflows.filter(workflow => this.activationErrors[workflow.id.toString()] === undefined);
|
where: { active: true },
|
||||||
|
select: ['id'],
|
||||||
|
})) as IWorkflowDb[];
|
||||||
|
return activeWorkflows.filter(
|
||||||
|
(workflow) => this.activationErrors[workflow.id.toString()] === undefined,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the workflow is active
|
* Returns if the workflow is active
|
||||||
*
|
*
|
||||||
|
@ -253,8 +341,8 @@ export class ActiveWorkflowRunner {
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
async isActive(id: string): Promise<boolean> {
|
async isActive(id: string): Promise<boolean> {
|
||||||
const workflow = await Db.collections.Workflow?.findOne({ id: Number(id) }) as IWorkflowDb;
|
const workflow = (await Db.collections.Workflow?.findOne({ id: Number(id) })) as IWorkflowDb;
|
||||||
return workflow?.active as boolean;
|
return workflow?.active;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -281,12 +369,16 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @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);
|
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true);
|
||||||
let path = '' as string | undefined;
|
let path = '' as string | undefined;
|
||||||
|
|
||||||
for (const webhookData of webhooks) {
|
for (const webhookData of webhooks) {
|
||||||
|
|
||||||
const node = workflow.getNode(webhookData.node) as INode;
|
const node = workflow.getNode(webhookData.node) as INode;
|
||||||
node.name = webhookData.node;
|
node.name = webhookData.node;
|
||||||
|
|
||||||
|
@ -312,18 +404,35 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await Db.collections.Webhook?.insert(webhook);
|
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 (webhookExists !== true) {
|
||||||
// If webhook does not exist yet create it
|
// 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) {
|
} catch (error) {
|
||||||
try {
|
try {
|
||||||
await this.removeWorkflowWebhooks(workflow.id as string);
|
await this.removeWorkflowWebhooks(workflow.id as string);
|
||||||
} catch (error) {
|
} 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 = '';
|
let errorMessage = '';
|
||||||
|
@ -337,6 +446,7 @@ export class ActiveWorkflowRunner {
|
||||||
// it's a error runnig the webhook methods (checkExists, create)
|
// it's a error runnig the webhook methods (checkExists, create)
|
||||||
errorMessage = error.detail;
|
errorMessage = error.detail;
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
errorMessage = error.message;
|
errorMessage = error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +457,6 @@ export class ActiveWorkflowRunner {
|
||||||
await WorkflowHelpers.saveStaticData(workflow);
|
await WorkflowHelpers.saveStaticData(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all the webhooks of the workflow
|
* Remove all the webhooks of the workflow
|
||||||
*
|
*
|
||||||
|
@ -362,7 +471,16 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
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';
|
const mode = 'internal';
|
||||||
|
|
||||||
|
@ -371,7 +489,14 @@ export class ActiveWorkflowRunner {
|
||||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true);
|
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true);
|
||||||
|
|
||||||
for (const webhookData of webhooks) {
|
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);
|
await WorkflowHelpers.saveStaticData(workflow);
|
||||||
|
@ -394,7 +519,14 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns
|
* @returns
|
||||||
* @memberof ActiveWorkflowRunner
|
* @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[] = [
|
const nodeExecutionStack: IExecuteData[] = [
|
||||||
{
|
{
|
||||||
node,
|
node,
|
||||||
|
@ -427,7 +559,6 @@ export class ActiveWorkflowRunner {
|
||||||
return workflowRunner.run(runData, true);
|
return workflowRunner.run(runData, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return poll function which gets the global functions from n8n-core
|
* Return poll function which gets the global functions from n8n-core
|
||||||
* and overwrites the __emit to be able to start it in subprocess
|
* and overwrites the __emit to be able to start it in subprocess
|
||||||
|
@ -438,18 +569,30 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {IGetExecutePollFunctions}
|
* @returns {IGetExecutePollFunctions}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
getExecutePollFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecutePollFunctions {
|
getExecutePollFunctions(
|
||||||
return ((workflow: Workflow, node: INode) => {
|
workflowData: IWorkflowDb,
|
||||||
const returnFunctions = NodeExecuteFunctions.getExecutePollFunctions(workflow, node, additionalData, mode, activation);
|
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 => {
|
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}"`);
|
Logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`);
|
||||||
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
||||||
};
|
};
|
||||||
return returnFunctions;
|
return returnFunctions;
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return trigger function which gets the global functions from n8n-core
|
* Return trigger function which gets the global functions from n8n-core
|
||||||
* and overwrites the emit to be able to start it in subprocess
|
* and overwrites the emit to be able to start it in subprocess
|
||||||
|
@ -460,16 +603,31 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {IGetExecuteTriggerFunctions}
|
* @returns {IGetExecuteTriggerFunctions}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions {
|
getExecuteTriggerFunctions(
|
||||||
return ((workflow: Workflow, node: INode) => {
|
workflowData: IWorkflowDb,
|
||||||
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation);
|
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 => {
|
returnFunctions.emit = (data: INodeExecutionData[][]): void => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
Logger.debug(`Received trigger for workflow "${workflow.name}"`);
|
Logger.debug(`Received trigger for workflow "${workflow.name}"`);
|
||||||
WorkflowHelpers.saveStaticData(workflow);
|
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;
|
return returnFunctions;
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -480,7 +638,11 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @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) {
|
if (this.activeWorkflows === null) {
|
||||||
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
||||||
}
|
}
|
||||||
|
@ -488,33 +650,69 @@ export class ActiveWorkflowRunner {
|
||||||
let workflowInstance: Workflow;
|
let workflowInstance: Workflow;
|
||||||
try {
|
try {
|
||||||
if (workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
workflowData = await Db.collections.Workflow!.findOne(workflowId) as IWorkflowDb;
|
workflowData = (await Db.collections.Workflow!.findOne(workflowId)) as IWorkflowDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!workflowData) {
|
if (!workflowData) {
|
||||||
throw new Error(`Could not find workflow with id "${workflowId}".`);
|
throw new Error(`Could not find workflow with id "${workflowId}".`);
|
||||||
}
|
}
|
||||||
const nodeTypes = NodeTypes();
|
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']);
|
const canBeActivated = workflowInstance.checkIfWorkflowCanBeActivated([
|
||||||
if (canBeActivated === false) {
|
'n8n-nodes-base.start',
|
||||||
|
]);
|
||||||
|
if (!canBeActivated) {
|
||||||
Logger.error(`Unable to activate workflow "${workflowData.name}"`);
|
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 mode = 'trigger';
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode, activation);
|
const getTriggerFunctions = this.getExecuteTriggerFunctions(
|
||||||
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode, activation);
|
workflowData,
|
||||||
|
additionalData,
|
||||||
|
mode,
|
||||||
|
activation,
|
||||||
|
);
|
||||||
|
const getPollFunctions = this.getExecutePollFunctions(
|
||||||
|
workflowData,
|
||||||
|
additionalData,
|
||||||
|
mode,
|
||||||
|
activation,
|
||||||
|
);
|
||||||
|
|
||||||
// Add the workflows which have webhooks defined
|
// Add the workflows which have webhooks defined
|
||||||
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode, activation);
|
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode, activation);
|
||||||
|
|
||||||
if (workflowInstance.getTriggerNodes().length !== 0
|
if (
|
||||||
|| workflowInstance.getPollNodes().length !== 0) {
|
workflowInstance.getTriggerNodes().length !== 0 ||
|
||||||
await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions);
|
workflowInstance.getPollNodes().length !== 0
|
||||||
Logger.verbose(`Successfully activated workflow "${workflowData.name}"`, { workflowId, workflowName: workflowData.name });
|
) {
|
||||||
|
await this.activeWorkflows.add(
|
||||||
|
workflowId,
|
||||||
|
workflowInstance,
|
||||||
|
additionalData,
|
||||||
|
mode,
|
||||||
|
activation,
|
||||||
|
getTriggerFunctions,
|
||||||
|
getPollFunctions,
|
||||||
|
);
|
||||||
|
Logger.verbose(`Successfully activated workflow "${workflowData.name}"`, {
|
||||||
|
workflowId,
|
||||||
|
workflowName: workflowData.name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.activationErrors[workflowId] !== undefined) {
|
if (this.activationErrors[workflowId] !== undefined) {
|
||||||
|
@ -548,13 +746,15 @@ export class ActiveWorkflowRunner {
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
async remove(workflowId: string): Promise<void> {
|
async remove(workflowId: string): Promise<void> {
|
||||||
|
|
||||||
if (this.activeWorkflows !== null) {
|
if (this.activeWorkflows !== null) {
|
||||||
// Remove all the webhooks of the workflow
|
// Remove all the webhooks of the workflow
|
||||||
try {
|
try {
|
||||||
await this.removeWorkflowWebhooks(workflowId);
|
await this.removeWorkflowWebhooks(workflowId);
|
||||||
} catch (error) {
|
} 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) {
|
if (this.activationErrors[workflowId] !== undefined) {
|
||||||
|
@ -576,8 +776,6 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let workflowRunnerInstance: ActiveWorkflowRunner | undefined;
|
let workflowRunnerInstance: ActiveWorkflowRunner | undefined;
|
||||||
|
|
||||||
export function getInstance(): ActiveWorkflowRunner {
|
export function getInstance(): ActiveWorkflowRunner {
|
||||||
|
|
|
@ -1,32 +1,30 @@
|
||||||
import {
|
import { ICredentialType, ICredentialTypes as ICredentialTypesInterface } from 'n8n-workflow';
|
||||||
ICredentialType,
|
|
||||||
ICredentialTypes as ICredentialTypesInterface,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line import/no-cycle
|
||||||
CredentialsOverwrites,
|
import { CredentialsOverwrites, ICredentialsTypeData } from '.';
|
||||||
ICredentialsTypeData,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
class CredentialTypesClass implements ICredentialTypesInterface {
|
class CredentialTypesClass implements ICredentialTypesInterface {
|
||||||
|
|
||||||
credentialTypes: ICredentialsTypeData = {};
|
credentialTypes: ICredentialsTypeData = {};
|
||||||
|
|
||||||
|
|
||||||
async init(credentialTypes: ICredentialsTypeData): Promise<void> {
|
async init(credentialTypes: ICredentialsTypeData): Promise<void> {
|
||||||
this.credentialTypes = credentialTypes;
|
this.credentialTypes = credentialTypes;
|
||||||
|
|
||||||
// Load the credentials overwrites if any exist
|
// Load the credentials overwrites if any exist
|
||||||
const credentialsOverwrites = CredentialsOverwrites().getAll();
|
const credentialsOverwrites = CredentialsOverwrites().getAll();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const credentialType of Object.keys(credentialsOverwrites)) {
|
for (const credentialType of Object.keys(credentialsOverwrites)) {
|
||||||
if (credentialTypes[credentialType] === undefined) {
|
if (credentialTypes[credentialType] === undefined) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add which properties got overwritten that the Editor-UI knows
|
// Add which properties got overwritten that the Editor-UI knows
|
||||||
// which properties it should hide
|
// 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;
|
let credentialTypesInstance: CredentialTypesClass | undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export function CredentialTypes(): CredentialTypesClass {
|
export function CredentialTypes(): CredentialTypesClass {
|
||||||
if (credentialTypesInstance === undefined) {
|
if (credentialTypesInstance === undefined) {
|
||||||
credentialTypesInstance = new CredentialTypesClass();
|
credentialTypesInstance = new CredentialTypesClass();
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import {
|
import { Credentials } from 'n8n-core';
|
||||||
Credentials,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ICredentialDataDecryptedObject,
|
ICredentialDataDecryptedObject,
|
||||||
|
@ -17,29 +15,24 @@ import {
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line import/no-cycle
|
||||||
CredentialsOverwrites,
|
import { CredentialsOverwrites, CredentialTypes, Db, ICredentialsDb } from '.';
|
||||||
CredentialTypes,
|
|
||||||
Db,
|
|
||||||
ICredentialsDb,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
|
|
||||||
const mockNodeTypes: INodeTypes = {
|
const mockNodeTypes: INodeTypes = {
|
||||||
nodeTypes: {},
|
nodeTypes: {},
|
||||||
init: async (nodeTypes?: INodeTypeData): Promise<void> => { },
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
init: async (nodeTypes?: INodeTypeData): Promise<void> => {},
|
||||||
getAll: (): INodeType[] => {
|
getAll: (): INodeType[] => {
|
||||||
// Does not get used in Workflow so no need to return it
|
// Does not get used in Workflow so no need to return it
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
getByName: (nodeType: string): INodeType | undefined => {
|
getByName: (nodeType: string): INodeType | undefined => {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export class CredentialsHelper extends ICredentialsHelper {
|
export class CredentialsHelper extends ICredentialsHelper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the credentials instance
|
* Returns the credentials instance
|
||||||
*
|
*
|
||||||
|
@ -49,23 +42,27 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
* @memberof CredentialsHelper
|
* @memberof CredentialsHelper
|
||||||
*/
|
*/
|
||||||
async getCredentials(name: string, type: string): Promise<Credentials> {
|
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) {
|
if (credentialsDb === undefined || credentialsDb.length === 0) {
|
||||||
throw new Error(`No credentials of type "${type}" exist.`);
|
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) {
|
if (credential === undefined) {
|
||||||
throw new Error(`No credentials with name "${name}" exist for type "${type}".`);
|
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
|
* Returns all the properties of the credentials with the given name
|
||||||
*
|
*
|
||||||
|
@ -86,6 +83,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
const combineProperties = [] as INodeProperties[];
|
const combineProperties = [] as INodeProperties[];
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const credentialsTypeName of credentialTypeData.extends) {
|
for (const credentialsTypeName of credentialTypeData.extends) {
|
||||||
const mergeCredentialProperties = this.getCredentialsProperties(credentialsTypeName);
|
const mergeCredentialProperties = this.getCredentialsProperties(credentialsTypeName);
|
||||||
NodeHelpers.mergeNodeProperties(combineProperties, mergeCredentialProperties);
|
NodeHelpers.mergeNodeProperties(combineProperties, mergeCredentialProperties);
|
||||||
|
@ -97,7 +95,6 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
return combineProperties;
|
return combineProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the decrypted credential data with applied overwrites
|
* Returns the decrypted credential data with applied overwrites
|
||||||
*
|
*
|
||||||
|
@ -107,7 +104,13 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
* @returns {ICredentialDataDecryptedObject}
|
* @returns {ICredentialDataDecryptedObject}
|
||||||
* @memberof CredentialsHelper
|
* @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 credentials = await this.getCredentials(name, type);
|
||||||
|
|
||||||
const decryptedDataOriginal = credentials.getData(this.encryptionKey);
|
const decryptedDataOriginal = credentials.getData(this.encryptionKey);
|
||||||
|
@ -116,10 +119,14 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
return decryptedDataOriginal;
|
return decryptedDataOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.applyDefaultsAndOverwrites(decryptedDataOriginal, type, mode, expressionResolveValues);
|
return this.applyDefaultsAndOverwrites(
|
||||||
|
decryptedDataOriginal,
|
||||||
|
type,
|
||||||
|
mode,
|
||||||
|
expressionResolveValues,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies credential default data and overwrites
|
* Applies credential default data and overwrites
|
||||||
*
|
*
|
||||||
|
@ -128,11 +135,21 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
* @returns {ICredentialDataDecryptedObject}
|
* @returns {ICredentialDataDecryptedObject}
|
||||||
* @memberof CredentialsHelper
|
* @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);
|
const credentialsProperties = this.getCredentialsProperties(type);
|
||||||
|
|
||||||
// Add the default credential values
|
// 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) {
|
if (decryptedDataOriginal.oauthTokenData !== undefined) {
|
||||||
// The OAuth data gets removed as it is not defined specifically as a parameter
|
// 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) {
|
if (expressionResolveValues) {
|
||||||
try {
|
try {
|
||||||
const workflow = new Workflow({ nodes: Object.values(expressionResolveValues.workflow.nodes), connections: expressionResolveValues.workflow.connectionsBySourceNode, active: false, nodeTypes: expressionResolveValues.workflow.nodeTypes });
|
const workflow = new Workflow({
|
||||||
decryptedData = workflow.expression.getParameterValue(decryptedData as INodeParameters, expressionResolveValues.runExecutionData, expressionResolveValues.runIndex, expressionResolveValues.itemIndex, expressionResolveValues.node.name, expressionResolveValues.connectionInputData, mode, {}, false, decryptedData) as ICredentialDataDecryptedObject;
|
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) {
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
e.message += ' [Error resolving credentials]';
|
e.message += ' [Error resolving credentials]';
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -157,18 +191,30 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
parameters: {} as INodeParameters,
|
parameters: {} as INodeParameters,
|
||||||
} as INode;
|
} 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
|
// 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
|
// Load and apply the credentials overwrites if any exist
|
||||||
const credentialsOverwrites = CredentialsOverwrites();
|
const credentialsOverwrites = CredentialsOverwrites();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return credentialsOverwrites.applyOverwrite(type, decryptedData);
|
return credentialsOverwrites.applyOverwrite(type, decryptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates credentials in the database
|
* Updates credentials in the database
|
||||||
*
|
*
|
||||||
|
@ -178,10 +224,15 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof CredentialsHelper
|
* @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);
|
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
|
// The first time executeWorkflow gets called the Database has
|
||||||
// to get initialized first
|
// to get initialized first
|
||||||
await Db.init();
|
await Db.init();
|
||||||
|
@ -201,7 +252,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
type,
|
type,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
await Db.collections.Credentials!.update(findQuery, newCredentialsData);
|
await Db.collections.Credentials!.update(findQuery, newCredentialsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
import {
|
/* eslint-disable no-underscore-dangle */
|
||||||
ICredentialDataDecryptedObject,
|
import { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CredentialTypes,
|
|
||||||
GenericHelpers,
|
|
||||||
ICredentialsOverwrite,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { CredentialTypes, GenericHelpers, ICredentialsOverwrite } from '.';
|
||||||
|
|
||||||
class CredentialsOverwritesClass {
|
class CredentialsOverwritesClass {
|
||||||
|
|
||||||
private credentialTypes = CredentialTypes();
|
private credentialTypes = CredentialTypes();
|
||||||
private overwriteData: ICredentialsOverwrite = {};
|
|
||||||
private resolvedTypes: string[] = [];
|
|
||||||
|
|
||||||
|
private overwriteData: ICredentialsOverwrite = {};
|
||||||
|
|
||||||
|
private resolvedTypes: string[] = [];
|
||||||
|
|
||||||
async init(overwriteData?: ICredentialsOverwrite) {
|
async init(overwriteData?: ICredentialsOverwrite) {
|
||||||
if (overwriteData !== undefined) {
|
if (overwriteData !== undefined) {
|
||||||
|
@ -24,9 +19,10 @@ class CredentialsOverwritesClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await GenericHelpers.getConfigValue('credentials.overwrite.data') as string;
|
const data = (await GenericHelpers.getConfigValue('credentials.overwrite.data')) as string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-shadow
|
||||||
const overwriteData = JSON.parse(data);
|
const overwriteData = JSON.parse(data);
|
||||||
this.__setData(overwriteData);
|
this.__setData(overwriteData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -34,10 +30,10 @@ class CredentialsOverwritesClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
__setData(overwriteData: ICredentialsOverwrite) {
|
__setData(overwriteData: ICredentialsOverwrite) {
|
||||||
this.overwriteData = overwriteData;
|
this.overwriteData = overwriteData;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const credentialTypeData of this.credentialTypes.getAll()) {
|
for (const credentialTypeData of this.credentialTypes.getAll()) {
|
||||||
const type = credentialTypeData.name;
|
const type = credentialTypeData.name;
|
||||||
|
|
||||||
|
@ -49,29 +45,30 @@ class CredentialsOverwritesClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
applyOverwrite(type: string, data: ICredentialDataDecryptedObject) {
|
applyOverwrite(type: string, data: ICredentialDataDecryptedObject) {
|
||||||
|
|
||||||
const overwrites = this.get(type);
|
const overwrites = this.get(type);
|
||||||
|
|
||||||
if (overwrites === undefined) {
|
if (overwrites === undefined) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const returnData = JSON.parse(JSON.stringify(data));
|
const returnData = JSON.parse(JSON.stringify(data));
|
||||||
// Overwrite only if there is currently no data set
|
// Overwrite only if there is currently no data set
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const key of Object.keys(overwrites)) {
|
for (const key of Object.keys(overwrites)) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
if ([null, undefined, ''].includes(returnData[key])) {
|
if ([null, undefined, ''].includes(returnData[key])) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
returnData[key] = overwrites[key];
|
returnData[key] = overwrites[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
__getExtended(type: string): ICredentialDataDecryptedObject | undefined {
|
__getExtended(type: string): ICredentialDataDecryptedObject | undefined {
|
||||||
|
|
||||||
if (this.resolvedTypes.includes(type)) {
|
if (this.resolvedTypes.includes(type)) {
|
||||||
// Type got already resolved and can so returned directly
|
// Type got already resolved and can so returned directly
|
||||||
return this.overwriteData[type];
|
return this.overwriteData[type];
|
||||||
|
@ -89,6 +86,7 @@ class CredentialsOverwritesClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
const overwrites: ICredentialDataDecryptedObject = {};
|
const overwrites: ICredentialDataDecryptedObject = {};
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const credentialsTypeName of credentialTypeData.extends) {
|
for (const credentialsTypeName of credentialTypeData.extends) {
|
||||||
Object.assign(overwrites, this.__getExtended(credentialsTypeName));
|
Object.assign(overwrites, this.__getExtended(credentialsTypeName));
|
||||||
}
|
}
|
||||||
|
@ -102,20 +100,18 @@ class CredentialsOverwritesClass {
|
||||||
return overwrites;
|
return overwrites;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
get(type: string): ICredentialDataDecryptedObject | undefined {
|
get(type: string): ICredentialDataDecryptedObject | undefined {
|
||||||
return this.overwriteData[type];
|
return this.overwriteData[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getAll(): ICredentialsOverwrite {
|
getAll(): ICredentialsOverwrite {
|
||||||
return this.overwriteData;
|
return this.overwriteData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let credentialsOverwritesInstance: CredentialsOverwritesClass | undefined;
|
let credentialsOverwritesInstance: CredentialsOverwritesClass | undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export function CredentialsOverwrites(): CredentialsOverwritesClass {
|
export function CredentialsOverwrites(): CredentialsOverwritesClass {
|
||||||
if (credentialsOverwritesInstance === undefined) {
|
if (credentialsOverwritesInstance === undefined) {
|
||||||
credentialsOverwritesInstance = new CredentialsOverwritesClass();
|
credentialsOverwritesInstance = new CredentialsOverwritesClass();
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
DatabaseType,
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
GenericHelpers,
|
/* eslint-disable no-case-declarations */
|
||||||
IDatabaseCollections,
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
} from './';
|
import { UserSettings } from 'n8n-core';
|
||||||
|
import { ConnectionOptions, createConnection, getRepository } from 'typeorm';
|
||||||
import {
|
|
||||||
UserSettings,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ConnectionOptions,
|
|
||||||
createConnection,
|
|
||||||
getRepository,
|
|
||||||
} from 'typeorm';
|
|
||||||
|
|
||||||
import { TlsOptions } from 'tls';
|
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';
|
import * as config from '../config';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { entities } from './databases/entities';
|
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,
|
Credentials: null,
|
||||||
Execution: null,
|
Execution: null,
|
||||||
Workflow: null,
|
Workflow: null,
|
||||||
|
@ -28,14 +26,8 @@ export let collections: IDatabaseCollections = {
|
||||||
Tag: null,
|
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> {
|
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();
|
const n8nFolder = UserSettings.getUserN8nFolderPath();
|
||||||
|
|
||||||
let connectionOptions: ConnectionOptions;
|
let connectionOptions: ConnectionOptions;
|
||||||
|
@ -44,13 +36,17 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||||
|
|
||||||
switch (dbType) {
|
switch (dbType) {
|
||||||
case 'postgresdb':
|
case 'postgresdb':
|
||||||
const sslCa = await GenericHelpers.getConfigValue('database.postgresdb.ssl.ca') as string;
|
const sslCa = (await GenericHelpers.getConfigValue('database.postgresdb.ssl.ca')) as string;
|
||||||
const sslCert = await GenericHelpers.getConfigValue('database.postgresdb.ssl.cert') as string;
|
const sslCert = (await GenericHelpers.getConfigValue(
|
||||||
const sslKey = await GenericHelpers.getConfigValue('database.postgresdb.ssl.key') as string;
|
'database.postgresdb.ssl.cert',
|
||||||
const sslRejectUnauthorized = await GenericHelpers.getConfigValue('database.postgresdb.ssl.rejectUnauthorized') as boolean;
|
)) 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;
|
let ssl: TlsOptions | undefined;
|
||||||
if (sslCa !== '' || sslCert !== '' || sslKey !== '' || sslRejectUnauthorized !== true) {
|
if (sslCa !== '' || sslCert !== '' || sslKey !== '' || !sslRejectUnauthorized) {
|
||||||
ssl = {
|
ssl = {
|
||||||
ca: sslCa || undefined,
|
ca: sslCa || undefined,
|
||||||
cert: sslCert || undefined,
|
cert: sslCert || undefined,
|
||||||
|
@ -62,11 +58,11 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||||
connectionOptions = {
|
connectionOptions = {
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
entityPrefix,
|
entityPrefix,
|
||||||
database: await GenericHelpers.getConfigValue('database.postgresdb.database') as string,
|
database: (await GenericHelpers.getConfigValue('database.postgresdb.database')) as string,
|
||||||
host: await GenericHelpers.getConfigValue('database.postgresdb.host') as string,
|
host: (await GenericHelpers.getConfigValue('database.postgresdb.host')) as string,
|
||||||
password: await GenericHelpers.getConfigValue('database.postgresdb.password') as string,
|
password: (await GenericHelpers.getConfigValue('database.postgresdb.password')) as string,
|
||||||
port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number,
|
port: (await GenericHelpers.getConfigValue('database.postgresdb.port')) as number,
|
||||||
username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string,
|
username: (await GenericHelpers.getConfigValue('database.postgresdb.user')) as string,
|
||||||
schema: config.get('database.postgresdb.schema'),
|
schema: config.get('database.postgresdb.schema'),
|
||||||
migrations: postgresMigrations,
|
migrations: postgresMigrations,
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
|
@ -80,12 +76,12 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||||
case 'mysqldb':
|
case 'mysqldb':
|
||||||
connectionOptions = {
|
connectionOptions = {
|
||||||
type: dbType === 'mysqldb' ? 'mysql' : 'mariadb',
|
type: dbType === 'mysqldb' ? 'mysql' : 'mariadb',
|
||||||
database: await GenericHelpers.getConfigValue('database.mysqldb.database') as string,
|
database: (await GenericHelpers.getConfigValue('database.mysqldb.database')) as string,
|
||||||
entityPrefix,
|
entityPrefix,
|
||||||
host: await GenericHelpers.getConfigValue('database.mysqldb.host') as string,
|
host: (await GenericHelpers.getConfigValue('database.mysqldb.host')) as string,
|
||||||
password: await GenericHelpers.getConfigValue('database.mysqldb.password') as string,
|
password: (await GenericHelpers.getConfigValue('database.mysqldb.password')) as string,
|
||||||
port: await GenericHelpers.getConfigValue('database.mysqldb.port') as number,
|
port: (await GenericHelpers.getConfigValue('database.mysqldb.port')) as number,
|
||||||
username: await GenericHelpers.getConfigValue('database.mysqldb.user') as string,
|
username: (await GenericHelpers.getConfigValue('database.mysqldb.user')) as string,
|
||||||
migrations: mysqlMigrations,
|
migrations: mysqlMigrations,
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
migrationsTableName: `${entityPrefix}migrations`,
|
migrationsTableName: `${entityPrefix}migrations`,
|
||||||
|
@ -122,8 +118,10 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||||
// n8n knows it has changed. Happens only on sqlite.
|
// n8n knows it has changed. Happens only on sqlite.
|
||||||
let migrations = [];
|
let migrations = [];
|
||||||
try {
|
try {
|
||||||
migrations = await connection.query(`SELECT id FROM ${entityPrefix}migrations where name = "MakeStoppedAtNullable1607431743769"`);
|
migrations = await connection.query(
|
||||||
} catch(error) {
|
`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.
|
// 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',
|
transaction: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
if (migrations.length === 0) {
|
if (migrations.length === 0) {
|
||||||
await connection.close();
|
await connection.close();
|
||||||
connection = await createConnection(connectionOptions);
|
connection = await createConnection(connectionOptions);
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
Db,
|
/* eslint-disable import/no-dynamic-require */
|
||||||
IExternalHooksClass,
|
/* eslint-disable no-restricted-syntax */
|
||||||
IExternalHooksFileData,
|
// eslint-disable-next-line import/no-cycle
|
||||||
IExternalHooksFunctions,
|
import { Db, IExternalHooksClass, IExternalHooksFileData, IExternalHooksFunctions } from '.';
|
||||||
} from './';
|
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
|
||||||
|
|
||||||
class ExternalHooksClass implements IExternalHooksClass {
|
class ExternalHooksClass implements IExternalHooksClass {
|
||||||
|
|
||||||
externalHooks: {
|
externalHooks: {
|
||||||
[key: string]: Array<() => {}>
|
[key: string]: Array<() => {}>;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
initDidRun = false;
|
initDidRun = false;
|
||||||
|
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
if (this.initDidRun === true) {
|
if (this.initDidRun) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +23,6 @@ class ExternalHooksClass implements IExternalHooksClass {
|
||||||
this.initDidRun = true;
|
this.initDidRun = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async reload(externalHooks?: IExternalHooksFileData) {
|
async reload(externalHooks?: IExternalHooksFileData) {
|
||||||
this.externalHooks = {};
|
this.externalHooks = {};
|
||||||
|
|
||||||
|
@ -37,7 +33,6 @@ class ExternalHooksClass implements IExternalHooksClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async loadHooksFiles(reload = false) {
|
async loadHooksFiles(reload = false) {
|
||||||
const externalHookFiles = config.get('externalHookFiles').split(':');
|
const externalHookFiles = config.get('externalHookFiles').split(':');
|
||||||
|
|
||||||
|
@ -46,21 +41,22 @@ class ExternalHooksClass implements IExternalHooksClass {
|
||||||
hookFilePath = hookFilePath.trim();
|
hookFilePath = hookFilePath.trim();
|
||||||
if (hookFilePath !== '') {
|
if (hookFilePath !== '') {
|
||||||
try {
|
try {
|
||||||
|
if (reload) {
|
||||||
if (reload === true) {
|
|
||||||
delete require.cache[require.resolve(hookFilePath)];
|
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;
|
const hookFile = require(hookFilePath) as IExternalHooksFileData;
|
||||||
this.loadHooks(hookFile);
|
this.loadHooks(hookFile);
|
||||||
} catch (error) {
|
} 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}`);
|
throw new Error(`Problem loading external hook file "${hookFilePath}": ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loadHooks(hookFileData: IExternalHooksFileData) {
|
loadHooks(hookFileData: IExternalHooksFileData) {
|
||||||
for (const resource of Object.keys(hookFileData)) {
|
for (const resource of Object.keys(hookFileData)) {
|
||||||
for (const operation of Object.keys(hookFileData[resource])) {
|
for (const operation of Object.keys(hookFileData[resource])) {
|
||||||
|
@ -71,13 +67,17 @@ class ExternalHooksClass implements IExternalHooksClass {
|
||||||
this.externalHooks[hookString] = [];
|
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],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async run(hookName: string, hookParameters?: any[]): Promise<void> { // tslint:disable-line:no-any
|
async run(hookName: string, hookParameters?: any[]): Promise<void> {
|
||||||
const externalHookFunctions: IExternalHooksFunctions = {
|
const externalHookFunctions: IExternalHooksFunctions = {
|
||||||
dbCollections: Db.collections,
|
dbCollections: Db.collections,
|
||||||
};
|
};
|
||||||
|
@ -86,22 +86,20 @@ class ExternalHooksClass implements IExternalHooksClass {
|
||||||
return;
|
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);
|
await externalHookFunction.apply(externalHookFunctions, hookParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
exists(hookName: string): boolean {
|
exists(hookName: string): boolean {
|
||||||
return !!this.externalHooks[hookName];
|
return !!this.externalHooks[hookName];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let externalHooksInstance: ExternalHooksClass | undefined;
|
let externalHooksInstance: ExternalHooksClass | undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export function ExternalHooks(): ExternalHooksClass {
|
export function ExternalHooks(): ExternalHooksClass {
|
||||||
if (externalHooksInstance === undefined) {
|
if (externalHooksInstance === undefined) {
|
||||||
externalHooksInstance = new ExternalHooksClass();
|
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 * as express from 'express';
|
||||||
import { join as pathJoin } from 'path';
|
import { join as pathJoin } from 'path';
|
||||||
import { readFile as fsReadFile } from 'fs/promises';
|
import { readFile as fsReadFile } from 'fs/promises';
|
||||||
import { readFileSync as fsReadFileSync } from 'fs';
|
import { readFileSync as fsReadFileSync } from 'fs';
|
||||||
import { IDataObject } from 'n8n-workflow';
|
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;
|
let versionCache: IPackageVersions | undefined;
|
||||||
|
|
||||||
|
@ -16,18 +22,17 @@ let versionCache: IPackageVersions | undefined;
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getBaseUrl(): string {
|
export function getBaseUrl(): string {
|
||||||
const protocol = config.get('protocol') as string;
|
const protocol = config.get('protocol');
|
||||||
const host = config.get('host') as string;
|
const host = config.get('host');
|
||||||
const port = config.get('port') as number;
|
const port = config.get('port');
|
||||||
const path = config.get('path') as string;
|
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}${path}`;
|
||||||
}
|
}
|
||||||
return `${protocol}://${host}:${port}${path}`;
|
return `${protocol}://${host}:${port}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the session id if one is set
|
* 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;
|
return req.headers.sessionid as string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns information which version of the packages are installed
|
* Returns information which version of the packages are installed
|
||||||
*
|
*
|
||||||
|
@ -51,10 +55,12 @@ export async function getVersions(): Promise<IPackageVersions> {
|
||||||
return versionCache;
|
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);
|
const packageData = JSON.parse(packageFile);
|
||||||
|
|
||||||
versionCache = {
|
versionCache = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
cli: packageData.version,
|
cli: packageData.version,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -71,9 +77,11 @@ export async function getVersions(): Promise<IPackageVersions> {
|
||||||
function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDataObject {
|
function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDataObject {
|
||||||
const configKeyParts = configKey.split('.');
|
const configKeyParts = configKey.split('.');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const key of configKeyParts) {
|
for (const key of configKeyParts) {
|
||||||
if (configSchema[key] === undefined) {
|
if (configSchema[key] === undefined) {
|
||||||
throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`);
|
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) {
|
} else if ((configSchema[key]! as IDataObject)._cvtProperties === undefined) {
|
||||||
configSchema = configSchema[key] as IDataObject;
|
configSchema = configSchema[key] as IDataObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -90,7 +98,9 @@ function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDat
|
||||||
* @param {string} configKey The key of the config data to get
|
* @param {string} configKey The key of the config data to get
|
||||||
* @returns {(Promise<string | boolean | number | undefined>)}
|
* @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
|
// Get the environment variable
|
||||||
const configSchema = config.getSchema();
|
const configSchema = config.getSchema();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -102,7 +112,7 @@ export async function getConfigValue(configKey: string): Promise<string | boolea
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if special file enviroment variable exists
|
// Check if special file enviroment variable exists
|
||||||
const fileEnvironmentVariable = process.env[currentSchema.env + '_FILE'];
|
const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`];
|
||||||
if (fileEnvironmentVariable === undefined) {
|
if (fileEnvironmentVariable === undefined) {
|
||||||
// Does not exist, so return value from config
|
// Does not exist, so return value from config
|
||||||
return config.get(configKey);
|
return config.get(configKey);
|
||||||
|
@ -110,7 +120,7 @@ export async function getConfigValue(configKey: string): Promise<string | boolea
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await fsReadFile(fileEnvironmentVariable, 'utf8') as string;
|
data = await fsReadFile(fileEnvironmentVariable, 'utf8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`);
|
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
|
// Check if special file enviroment variable exists
|
||||||
const fileEnvironmentVariable = process.env[currentSchema.env + '_FILE'];
|
const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`];
|
||||||
if (fileEnvironmentVariable === undefined) {
|
if (fileEnvironmentVariable === undefined) {
|
||||||
// Does not exist, so return value from config
|
// Does not exist, so return value from config
|
||||||
return config.get(configKey);
|
return config.get(configKey);
|
||||||
|
@ -149,7 +159,7 @@ export function getConfigValueSync(configKey: string): string | boolean | number
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = fsReadFileSync(fileEnvironmentVariable, 'utf8') as string;
|
data = fsReadFileSync(fileEnvironmentVariable, 'utf8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`);
|
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 {
|
import {
|
||||||
ExecutionError,
|
ExecutionError,
|
||||||
ICredentialDataDecryptedObject,
|
ICredentialDataDecryptedObject,
|
||||||
|
@ -10,15 +12,15 @@ import {
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
IWorkflowBase as IWorkflowBaseWorkflow,
|
IWorkflowBase as IWorkflowBaseWorkflow,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
IWorkflowCredentials,
|
IWorkflowCredentials,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import { IDeferredPromise, WorkflowExecute } from 'n8n-core';
|
||||||
IDeferredPromise, WorkflowExecute,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import * as PCancelable from 'p-cancelable';
|
import * as PCancelable from 'p-cancelable';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ export interface ITagDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UsageCount = {
|
export type UsageCount = {
|
||||||
usageCount: number
|
usageCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ITagWithCountDb = ITagDb & UsageCount;
|
export type ITagWithCountDb = ITagDb & UsageCount;
|
||||||
|
@ -214,7 +216,6 @@ export interface IExecutionsSummary {
|
||||||
workflowName?: string;
|
workflowName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IExecutionsCurrentSummary {
|
export interface IExecutionsCurrentSummary {
|
||||||
id: string;
|
id: string;
|
||||||
retryOf?: string;
|
retryOf?: string;
|
||||||
|
@ -223,7 +224,6 @@ export interface IExecutionsCurrentSummary {
|
||||||
workflowId: string;
|
workflowId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IExecutionDeleteFilter {
|
export interface IExecutionDeleteFilter {
|
||||||
deleteBefore?: Date;
|
deleteBefore?: Date;
|
||||||
filters?: IDataObject;
|
filters?: IDataObject;
|
||||||
|
@ -240,22 +240,33 @@ export interface IExecutingWorkflowData {
|
||||||
|
|
||||||
export interface IExternalHooks {
|
export interface IExternalHooks {
|
||||||
credentials?: {
|
credentials?: {
|
||||||
create?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsEncrypted): Promise<void>; }>
|
create?: Array<{
|
||||||
delete?: Array<{ (this: IExternalHooksFunctions, credentialId: string): Promise<void>; }>
|
(this: IExternalHooksFunctions, credentialsData: ICredentialsEncrypted): Promise<void>;
|
||||||
update?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsDb): Promise<void>; }>
|
}>;
|
||||||
|
delete?: Array<{ (this: IExternalHooksFunctions, credentialId: string): Promise<void> }>;
|
||||||
|
update?: Array<{
|
||||||
|
(this: IExternalHooksFunctions, credentialsData: ICredentialsDb): Promise<void>;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
workflow?: {
|
workflow?: {
|
||||||
activate?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void>; }>
|
activate?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void> }>;
|
||||||
create?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowBase): Promise<void>; }>
|
create?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowBase): Promise<void> }>;
|
||||||
delete?: Array<{ (this: IExternalHooksFunctions, workflowId: string): Promise<void>; }>
|
delete?: Array<{ (this: IExternalHooksFunctions, workflowId: string): Promise<void> }>;
|
||||||
execute?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb, mode: WorkflowExecuteMode): Promise<void>; }>
|
execute?: Array<{
|
||||||
update?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void>; }>
|
(
|
||||||
|
this: IExternalHooksFunctions,
|
||||||
|
workflowData: IWorkflowDb,
|
||||||
|
mode: WorkflowExecuteMode,
|
||||||
|
): Promise<void>;
|
||||||
|
}>;
|
||||||
|
update?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void> }>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExternalHooksFileData {
|
export interface IExternalHooksFileData {
|
||||||
[key: string]: {
|
[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 {
|
export interface IExternalHooksClass {
|
||||||
init(): Promise<void>;
|
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 {
|
export interface IN8nConfig {
|
||||||
|
@ -295,12 +307,14 @@ export interface IN8nConfigEndpoints {
|
||||||
webhookTest: string;
|
webhookTest: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/export
|
||||||
export interface IN8nConfigExecutions {
|
export interface IN8nConfigExecutions {
|
||||||
saveDataOnError: SaveExecutionDataType;
|
saveDataOnError: SaveExecutionDataType;
|
||||||
saveDataOnSuccess: SaveExecutionDataType;
|
saveDataOnSuccess: SaveExecutionDataType;
|
||||||
saveDataManualExecutions: boolean;
|
saveDataManualExecutions: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/export
|
||||||
export interface IN8nConfigExecutions {
|
export interface IN8nConfigExecutions {
|
||||||
saveDataOnError: SaveExecutionDataType;
|
saveDataOnError: SaveExecutionDataType;
|
||||||
saveDataOnSuccess: SaveExecutionDataType;
|
saveDataOnSuccess: SaveExecutionDataType;
|
||||||
|
@ -409,13 +423,11 @@ export interface IPushDataNodeExecuteAfter {
|
||||||
nodeName: string;
|
nodeName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IPushDataNodeExecuteBefore {
|
export interface IPushDataNodeExecuteBefore {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
nodeName: string;
|
nodeName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IPushDataTestWebhook {
|
export interface IPushDataTestWebhook {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
workflowId: string;
|
workflowId: string;
|
||||||
|
@ -432,7 +444,6 @@ export interface IResponseCallbackData {
|
||||||
responseCode?: number;
|
responseCode?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface ITransferNodeTypes {
|
export interface ITransferNodeTypes {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
className: string;
|
className: string;
|
||||||
|
@ -440,7 +451,6 @@ export interface ITransferNodeTypes {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IWorkflowErrorData {
|
export interface IWorkflowErrorData {
|
||||||
[key: string]: IDataObject | string | number | ExecutionError;
|
[key: string]: IDataObject | string | number | ExecutionError;
|
||||||
execution: {
|
execution: {
|
||||||
|
@ -457,7 +467,8 @@ export interface IWorkflowErrorData {
|
||||||
|
|
||||||
export interface IProcessMessageDataHook {
|
export interface IProcessMessageDataHook {
|
||||||
hook: string;
|
hook: string;
|
||||||
parameters: any[]; // tslint:disable-line:no-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
parameters: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowExecutionDataProcess {
|
export interface IWorkflowExecutionDataProcess {
|
||||||
|
@ -471,7 +482,6 @@ export interface IWorkflowExecutionDataProcess {
|
||||||
workflowData: IWorkflowBase;
|
workflowData: IWorkflowBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IWorkflowExecutionDataProcessWithExecution extends IWorkflowExecutionDataProcess {
|
export interface IWorkflowExecutionDataProcessWithExecution extends IWorkflowExecutionDataProcess {
|
||||||
credentialsOverwrite: ICredentialsOverwrite;
|
credentialsOverwrite: ICredentialsOverwrite;
|
||||||
credentialsTypeData: ICredentialsTypeData;
|
credentialsTypeData: ICredentialsTypeData;
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
CUSTOM_EXTENSION_ENV,
|
/* eslint-disable no-prototype-builtins */
|
||||||
UserSettings,
|
/* eslint-disable no-param-reassign */
|
||||||
} from 'n8n-core';
|
/* 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 {
|
import {
|
||||||
CodexData,
|
CodexData,
|
||||||
ICredentialType,
|
ICredentialType,
|
||||||
|
@ -11,12 +18,6 @@ import {
|
||||||
LoggerProxy,
|
LoggerProxy,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import * as config from '../config';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getLogger,
|
|
||||||
} from '../src/Logger';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
access as fsAccess,
|
access as fsAccess,
|
||||||
readdir as fsReaddir,
|
readdir as fsReaddir,
|
||||||
|
@ -25,18 +26,20 @@ import {
|
||||||
} from 'fs/promises';
|
} from 'fs/promises';
|
||||||
import * as glob from 'fast-glob';
|
import * as glob from 'fast-glob';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { getLogger } from './Logger';
|
||||||
|
import * as config from '../config';
|
||||||
|
|
||||||
const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
|
const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
|
||||||
|
|
||||||
|
|
||||||
class LoadNodesAndCredentialsClass {
|
class LoadNodesAndCredentialsClass {
|
||||||
nodeTypes: INodeTypeData = {};
|
nodeTypes: INodeTypeData = {};
|
||||||
|
|
||||||
credentialTypes: {
|
credentialTypes: {
|
||||||
[key: string]: ICredentialType
|
[key: string]: ICredentialType;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
excludeNodes: string[] | undefined = undefined;
|
excludeNodes: string[] | undefined = undefined;
|
||||||
|
|
||||||
includeNodes: string[] | undefined = undefined;
|
includeNodes: string[] | undefined = undefined;
|
||||||
|
|
||||||
nodeModulesPath = '';
|
nodeModulesPath = '';
|
||||||
|
@ -64,6 +67,7 @@ class LoadNodesAndCredentialsClass {
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Folder does not exist so get next one
|
// Folder does not exist so get next one
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +94,9 @@ class LoadNodesAndCredentialsClass {
|
||||||
|
|
||||||
// Add folders from special environment variable
|
// Add folders from special environment variable
|
||||||
if (process.env[CUSTOM_EXTENSION_ENV] !== undefined) {
|
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(';');
|
const customExtensionFolders = process.env[CUSTOM_EXTENSION_ENV]!.split(';');
|
||||||
|
// eslint-disable-next-line prefer-spread
|
||||||
customDirectories.push.apply(customDirectories, customExtensionFolders);
|
customDirectories.push.apply(customDirectories, customExtensionFolders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +105,6 @@ class LoadNodesAndCredentialsClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the names of the packages which could
|
* Returns all the names of the packages which could
|
||||||
* contain n8n nodes
|
* contain n8n nodes
|
||||||
|
@ -120,9 +125,11 @@ class LoadNodesAndCredentialsClass {
|
||||||
if (!(await fsStat(nodeModulesPath)).isDirectory()) {
|
if (!(await fsStat(nodeModulesPath)).isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isN8nNodesPackage) { results.push(`${relativePath}${file}`); }
|
if (isN8nNodesPackage) {
|
||||||
|
results.push(`${relativePath}${file}`);
|
||||||
|
}
|
||||||
if (isNpmScopedPackage) {
|
if (isNpmScopedPackage) {
|
||||||
results.push(...await getN8nNodePackagesRecursive(`${relativePath}${file}/`));
|
results.push(...(await getN8nNodePackagesRecursive(`${relativePath}${file}/`)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
|
@ -138,6 +145,7 @@ class LoadNodesAndCredentialsClass {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadCredentialsFromFile(credentialName: string, filePath: string): 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);
|
const tempModule = require(filePath);
|
||||||
|
|
||||||
let tempCredential: ICredentialType;
|
let tempCredential: ICredentialType;
|
||||||
|
@ -145,7 +153,9 @@ class LoadNodesAndCredentialsClass {
|
||||||
tempCredential = new tempModule[credentialName]() as ICredentialType;
|
tempCredential = new tempModule[credentialName]() as ICredentialType;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TypeError) {
|
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 {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -154,7 +164,6 @@ class LoadNodesAndCredentialsClass {
|
||||||
this.credentialTypes[tempCredential.name] = tempCredential;
|
this.credentialTypes[tempCredential.name] = tempCredential;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a node from a file
|
* Loads a node from a file
|
||||||
*
|
*
|
||||||
|
@ -167,26 +176,34 @@ class LoadNodesAndCredentialsClass {
|
||||||
let tempNode: INodeType;
|
let tempNode: INodeType;
|
||||||
let fullNodeName: string;
|
let fullNodeName: string;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
|
||||||
const tempModule = require(filePath);
|
const tempModule = require(filePath);
|
||||||
try {
|
try {
|
||||||
tempNode = new tempModule[nodeName]() as INodeType;
|
tempNode = new tempModule[nodeName]() as INodeType;
|
||||||
this.addCodex({ node: tempNode, filePath, isCustom: packageName === 'CUSTOM' });
|
this.addCodex({ node: tempNode, filePath, isCustom: packageName === 'CUSTOM' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error(`Error loading node "${nodeName}" from: "${filePath}"`);
|
console.error(`Error loading node "${nodeName}" from: "${filePath}"`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullNodeName = packageName + '.' + tempNode.description.name;
|
// eslint-disable-next-line prefer-const
|
||||||
|
fullNodeName = `${packageName}.${tempNode.description.name}`;
|
||||||
tempNode.description.name = fullNodeName;
|
tempNode.description.name = fullNodeName;
|
||||||
|
|
||||||
if (tempNode.description.icon !== undefined &&
|
if (tempNode.description.icon !== undefined && tempNode.description.icon.startsWith('file:')) {
|
||||||
tempNode.description.icon.startsWith('file:')) {
|
|
||||||
// If a file icon gets used add the full path
|
// 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) {
|
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)) {
|
if (this.includeNodes !== undefined && !this.includeNodes.includes(fullNodeName)) {
|
||||||
|
@ -212,7 +229,9 @@ class LoadNodesAndCredentialsClass {
|
||||||
* @returns {CodexData}
|
* @returns {CodexData}
|
||||||
*/
|
*/
|
||||||
getCodex(filePath: string): 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
|
const { categories, subcategories, alias } = require(`${filePath}on`); // .js to .json
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return {
|
return {
|
||||||
...(categories && { categories }),
|
...(categories && { categories }),
|
||||||
...(subcategories && { subcategories }),
|
...(subcategories && { subcategories }),
|
||||||
|
@ -230,11 +249,7 @@ class LoadNodesAndCredentialsClass {
|
||||||
* @param obj.isCustom Whether the node is custom
|
* @param obj.isCustom Whether the node is custom
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
addCodex({ node, filePath, isCustom }: {
|
addCodex({ node, filePath, isCustom }: { node: INodeType; filePath: string; isCustom: boolean }) {
|
||||||
node: INodeType;
|
|
||||||
filePath: string;
|
|
||||||
isCustom: boolean;
|
|
||||||
}) {
|
|
||||||
try {
|
try {
|
||||||
const codex = this.getCodex(filePath);
|
const codex = this.getCodex(filePath);
|
||||||
|
|
||||||
|
@ -246,6 +261,7 @@ class LoadNodesAndCredentialsClass {
|
||||||
|
|
||||||
node.description.codex = codex;
|
node.description.codex = codex;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
this.logger.debug(`No codex available for: ${filePath.split('/').pop()}`);
|
this.logger.debug(`No codex available for: ${filePath.split('/').pop()}`);
|
||||||
|
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
|
@ -264,7 +280,7 @@ class LoadNodesAndCredentialsClass {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadDataFromDirectory(setPackageName: string, directory: string): 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 fileName: string;
|
||||||
let type: string;
|
let type: string;
|
||||||
|
@ -283,7 +299,6 @@ class LoadNodesAndCredentialsClass {
|
||||||
await Promise.all(loadPromises);
|
await Promise.all(loadPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads nodes and credentials from the package with the given name
|
* Loads nodes and credentials from the package with the given name
|
||||||
*
|
*
|
||||||
|
@ -301,10 +316,12 @@ class LoadNodesAndCredentialsClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tempPath: string, filePath: string;
|
let tempPath: string;
|
||||||
|
let filePath: string;
|
||||||
|
|
||||||
// Read all node types
|
// 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)) {
|
if (packageFile.n8n.hasOwnProperty('nodes') && Array.isArray(packageFile.n8n.nodes)) {
|
||||||
for (filePath of packageFile.n8n.nodes) {
|
for (filePath of packageFile.n8n.nodes) {
|
||||||
tempPath = path.join(packagePath, filePath);
|
tempPath = path.join(packagePath, filePath);
|
||||||
|
@ -314,18 +331,21 @@ class LoadNodesAndCredentialsClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read all credential types
|
// 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) {
|
for (filePath of packageFile.n8n.credentials) {
|
||||||
tempPath = path.join(packagePath, filePath);
|
tempPath = path.join(packagePath, filePath);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
[fileName, type] = path.parse(filePath).name.split('.');
|
[fileName, type] = path.parse(filePath).name.split('.');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.loadCredentialsFromFile(fileName, tempPath);
|
this.loadCredentialsFromFile(fileName, tempPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let packagesInformationInstance: LoadNodesAndCredentialsClass | undefined;
|
let packagesInformationInstance: LoadNodesAndCredentialsClass | undefined;
|
||||||
|
|
||||||
export function LoadNodesAndCredentials(): LoadNodesAndCredentialsClass {
|
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 * as winston from 'winston';
|
||||||
|
|
||||||
import {
|
import { IDataObject, ILogger, LogTypes } from 'n8n-workflow';
|
||||||
IDataObject,
|
|
||||||
ILogger,
|
|
||||||
LogTypes,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import * as callsites from 'callsites';
|
import * as callsites from 'callsites';
|
||||||
import { basename } from 'path';
|
import { basename } from 'path';
|
||||||
|
import config = require('../config');
|
||||||
|
|
||||||
class Logger implements ILogger {
|
class Logger implements ILogger {
|
||||||
private logger: winston.Logger;
|
private logger: winston.Logger;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const level = config.get('logs.level');
|
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({
|
this.logger = winston.createLogger({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
level,
|
level,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -28,18 +28,22 @@ class Logger implements ILogger {
|
||||||
winston.format.metadata(),
|
winston.format.metadata(),
|
||||||
winston.format.timestamp(),
|
winston.format.timestamp(),
|
||||||
winston.format.colorize({ all: true }),
|
winston.format.colorize({ all: true }),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
winston.format.printf(({ level, message, timestamp, metadata }) => {
|
winston.format.printf(({ level, message, timestamp, metadata }) => {
|
||||||
return `${timestamp} | ${level.padEnd(18)} | ${message}` + (Object.keys(metadata).length ? ` ${JSON.stringify(metadata)}` : '');
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
}) as winston.Logform.Format
|
return `${timestamp} | ${level.padEnd(18)} | ${message}${
|
||||||
|
Object.keys(metadata).length ? ` ${JSON.stringify(metadata)}` : ''
|
||||||
|
}`;
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
format = winston.format.printf(({ message }) => message) as winston.Logform.Format;
|
format = winston.format.printf(({ message }) => message);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.add(
|
this.logger.add(
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format,
|
format,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,15 +51,15 @@ class Logger implements ILogger {
|
||||||
const fileLogFormat = winston.format.combine(
|
const fileLogFormat = winston.format.combine(
|
||||||
winston.format.timestamp(),
|
winston.format.timestamp(),
|
||||||
winston.format.metadata(),
|
winston.format.metadata(),
|
||||||
winston.format.json()
|
winston.format.json(),
|
||||||
);
|
);
|
||||||
this.logger.add(
|
this.logger.add(
|
||||||
new winston.transports.File({
|
new winston.transports.File({
|
||||||
filename: config.get('logs.file.location'),
|
filename: config.get('logs.file.location'),
|
||||||
format: fileLogFormat,
|
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'),
|
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
|
// We are in runtime, so it means we are looking at compiled js files
|
||||||
const logDetails = {} as IDataObject;
|
const logDetails = {} as IDataObject;
|
||||||
if (callsite[2] !== undefined) {
|
if (callsite[2] !== undefined) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
logDetails.file = basename(callsite[2].getFileName() || '');
|
logDetails.file = basename(callsite[2].getFileName() || '');
|
||||||
const functionName = callsite[2].getFunctionName();
|
const functionName = callsite[2].getFunctionName();
|
||||||
if (functionName) {
|
if (functionName) {
|
||||||
logDetails.function = functionName;
|
logDetails.function = functionName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.logger.log(type, message, {...meta, ...logDetails});
|
this.logger.log(type, message, { ...meta, ...logDetails });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience methods below
|
// Convenience methods below
|
||||||
|
@ -100,11 +105,11 @@ class Logger implements ILogger {
|
||||||
warn(message: string, meta: object = {}) {
|
warn(message: string, meta: object = {}) {
|
||||||
this.log('warn', message, meta);
|
this.log('warn', message, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeLoggerInstance: Logger | undefined;
|
let activeLoggerInstance: Logger | undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export function getLogger() {
|
export function getLogger() {
|
||||||
if (activeLoggerInstance === undefined) {
|
if (activeLoggerInstance === undefined) {
|
||||||
activeLoggerInstance = new Logger();
|
activeLoggerInstance = new Logger();
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
import {
|
import { INodeType, INodeTypeData, INodeTypes, NodeHelpers } from 'n8n-workflow';
|
||||||
INodeType,
|
|
||||||
INodeTypeData,
|
|
||||||
INodeTypes,
|
|
||||||
NodeHelpers,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
|
|
||||||
class NodeTypesClass implements INodeTypes {
|
class NodeTypesClass implements INodeTypes {
|
||||||
|
|
||||||
nodeTypes: INodeTypeData = {};
|
nodeTypes: INodeTypeData = {};
|
||||||
|
|
||||||
|
|
||||||
async init(nodeTypes: INodeTypeData): Promise<void> {
|
async init(nodeTypes: INodeTypeData): Promise<void> {
|
||||||
// Some nodeTypes need to get special parameters applied like the
|
// Some nodeTypes need to get special parameters applied like the
|
||||||
// polling nodes the polling times
|
// polling nodes the polling times
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const nodeTypeData of Object.values(nodeTypes)) {
|
for (const nodeTypeData of Object.values(nodeTypes)) {
|
||||||
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type);
|
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type);
|
||||||
|
|
||||||
if (applyParameters.length) {
|
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;
|
this.nodeTypes = nodeTypes;
|
||||||
|
@ -36,10 +33,9 @@ class NodeTypesClass implements INodeTypes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export function NodeTypes(): NodeTypesClass {
|
export function NodeTypes(): NodeTypesClass {
|
||||||
if (nodeTypesInstance === undefined) {
|
if (nodeTypesInstance === undefined) {
|
||||||
nodeTypesInstance = new NodeTypesClass();
|
nodeTypesInstance = new NodeTypesClass();
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as sseChannel from 'sse-channel';
|
import * as sseChannel from 'sse-channel';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
|
||||||
import {
|
import { LoggerProxy as Logger } from 'n8n-workflow';
|
||||||
IPushData,
|
// eslint-disable-next-line import/no-cycle
|
||||||
IPushDataType,
|
import { IPushData, IPushDataType } from '.';
|
||||||
} from '.';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LoggerProxy as Logger,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
export class Push {
|
export class Push {
|
||||||
private channel: sseChannel;
|
private channel: sseChannel;
|
||||||
|
|
||||||
private connections: {
|
private connections: {
|
||||||
[key: string]: express.Response;
|
[key: string]: express.Response;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, new-cap
|
||||||
this.channel = new sseChannel({
|
this.channel = new sseChannel({
|
||||||
cors: {
|
cors: {
|
||||||
// Allow access also from frontend when developing
|
// 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) => {
|
this.channel.on('disconnect', (channel: string, res: express.Response) => {
|
||||||
if (res.req !== undefined) {
|
if (res.req !== undefined) {
|
||||||
Logger.debug(`Remove editor-UI session`, { sessionId: res.req.query.sessionId });
|
Logger.debug(`Remove editor-UI session`, { sessionId: res.req.query.sessionId });
|
||||||
|
@ -34,7 +33,6 @@ export class Push {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new push connection
|
* Adds a new push connection
|
||||||
*
|
*
|
||||||
|
@ -43,6 +41,7 @@ export class Push {
|
||||||
* @param {express.Response} res The response
|
* @param {express.Response} res The response
|
||||||
* @memberof Push
|
* @memberof Push
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
add(sessionId: string, req: express.Request, res: express.Response) {
|
add(sessionId: string, req: express.Request, res: express.Response) {
|
||||||
Logger.debug(`Add editor-UI session`, { sessionId });
|
Logger.debug(`Add editor-UI session`, { sessionId });
|
||||||
|
|
||||||
|
@ -57,7 +56,6 @@ export class Push {
|
||||||
this.channel.addClient(req, res);
|
this.channel.addClient(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends data to the client which is connected via a specific session
|
* Sends data to the client which is connected via a specific session
|
||||||
*
|
*
|
||||||
|
@ -67,9 +65,8 @@ export class Push {
|
||||||
* @memberof Push
|
* @memberof Push
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||||
|
send(type: IPushDataType, data: any, sessionId?: string) {
|
||||||
send(type: IPushDataType, data: any, sessionId?: string) { // tslint:disable-line:no-any
|
|
||||||
if (sessionId !== undefined && this.connections[sessionId] === undefined) {
|
if (sessionId !== undefined && this.connections[sessionId] === undefined) {
|
||||||
Logger.error(`The session "${sessionId}" is not registred.`, { sessionId });
|
Logger.error(`The session "${sessionId}" is not registred.`, { sessionId });
|
||||||
return;
|
return;
|
||||||
|
@ -79,6 +76,7 @@ export class Push {
|
||||||
|
|
||||||
const sendData: IPushData = {
|
const sendData: IPushData = {
|
||||||
type,
|
type,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,7 +87,6 @@ export class Push {
|
||||||
// Send only to a specific client
|
// Send only to a specific client
|
||||||
this.channel.send(JSON.stringify(sendData), [this.connections[sessionId]]);
|
this.channel.send(JSON.stringify(sendData), [this.connections[sessionId]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as Bull from 'bull';
|
import * as Bull from 'bull';
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { IBullJobData } from './Interfaces';
|
import { IBullJobData } from './Interfaces';
|
||||||
|
|
||||||
export class Queue {
|
export class Queue {
|
||||||
|
@ -18,15 +19,15 @@ export class Queue {
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(jobData: IBullJobData, jobOptions: object): Promise<Bull.Job> {
|
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> {
|
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[]> {
|
async getJobs(jobTypes: Bull.JobStatus[]): Promise<Bull.Job[]> {
|
||||||
return await this.jobQueue.getJobs(jobTypes);
|
return this.jobQueue.getJobs(jobTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBullObjectInstance(): Bull.Queue {
|
getBullObjectInstance(): Bull.Queue {
|
||||||
|
@ -43,7 +44,7 @@ export class Queue {
|
||||||
// Job is already running so tell it to stop
|
// Job is already running so tell it to stop
|
||||||
await job.progress(-1);
|
await job.progress(-1);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
// Job did not get started yet so remove from queue
|
// Job did not get started yet so remove from queue
|
||||||
try {
|
try {
|
||||||
await job.remove();
|
await job.remove();
|
||||||
|
@ -51,7 +52,7 @@ export class Queue {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await job.progress(-1);
|
await job.progress(-1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
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 { Request, Response } from 'express';
|
||||||
import { parse, stringify } from 'flatted';
|
import { parse, stringify } from 'flatted';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import {
|
import {
|
||||||
IExecutionDb,
|
IExecutionDb,
|
||||||
IExecutionFlatted,
|
IExecutionFlatted,
|
||||||
IExecutionFlattedDb,
|
IExecutionFlattedDb,
|
||||||
IExecutionResponse,
|
IExecutionResponse,
|
||||||
IWorkflowDb,
|
IWorkflowDb,
|
||||||
} from './';
|
} from '.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special Error which allows to return also an error code and http status code
|
* Special Error which allows to return also an error code and http status code
|
||||||
|
@ -17,7 +23,6 @@ import {
|
||||||
* @extends {Error}
|
* @extends {Error}
|
||||||
*/
|
*/
|
||||||
export class ResponseError extends Error {
|
export class ResponseError extends Error {
|
||||||
|
|
||||||
// The HTTP status code of response
|
// The HTTP status code of response
|
||||||
httpStatusCode?: number;
|
httpStatusCode?: number;
|
||||||
|
|
||||||
|
@ -35,7 +40,7 @@ export class ResponseError extends Error {
|
||||||
* @param {string} [hint] The error hint to provide a context (webhook related)
|
* @param {string} [hint] The error hint to provide a context (webhook related)
|
||||||
* @memberof ResponseError
|
* @memberof ResponseError
|
||||||
*/
|
*/
|
||||||
constructor(message: string, errorCode?: number, httpStatusCode?: number, hint?:string) {
|
constructor(message: string, errorCode?: number, httpStatusCode?: number, hint?: string) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'ResponseError';
|
this.name = 'ResponseError';
|
||||||
|
|
||||||
|
@ -51,21 +56,23 @@ export class ResponseError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function basicAuthAuthorizationError(resp: Response, realm: string, message?: string) {
|
export function basicAuthAuthorizationError(resp: Response, realm: string, message?: string) {
|
||||||
resp.statusCode = 401;
|
resp.statusCode = 401;
|
||||||
resp.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
|
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) {
|
export function jwtAuthAuthorizationError(resp: Response, message?: string) {
|
||||||
resp.statusCode = 403;
|
resp.statusCode = 403;
|
||||||
resp.json({code: resp.statusCode, message});
|
resp.json({ code: resp.statusCode, message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sendSuccessResponse(
|
||||||
export function sendSuccessResponse(res: Response, data: any, raw?: boolean, responseCode?: number) { // tslint:disable-line:no-any
|
res: Response,
|
||||||
|
data: any,
|
||||||
|
raw?: boolean,
|
||||||
|
responseCode?: number,
|
||||||
|
) {
|
||||||
if (responseCode !== undefined) {
|
if (responseCode !== undefined) {
|
||||||
res.status(responseCode);
|
res.status(responseCode);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +90,6 @@ export function sendSuccessResponse(res: Response, data: any, raw?: boolean, res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function sendErrorResponse(res: Response, error: ResponseError) {
|
export function sendErrorResponse(res: Response, error: ResponseError) {
|
||||||
let httpStatusCode = 500;
|
let httpStatusCode = 500;
|
||||||
if (error.httpStatusCode) {
|
if (error.httpStatusCode) {
|
||||||
|
@ -122,7 +128,6 @@ export function sendErrorResponse(res: Response, error: ResponseError) {
|
||||||
res.status(httpStatusCode).json(response);
|
res.status(httpStatusCode).json(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper function which does not just allow to return Promises it also makes sure that
|
* A helper function which does not just allow to return Promises it also makes sure that
|
||||||
* all the responses have the same format
|
* all the responses have the same format
|
||||||
|
@ -133,8 +138,7 @@ export function sendErrorResponse(res: Response, error: ResponseError) {
|
||||||
* @returns
|
* @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) => {
|
return async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const data = await processFunction(req, res);
|
const data = await processFunction(req, res);
|
||||||
|
@ -148,7 +152,6 @@ export function send(processFunction: (req: Request, res: Response) => Promise<a
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flattens the Execution data.
|
* Flattens the Execution data.
|
||||||
* As it contains a lot of references which normally would be saved as duplicate 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 {
|
export function flattenExecutionData(fullExecutionData: IExecutionDb): IExecutionFlatted {
|
||||||
// Flatten the data
|
// Flatten the data
|
||||||
const returnData: IExecutionFlatted = Object.assign({}, {
|
const returnData: IExecutionFlatted = {
|
||||||
data: stringify(fullExecutionData.data),
|
data: stringify(fullExecutionData.data),
|
||||||
mode: fullExecutionData.mode,
|
mode: fullExecutionData.mode,
|
||||||
|
// @ts-ignore
|
||||||
waitTill: fullExecutionData.waitTill,
|
waitTill: fullExecutionData.waitTill,
|
||||||
startedAt: fullExecutionData.startedAt,
|
startedAt: fullExecutionData.startedAt,
|
||||||
stoppedAt: fullExecutionData.stoppedAt,
|
stoppedAt: fullExecutionData.stoppedAt,
|
||||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
||||||
workflowId: fullExecutionData.workflowId,
|
workflowId: fullExecutionData.workflowId,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
workflowData: fullExecutionData.workflowData!,
|
workflowData: fullExecutionData.workflowData!,
|
||||||
});
|
};
|
||||||
|
|
||||||
if (fullExecutionData.id !== undefined) {
|
if (fullExecutionData.id !== undefined) {
|
||||||
returnData.id = fullExecutionData.id!.toString();
|
returnData.id = fullExecutionData.id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullExecutionData.retryOf !== undefined) {
|
if (fullExecutionData.retryOf !== undefined) {
|
||||||
returnData.retryOf = fullExecutionData.retryOf!.toString();
|
returnData.retryOf = fullExecutionData.retryOf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullExecutionData.retrySuccessId !== undefined) {
|
if (fullExecutionData.retrySuccessId !== undefined) {
|
||||||
returnData.retrySuccessId = fullExecutionData.retrySuccessId!.toString();
|
returnData.retrySuccessId = fullExecutionData.retrySuccessId.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unflattens the Execution data.
|
* Unflattens the Execution data.
|
||||||
*
|
*
|
||||||
|
@ -195,8 +199,7 @@ export function flattenExecutionData(fullExecutionData: IExecutionDb): IExecutio
|
||||||
* @returns {IExecutionResponse}
|
* @returns {IExecutionResponse}
|
||||||
*/
|
*/
|
||||||
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb): IExecutionResponse {
|
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb): IExecutionResponse {
|
||||||
|
const returnData: IExecutionResponse = {
|
||||||
const returnData: IExecutionResponse = Object.assign({}, {
|
|
||||||
id: fullExecutionData.id.toString(),
|
id: fullExecutionData.id.toString(),
|
||||||
workflowData: fullExecutionData.workflowData as IWorkflowDb,
|
workflowData: fullExecutionData.workflowData as IWorkflowDb,
|
||||||
data: parse(fullExecutionData.data),
|
data: parse(fullExecutionData.data),
|
||||||
|
@ -206,7 +209,7 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb):
|
||||||
stoppedAt: fullExecutionData.stoppedAt,
|
stoppedAt: fullExecutionData.stoppedAt,
|
||||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
||||||
workflowId: fullExecutionData.workflowId,
|
workflowId: fullExecutionData.workflowId,
|
||||||
});
|
};
|
||||||
|
|
||||||
return returnData;
|
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 { validate } from 'class-validator';
|
||||||
|
|
||||||
import {
|
import { ResponseHelper } from '.';
|
||||||
ResponseHelper,
|
|
||||||
} from ".";
|
|
||||||
|
|
||||||
import {
|
import { TagEntity } from './databases/entities/TagEntity';
|
||||||
TagEntity,
|
|
||||||
} from "./databases/entities/TagEntity";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ITagWithCountDb,
|
|
||||||
} from "./Interfaces";
|
|
||||||
|
|
||||||
|
import { ITagWithCountDb } from './Interfaces';
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// utils
|
// utils
|
||||||
|
@ -29,7 +25,7 @@ export function sortByRequestOrder(tagsDb: TagEntity[], tagIds: string[]) {
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as { [key: string]: TagEntity });
|
}, {} 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);
|
const errors = await validate(newTag);
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const validationErrorMessage = Object.values(errors[0].constraints!)[0];
|
const validationErrorMessage = Object.values(errors[0].constraints!)[0];
|
||||||
throw new ResponseHelper.ResponseError(validationErrorMessage, undefined, 400);
|
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.
|
* 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()
|
return getConnection()
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.select(`${tablePrefix}tag_entity.id`, 'id')
|
.select(`${tablePrefix}tag_entity.id`, 'id')
|
||||||
.addSelect(`${tablePrefix}tag_entity.name`, 'name')
|
.addSelect(`${tablePrefix}tag_entity.name`, 'name')
|
||||||
.addSelect(`COUNT(${tablePrefix}workflows_tags.workflowId)`, 'usageCount')
|
.addSelect(`COUNT(${tablePrefix}workflows_tags.workflowId)`, 'usageCount')
|
||||||
.from(`${tablePrefix}tag_entity`, 'tag_entity')
|
.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`)
|
.groupBy(`${tablePrefix}tag_entity.id`)
|
||||||
.getRawMany()
|
.getRawMany()
|
||||||
.then(tagsWithCount => {
|
.then((tagsWithCount) => {
|
||||||
tagsWithCount.forEach(tag => {
|
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();
|
tag.id = tag.id.toString();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
tag.usageCount = Number(tag.usageCount);
|
tag.usageCount = Number(tag.usageCount);
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return tagsWithCount;
|
return tagsWithCount;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -90,19 +94,19 @@ export function getTagsWithCountDb(tablePrefix: string): Promise<ITagWithCountDb
|
||||||
/**
|
/**
|
||||||
* Relate a workflow to one or more tags.
|
* 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()
|
return getConnection()
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.insert()
|
.insert()
|
||||||
.into(`${tablePrefix}workflows_tags`)
|
.into(`${tablePrefix}workflows_tags`)
|
||||||
.values(tagIds.map(tagId => ({ workflowId, tagId })))
|
.values(tagIds.map((tagId) => ({ workflowId, tagId })))
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all tags for a workflow during a tag update operation.
|
* 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()
|
return getConnection()
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.delete()
|
.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 * as express from 'express';
|
||||||
|
|
||||||
import {
|
import { ActiveWebhooks } from 'n8n-core';
|
||||||
IResponseCallbackData,
|
|
||||||
IWorkflowDb,
|
|
||||||
Push,
|
|
||||||
ResponseHelper,
|
|
||||||
WebhookHelpers,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActiveWebhooks,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IWebhookData,
|
IWebhookData,
|
||||||
|
@ -20,28 +13,28 @@ import {
|
||||||
WorkflowActivateMode,
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} 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)`;
|
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 {
|
export class TestWebhooks {
|
||||||
|
|
||||||
private testWebhookData: {
|
private testWebhookData: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
timeout: NodeJS.Timeout,
|
timeout: NodeJS.Timeout;
|
||||||
workflowData: IWorkflowDb;
|
workflowData: IWorkflowDb;
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
private activeWebhooks: ActiveWebhooks | null = null;
|
|
||||||
|
|
||||||
|
private activeWebhooks: ActiveWebhooks | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.activeWebhooks = new ActiveWebhooks();
|
this.activeWebhooks = new ActiveWebhooks();
|
||||||
this.activeWebhooks.testWebhooks = true;
|
this.activeWebhooks.testWebhooks = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a test-webhook and returns the data. It also makes sure that the
|
* 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
|
* data gets additionally send to the UI. After the request got handled it
|
||||||
|
@ -54,7 +47,12 @@ export class TestWebhooks {
|
||||||
* @returns {Promise<object>}
|
* @returns {Promise<object>}
|
||||||
* @memberof TestWebhooks
|
* @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
|
// Reset request parameters
|
||||||
request.params = {};
|
request.params = {};
|
||||||
|
|
||||||
|
@ -69,10 +67,16 @@ export class TestWebhooks {
|
||||||
if (webhookData === undefined) {
|
if (webhookData === undefined) {
|
||||||
const pathElements = path.split('/');
|
const pathElements = path.split('/');
|
||||||
const webhookId = pathElements.shift();
|
const webhookId = pathElements.shift();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
webhookData = this.activeWebhooks!.get(httpMethod, pathElements.join('/'), webhookId);
|
webhookData = this.activeWebhooks!.get(httpMethod, pathElements.join('/'), webhookId);
|
||||||
if (webhookData === undefined) {
|
if (webhookData === undefined) {
|
||||||
// The requested webhook is not registered
|
// 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;
|
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
|
// TODO: Clean that duplication up one day and improve code generally
|
||||||
if (this.testWebhookData[webhookKey] === undefined) {
|
if (this.testWebhookData[webhookKey] === undefined) {
|
||||||
// The requested webhook is not registered
|
// 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 the node which has the webhook defined to know where to start from and to
|
||||||
// get additional data
|
// get additional data
|
||||||
|
@ -102,15 +115,28 @@ export class TestWebhooks {
|
||||||
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
|
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) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const executionMode = 'manual';
|
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) {
|
if (error !== null) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (executionId === undefined) {
|
if (executionId === undefined) {
|
||||||
// The workflow did not run as the request was probably setup related
|
// 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
|
// Inform editor-ui that webhook got received
|
||||||
if (this.testWebhookData[webhookKey].sessionId !== undefined) {
|
if (this.testWebhookData[webhookKey].sessionId !== undefined) {
|
||||||
const pushInstance = Push.getInstance();
|
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) {
|
} catch (error) {
|
||||||
// Delete webhook also if an error is thrown
|
// Delete webhook also if an error is thrown
|
||||||
}
|
}
|
||||||
|
@ -132,6 +161,7 @@ export class TestWebhooks {
|
||||||
// Remove the webhook
|
// Remove the webhook
|
||||||
clearTimeout(this.testWebhookData[webhookKey].timeout);
|
clearTimeout(this.testWebhookData[webhookKey].timeout);
|
||||||
delete this.testWebhookData[webhookKey];
|
delete this.testWebhookData[webhookKey];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.activeWebhooks!.removeWorkflow(workflow);
|
this.activeWebhooks!.removeWorkflow(workflow);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -140,18 +170,22 @@ export class TestWebhooks {
|
||||||
* Gets all request methods associated with a single test webhook
|
* Gets all request methods associated with a single test webhook
|
||||||
* @param path webhook path
|
* @param path webhook path
|
||||||
*/
|
*/
|
||||||
async getWebhookMethods(path : string) : Promise<string[]> {
|
async getWebhookMethods(path: string): Promise<string[]> {
|
||||||
const webhookMethods: string[] = this.activeWebhooks!.getWebhookMethods(path);
|
const webhookMethods: string[] = this.activeWebhooks!.getWebhookMethods(path);
|
||||||
|
|
||||||
if (webhookMethods === undefined) {
|
if (webhookMethods === undefined) {
|
||||||
// The requested webhook is not registered
|
// 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;
|
return webhookMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if it has to wait for webhook data to execute the workflow. If yes it waits
|
* 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
|
* 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>)}
|
* @returns {(Promise<IExecutionDb | undefined>)}
|
||||||
* @memberof TestWebhooks
|
* @memberof TestWebhooks
|
||||||
*/
|
*/
|
||||||
async needsWebhookData(workflowData: IWorkflowDb, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, sessionId?: string, destinationNode?: string): Promise<boolean> {
|
async needsWebhookData(
|
||||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, destinationNode, true);
|
workflowData: IWorkflowDb,
|
||||||
if (!webhooks.find(webhook => webhook.webhookDescription.restartWebhook !== true)) {
|
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
|
// No webhooks found to start a workflow
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -180,8 +227,13 @@ export class TestWebhooks {
|
||||||
|
|
||||||
let key: string;
|
let key: string;
|
||||||
const activatedKey: string[] = [];
|
const activatedKey: string[] = [];
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const webhookData of webhooks) {
|
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);
|
activatedKey.push(key);
|
||||||
|
|
||||||
|
@ -193,9 +245,11 @@ export class TestWebhooks {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await this.activeWebhooks!.add(workflow, webhookData, mode, activation);
|
await this.activeWebhooks!.add(workflow, webhookData, mode, activation);
|
||||||
} catch (error) {
|
} 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);
|
await this.activeWebhooks!.removeWorkflow(workflow);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -204,7 +258,6 @@ export class TestWebhooks {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a test webhook of the workflow with the given id
|
* Removes a test webhook of the workflow with the given id
|
||||||
*
|
*
|
||||||
|
@ -214,10 +267,12 @@ export class TestWebhooks {
|
||||||
*/
|
*/
|
||||||
cancelTestWebhook(workflowId: string): boolean {
|
cancelTestWebhook(workflowId: string): boolean {
|
||||||
let foundWebhook = false;
|
let foundWebhook = false;
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
||||||
const webhookData = this.testWebhookData[webhookKey];
|
const webhookData = this.testWebhookData[webhookKey];
|
||||||
|
|
||||||
if (webhookData.workflowData.id.toString() !== workflowId) {
|
if (webhookData.workflowData.id.toString() !== workflowId) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,19 +282,24 @@ export class TestWebhooks {
|
||||||
if (this.testWebhookData[webhookKey].sessionId !== undefined) {
|
if (this.testWebhookData[webhookKey].sessionId !== undefined) {
|
||||||
try {
|
try {
|
||||||
const pushInstance = Push.getInstance();
|
const pushInstance = Push.getInstance();
|
||||||
pushInstance.send('testWebhookDeleted', { workflowId }, this.testWebhookData[webhookKey].sessionId!);
|
pushInstance.send(
|
||||||
|
'testWebhookDeleted',
|
||||||
|
{ workflowId },
|
||||||
|
this.testWebhookData[webhookKey].sessionId,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Could not inform editor, probably is not connected anymore. So sipmly go on.
|
// 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
|
// Remove the webhook
|
||||||
delete this.testWebhookData[webhookKey];
|
delete this.testWebhookData[webhookKey];
|
||||||
|
|
||||||
if (foundWebhook === false) {
|
if (!foundWebhook) {
|
||||||
// As it removes all webhooks of the workflow execute only once
|
// As it removes all webhooks of the workflow execute only once
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.activeWebhooks!.removeWorkflow(workflow);
|
this.activeWebhooks!.removeWorkflow(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +309,6 @@ export class TestWebhooks {
|
||||||
return foundWebhook;
|
return foundWebhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all the currently active test webhooks
|
* Removes all the currently active test webhooks
|
||||||
*/
|
*/
|
||||||
|
@ -260,6 +319,7 @@ export class TestWebhooks {
|
||||||
|
|
||||||
let workflow: Workflow;
|
let workflow: Workflow;
|
||||||
const workflows: Workflow[] = [];
|
const workflows: Workflow[] = [];
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
||||||
workflow = this.testWebhookData[webhookKey].workflow;
|
workflow = this.testWebhookData[webhookKey].workflow;
|
||||||
workflows.push(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 {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
DatabaseType,
|
DatabaseType,
|
||||||
|
@ -7,38 +20,23 @@ import {
|
||||||
IExecutionsStopData,
|
IExecutionsStopData,
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
WorkflowCredentials,
|
WorkflowCredentials,
|
||||||
WorkflowRunner,
|
WorkflowRunner,
|
||||||
} from '.';
|
} 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 {
|
export class WaitTrackerClass {
|
||||||
activeExecutionsInstance: ActiveExecutions.ActiveExecutions;
|
activeExecutionsInstance: ActiveExecutions.ActiveExecutions;
|
||||||
|
|
||||||
private waitingExecutions: {
|
private waitingExecutions: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
executionId: string,
|
executionId: string;
|
||||||
timer: NodeJS.Timeout,
|
timer: NodeJS.Timeout;
|
||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
mainTimer: NodeJS.Timeout;
|
mainTimer: NodeJS.Timeout;
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.activeExecutionsInstance = ActiveExecutions.getInstance();
|
this.activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||||
|
|
||||||
|
@ -50,7 +48,7 @@ export class WaitTrackerClass {
|
||||||
this.getwaitingExecutions();
|
this.getwaitingExecutions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
async getwaitingExecutions() {
|
async getwaitingExecutions() {
|
||||||
Logger.debug('Wait tracker querying database for waiting executions');
|
Logger.debug('Wait tracker querying database for waiting executions');
|
||||||
// Find all the executions which should be triggered in the next 70 seconds
|
// Find all the executions which should be triggered in the next 70 seconds
|
||||||
|
@ -63,11 +61,13 @@ export class WaitTrackerClass {
|
||||||
waitTill: 'ASC',
|
waitTill: 'ASC',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;
|
||||||
if (dbType === 'sqlite') {
|
if (dbType === 'sqlite') {
|
||||||
// This is needed because of issue in TypeORM <> SQLite:
|
// This is needed because of issue in TypeORM <> SQLite:
|
||||||
// https://github.com/typeorm/typeorm/issues/2286
|
// 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);
|
const executions = await Db.collections.Execution!.find(findQuery);
|
||||||
|
@ -76,10 +76,13 @@ export class WaitTrackerClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionIds = executions.map(execution => execution.id.toString()).join(', ');
|
const executionIds = executions.map((execution) => execution.id.toString()).join(', ');
|
||||||
Logger.debug(`Wait tracker found ${executions.length} executions. Setting timer for IDs: ${executionIds}`);
|
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
|
// 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) {
|
for (const execution of executions) {
|
||||||
const executionId = execution.id.toString();
|
const executionId = execution.id.toString();
|
||||||
if (this.waitingExecutions[executionId] === undefined) {
|
if (this.waitingExecutions[executionId] === undefined) {
|
||||||
|
@ -94,7 +97,6 @@ export class WaitTrackerClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async stopExecution(executionId: string): Promise<IExecutionsStopData> {
|
async stopExecution(executionId: string): Promise<IExecutionsStopData> {
|
||||||
if (this.waitingExecutions[executionId] !== undefined) {
|
if (this.waitingExecutions[executionId] !== undefined) {
|
||||||
// The waiting execution was already sheduled to execute.
|
// The waiting execution was already sheduled to execute.
|
||||||
|
@ -124,7 +126,10 @@ export class WaitTrackerClass {
|
||||||
fullExecutionData.stoppedAt = new Date();
|
fullExecutionData.stoppedAt = new Date();
|
||||||
fullExecutionData.waitTill = undefined;
|
fullExecutionData.waitTill = undefined;
|
||||||
|
|
||||||
await Db.collections.Execution!.update(executionId, ResponseHelper.flattenExecutionData(fullExecutionData));
|
await Db.collections.Execution!.update(
|
||||||
|
executionId,
|
||||||
|
ResponseHelper.flattenExecutionData(fullExecutionData),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mode: fullExecutionData.mode,
|
mode: fullExecutionData.mode,
|
||||||
|
@ -134,9 +139,8 @@ export class WaitTrackerClass {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
startExecution(executionId: string) {
|
startExecution(executionId: string) {
|
||||||
Logger.debug(`Wait tracker resuming execution ${executionId}`, {executionId});
|
Logger.debug(`Wait tracker resuming execution ${executionId}`, { executionId });
|
||||||
delete this.waitingExecutions[executionId];
|
delete this.waitingExecutions[executionId];
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -149,7 +153,7 @@ export class WaitTrackerClass {
|
||||||
|
|
||||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(fullExecutionDataFlatted);
|
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.');
|
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();
|
const workflowRunner = new WorkflowRunner();
|
||||||
await workflowRunner.run(data, false, false, executionId);
|
await workflowRunner.run(data, false, false, executionId);
|
||||||
})().catch((error) => {
|
})().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;
|
let waitTrackerInstance: WaitTrackerClass | undefined;
|
||||||
|
|
||||||
export function WaitTracker(): WaitTrackerClass {
|
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 {
|
import {
|
||||||
Db,
|
Db,
|
||||||
IExecutionResponse,
|
IExecutionResponse,
|
||||||
|
@ -6,26 +22,18 @@ import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
WebhookHelpers,
|
WebhookHelpers,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
WorkflowCredentials,
|
WorkflowCredentials,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
} from '.';
|
} 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 {
|
export class WaitingWebhooks {
|
||||||
|
async executeWebhook(
|
||||||
async executeWebhook(httpMethod: WebhookHttpMethod, fullPath: string, req: express.Request, res: express.Response): Promise<IResponseCallbackData> {
|
httpMethod: WebhookHttpMethod,
|
||||||
|
fullPath: string,
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
): Promise<IResponseCallbackData> {
|
||||||
Logger.debug(`Received waiting-webhoook "${httpMethod}" for path "${fullPath}"`);
|
Logger.debug(`Received waiting-webhoook "${httpMethod}" for path "${fullPath}"`);
|
||||||
|
|
||||||
// Reset request parameters
|
// Reset request parameters
|
||||||
|
@ -44,47 +52,77 @@ export class WaitingWebhooks {
|
||||||
const execution = await Db.collections.Execution?.findOne(executionId);
|
const execution = await Db.collections.Execution?.findOne(executionId);
|
||||||
|
|
||||||
if (execution === undefined) {
|
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);
|
const fullExecutionData = ResponseHelper.unflattenExecutionData(execution);
|
||||||
|
|
||||||
if (fullExecutionData.finished === true || fullExecutionData.data.resultData.error) {
|
if (fullExecutionData.finished || fullExecutionData.data.resultData.error) {
|
||||||
throw new ResponseHelper.ResponseError(`The execution "${executionId} has finished already.`, 409, 409);
|
throw new ResponseHelper.ResponseError(
|
||||||
|
`The execution "${executionId} has finished already.`,
|
||||||
|
409,
|
||||||
|
409,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.startExecution(httpMethod, path, fullExecutionData, req, res);
|
return this.startExecution(httpMethod, path, fullExecutionData, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startExecution(
|
||||||
async startExecution(httpMethod: WebhookHttpMethod, path: string, fullExecutionData: IExecutionResponse, req: express.Request, res: express.Response): Promise<IResponseCallbackData> {
|
httpMethod: WebhookHttpMethod,
|
||||||
|
path: string,
|
||||||
|
fullExecutionData: IExecutionResponse,
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
): Promise<IResponseCallbackData> {
|
||||||
const executionId = fullExecutionData.id;
|
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.');
|
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
|
// 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
|
// 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
|
// 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
|
// 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 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 additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||||
|
|
||||||
const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(lastNodeExecuted) as INode, additionalData).filter((webhook) => {
|
const webhookData = NodeHelpers.getNodeWebhooks(
|
||||||
return (webhook.httpMethod === httpMethod && webhook.path === path && webhook.webhookDescription.restartWebhook === true);
|
workflow,
|
||||||
|
workflow.getNode(lastNodeExecuted) as INode,
|
||||||
|
additionalData,
|
||||||
|
).filter((webhook) => {
|
||||||
|
return (
|
||||||
|
webhook.httpMethod === httpMethod &&
|
||||||
|
webhook.path === path &&
|
||||||
|
webhook.webhookDescription.restartWebhook === true
|
||||||
|
);
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
if (webhookData === undefined) {
|
if (webhookData === undefined) {
|
||||||
|
@ -100,18 +138,30 @@ export class WaitingWebhooks {
|
||||||
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const executionMode = 'webhook';
|
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) {
|
if (error !== null) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
resolve(data);
|
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';
|
import * as express from 'express';
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
import {
|
import { BINARY_ENCODING, NodeExecuteFunctions } from 'n8n-core';
|
||||||
ActiveExecutions,
|
|
||||||
GenericHelpers,
|
|
||||||
IExecutionDb,
|
|
||||||
IResponseCallbackData,
|
|
||||||
IWorkflowDb,
|
|
||||||
IWorkflowExecutionDataProcess,
|
|
||||||
ResponseHelper,
|
|
||||||
WorkflowCredentials,
|
|
||||||
WorkflowExecuteAdditionalData,
|
|
||||||
WorkflowHelpers,
|
|
||||||
WorkflowRunner,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
import {
|
|
||||||
BINARY_ENCODING,
|
|
||||||
NodeExecuteFunctions,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IBinaryKeyData,
|
IBinaryKeyData,
|
||||||
|
@ -35,7 +32,21 @@ import {
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} 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();
|
const activeExecutions = ActiveExecutions.getInstance();
|
||||||
|
|
||||||
|
@ -47,7 +58,12 @@ const activeExecutions = ActiveExecutions.getInstance();
|
||||||
* @param {Workflow} workflow
|
* @param {Workflow} workflow
|
||||||
* @returns {IWebhookData[]}
|
* @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
|
// Check all the nodes in the workflow if they have webhooks
|
||||||
|
|
||||||
const returnData: IWebhookData[] = [];
|
const returnData: IWebhookData[] = [];
|
||||||
|
@ -63,9 +79,13 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
|
||||||
if (parentNodes !== undefined && !parentNodes.includes(node.name)) {
|
if (parentNodes !== undefined && !parentNodes.includes(node.name)) {
|
||||||
// If parentNodes are given check only them if they have webhooks
|
// If parentNodes are given check only them if they have webhooks
|
||||||
// and no other ones
|
// and no other ones
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
returnData.push.apply(returnData, NodeHelpers.getNodeWebhooks(workflow, node, additionalData, ignoreRestartWehbooks));
|
returnData.push.apply(
|
||||||
|
returnData,
|
||||||
|
NodeHelpers.getNodeWebhooks(workflow, node, additionalData, ignoreRestartWehbooks),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
|
@ -91,8 +111,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
/**
|
|
||||||
* Executes a webhook
|
* Executes a webhook
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -106,7 +125,19 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||||
* @param {((error: Error | null, data: IResponseCallbackData) => void)} responseCallback
|
* @param {((error: Error | null, data: IResponseCallbackData) => void)} responseCallback
|
||||||
* @returns {(Promise<string | undefined>)}
|
* @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
|
// Get the nodeType to know which responseMode is set
|
||||||
const nodeType = workflow.nodeTypes.getByName(workflowStartNode.type);
|
const nodeType = workflow.nodeTypes.getByName(workflowStartNode.type);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
|
@ -120,8 +151,20 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the responseMode
|
// Get the responseMode
|
||||||
const responseMode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], executionMode, additionalKeys, 'onReceived');
|
const responseMode = workflow.expression.getSimpleParameterValue(
|
||||||
const responseCode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], executionMode, additionalKeys, 200) as number;
|
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 (!['onReceived', 'lastNode'].includes(responseMode as string)) {
|
||||||
// If the mode is not known we error. Is probably best like that instead of using
|
// 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;
|
let webhookResultData: IWebhookResponseData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
webhookResultData = await workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode);
|
webhookResultData = await workflow.runWebhook(
|
||||||
|
webhookData,
|
||||||
|
workflowStartNode,
|
||||||
|
additionalData,
|
||||||
|
NodeExecuteFunctions,
|
||||||
|
executionMode,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Send error response to webhook caller
|
// Send error response to webhook caller
|
||||||
const errorMessage = 'Workflow Webhook Error: Workflow could not be started!';
|
const errorMessage = 'Workflow Webhook Error: Workflow could not be started!';
|
||||||
|
@ -171,7 +220,7 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
noWebhookResponse: true,
|
noWebhookResponse: true,
|
||||||
// Add empty data that it at least tries to "execute" the webhook
|
// Add empty data that it at least tries to "execute" the webhook
|
||||||
// which then so gets the chance to throw the error.
|
// 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,
|
$executionId: executionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (webhookData.webhookDescription['responseHeaders'] !== undefined) {
|
if (webhookData.webhookDescription.responseHeaders !== undefined) {
|
||||||
const responseHeaders = workflow.expression.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], executionMode, additionalKeys, undefined) as {
|
const responseHeaders = workflow.expression.getComplexParameterValue(
|
||||||
entries?: Array<{
|
workflowStartNode,
|
||||||
|
webhookData.webhookDescription.responseHeaders,
|
||||||
|
executionMode,
|
||||||
|
additionalKeys,
|
||||||
|
undefined,
|
||||||
|
) as {
|
||||||
|
entries?:
|
||||||
|
| Array<{
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
}> | undefined;
|
}>
|
||||||
|
| undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (responseHeaders !== undefined && responseHeaders['entries'] !== undefined) {
|
if (responseHeaders !== undefined && responseHeaders.entries !== undefined) {
|
||||||
for (const item of responseHeaders['entries']) {
|
for (const item of responseHeaders.entries) {
|
||||||
res.setHeader(item['name'], item['value']);
|
res.setHeader(item.name, item.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webhookResultData.noWebhookResponse === true && didSendResponse === false) {
|
if (webhookResultData.noWebhookResponse === true && !didSendResponse) {
|
||||||
// The response got already send
|
// The response got already send
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
noWebhookResponse: true,
|
noWebhookResponse: true,
|
||||||
|
@ -209,7 +266,7 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
// Workflow should not run
|
// Workflow should not run
|
||||||
if (webhookResultData.webhookResponse !== undefined) {
|
if (webhookResultData.webhookResponse !== undefined) {
|
||||||
// Data to respond with is given
|
// Data to respond with is given
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
data: webhookResultData.webhookResponse,
|
data: webhookResultData.webhookResponse,
|
||||||
responseCode,
|
responseCode,
|
||||||
|
@ -218,7 +275,8 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Send default response
|
// Send default response
|
||||||
if (didSendResponse === false) {
|
// eslint-disable-next-line no-lonely-if
|
||||||
|
if (!didSendResponse) {
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
data: {
|
data: {
|
||||||
message: 'Webhook call got received.',
|
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
|
// 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
|
// 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
|
// Return response directly and do not wait for the workflow to finish
|
||||||
if (webhookResultData.webhookResponse !== undefined) {
|
if (webhookResultData.webhookResponse !== undefined) {
|
||||||
// Data to respond with is given
|
// 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
|
// Initialize the data of the webhook node
|
||||||
const nodeExecutionStack: IExecuteData[] = [];
|
const nodeExecutionStack: IExecuteData[] = [];
|
||||||
nodeExecutionStack.push(
|
nodeExecutionStack.push({
|
||||||
{
|
|
||||||
node: workflowStartNode,
|
node: workflowStartNode,
|
||||||
data: {
|
data: {
|
||||||
main: webhookResultData.workflowData,
|
main: webhookResultData.workflowData,
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
runExecutionData = runExecutionData || {
|
runExecutionData =
|
||||||
startData: {
|
runExecutionData ||
|
||||||
},
|
({
|
||||||
|
startData: {},
|
||||||
resultData: {
|
resultData: {
|
||||||
runData: {},
|
runData: {},
|
||||||
},
|
},
|
||||||
|
@ -275,12 +332,13 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
nodeExecutionStack,
|
nodeExecutionStack,
|
||||||
waitingExecution: {},
|
waitingExecution: {},
|
||||||
},
|
},
|
||||||
} as IRunExecutionData;
|
} as IRunExecutionData);
|
||||||
|
|
||||||
if (executionId !== undefined) {
|
if (executionId !== undefined) {
|
||||||
// Set the data the webhook node did return on the waiting node if executionId
|
// 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.
|
// 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) {
|
if (Object.keys(runExecutionDataMerge).length !== 0) {
|
||||||
|
@ -299,13 +357,19 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
const workflowRunner = new WorkflowRunner();
|
const workflowRunner = new WorkflowRunner();
|
||||||
executionId = await workflowRunner.run(runData, true, !didSendResponse, executionId);
|
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
|
// Get a promise which resolves when the workflow did execute and send then response
|
||||||
const executePromise = activeExecutions.getPostExecutePromise(executionId) as Promise<IExecutionDb | undefined>;
|
const executePromise = activeExecutions.getPostExecutePromise(executionId) as Promise<
|
||||||
executePromise.then((data) => {
|
IExecutionDb | undefined
|
||||||
|
>;
|
||||||
|
executePromise
|
||||||
|
.then((data) => {
|
||||||
if (data === undefined) {
|
if (data === undefined) {
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
data: {
|
data: {
|
||||||
message: 'Workflow did execute sucessfully but no data got returned.',
|
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);
|
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||||
if(data.data.resultData.error || returnData?.error !== undefined) {
|
if (data.data.resultData.error || returnData?.error !== undefined) {
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
data: {
|
data: {
|
||||||
message: 'Workflow did error.',
|
message: 'Workflow did error.',
|
||||||
|
@ -332,10 +396,11 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnData === undefined) {
|
if (returnData === undefined) {
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
data: {
|
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,
|
responseCode,
|
||||||
});
|
});
|
||||||
|
@ -348,9 +413,15 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
$executionId: executionId,
|
$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[];
|
let data: IDataObject | IDataObject[];
|
||||||
|
|
||||||
if (responseData === 'firstEntryJson') {
|
if (responseData === 'firstEntryJson') {
|
||||||
|
@ -363,20 +434,36 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
|
|
||||||
data = returnData.data!.main[0]![0].json;
|
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) {
|
if (responsePropertyName !== undefined) {
|
||||||
data = get(data, responsePropertyName as string) as IDataObject;
|
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) {
|
if (responseContentType !== undefined) {
|
||||||
// Send the webhook response manually to be able to set the content-type
|
// Send the webhook response manually to be able to set the content-type
|
||||||
res.setHeader('Content-Type', responseContentType as string);
|
res.setHeader('Content-Type', responseContentType as string);
|
||||||
|
|
||||||
// Returning an object, boolean, number, ... causes problems so make sure to stringify if needed
|
// 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);
|
res.end(data);
|
||||||
} else {
|
} else {
|
||||||
res.end(JSON.stringify(data));
|
res.end(JSON.stringify(data));
|
||||||
|
@ -387,7 +474,6 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
});
|
});
|
||||||
didSendResponse = true;
|
didSendResponse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (responseData === 'firstEntryBinary') {
|
} else if (responseData === 'firstEntryBinary') {
|
||||||
// Return the binary data of the first entry
|
// Return the binary data of the first entry
|
||||||
data = returnData.data!.main[0]![0];
|
data = returnData.data!.main[0]![0];
|
||||||
|
@ -402,20 +488,33 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
didSendResponse = true;
|
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.'), {});
|
responseCallback(new Error('No "responseBinaryPropertyName" is set.'), {});
|
||||||
didSendResponse = true;
|
didSendResponse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const binaryData = (data.binary as IBinaryKeyData)[responseBinaryPropertyName as string];
|
const binaryData = (data.binary as IBinaryKeyData)[
|
||||||
if (binaryData === undefined && didSendResponse === false) {
|
responseBinaryPropertyName as string
|
||||||
responseCallback(new Error(`The binary property "${responseBinaryPropertyName}" which should be returned does not exist.`), {});
|
];
|
||||||
|
if (binaryData === undefined && !didSendResponse) {
|
||||||
|
responseCallback(
|
||||||
|
new Error(
|
||||||
|
`The binary property "${responseBinaryPropertyName}" which should be returned does not exist.`,
|
||||||
|
),
|
||||||
|
{},
|
||||||
|
);
|
||||||
didSendResponse = true;
|
didSendResponse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
// Send the webhook response manually
|
// Send the webhook response manually
|
||||||
res.setHeader('Content-Type', binaryData.mimeType);
|
res.setHeader('Content-Type', binaryData.mimeType);
|
||||||
res.end(Buffer.from(binaryData.data, BINARY_ENCODING));
|
res.end(Buffer.from(binaryData.data, BINARY_ENCODING));
|
||||||
|
@ -424,7 +523,6 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
noWebhookResponse: true,
|
noWebhookResponse: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Return the JSON data of all the entries
|
// Return the JSON data of all the entries
|
||||||
data = [];
|
data = [];
|
||||||
|
@ -433,7 +531,7 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
data,
|
data,
|
||||||
responseCode,
|
responseCode,
|
||||||
|
@ -445,17 +543,17 @@ export async function executeWebhook(workflow: Workflow, webhookData: IWebhookDa
|
||||||
return data;
|
return data;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
responseCallback(new Error('There was a problem executing the workflow.'), {});
|
responseCallback(new Error('There was a problem executing the workflow.'), {});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ResponseHelper.ResponseError(e.message, 500, 500);
|
throw new ResponseHelper.ResponseError(e.message, 500, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
return executionId;
|
return executionId;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (didSendResponse === false) {
|
if (!didSendResponse) {
|
||||||
responseCallback(new Error('There was a problem executing the workflow.'), {});
|
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
|
* 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 * as express from 'express';
|
||||||
import {
|
import { readFileSync } from 'fs';
|
||||||
readFileSync,
|
import { getConnectionManager } from 'typeorm';
|
||||||
} from 'fs';
|
|
||||||
import {
|
|
||||||
getConnectionManager,
|
|
||||||
} from 'typeorm';
|
|
||||||
import * as bodyParser from 'body-parser';
|
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 _ 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 {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
ActiveWorkflowRunner,
|
ActiveWorkflowRunner,
|
||||||
|
@ -19,25 +27,31 @@ import {
|
||||||
IExternalHooksClass,
|
IExternalHooksClass,
|
||||||
IPackageVersions,
|
IPackageVersions,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
} from './';
|
} from '.';
|
||||||
|
|
||||||
import * as compression from 'compression';
|
|
||||||
import * as config from '../config';
|
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() {
|
export function registerProductionWebhooks() {
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Regular Webhooks
|
// Regular Webhooks
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
// HEAD webhook requests
|
// 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
|
// 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;
|
let response;
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
response = await this.activeWorkflowRunner.executeWebhook('HEAD', requestUrl, req, res);
|
response = await this.activeWorkflowRunner.executeWebhook('HEAD', requestUrl, req, res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ResponseHelper.sendErrorResponse(res, error);
|
ResponseHelper.sendErrorResponse(res, error);
|
||||||
|
@ -50,12 +64,17 @@ export function registerProductionWebhooks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// OPTIONS webhook requests
|
// 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
|
// 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[];
|
let allowedMethods: string[];
|
||||||
try {
|
try {
|
||||||
|
@ -70,12 +89,17 @@ export function registerProductionWebhooks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// GET webhook requests
|
// 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
|
// 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;
|
let response;
|
||||||
try {
|
try {
|
||||||
|
@ -91,12 +115,17 @@ export function registerProductionWebhooks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// POST webhook requests
|
// 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
|
// 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;
|
let response;
|
||||||
try {
|
try {
|
||||||
|
@ -112,27 +141,43 @@ export function registerProductionWebhooks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
|
|
||||||
app: express.Application;
|
app: express.Application;
|
||||||
|
|
||||||
activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner;
|
activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner;
|
||||||
|
|
||||||
endpointWebhook: string;
|
endpointWebhook: string;
|
||||||
|
|
||||||
endpointPresetCredentials: string;
|
endpointPresetCredentials: string;
|
||||||
|
|
||||||
externalHooks: IExternalHooksClass;
|
externalHooks: IExternalHooksClass;
|
||||||
|
|
||||||
saveDataErrorExecution: string;
|
saveDataErrorExecution: string;
|
||||||
|
|
||||||
saveDataSuccessExecution: string;
|
saveDataSuccessExecution: string;
|
||||||
|
|
||||||
saveManualExecutions: boolean;
|
saveManualExecutions: boolean;
|
||||||
|
|
||||||
executionTimeout: number;
|
executionTimeout: number;
|
||||||
|
|
||||||
maxExecutionTimeout: number;
|
maxExecutionTimeout: number;
|
||||||
|
|
||||||
timezone: string;
|
timezone: string;
|
||||||
|
|
||||||
activeExecutionsInstance: ActiveExecutions.ActiveExecutions;
|
activeExecutionsInstance: ActiveExecutions.ActiveExecutions;
|
||||||
|
|
||||||
versions: IPackageVersions | undefined;
|
versions: IPackageVersions | undefined;
|
||||||
|
|
||||||
restEndpoint: string;
|
restEndpoint: string;
|
||||||
|
|
||||||
protocol: string;
|
protocol: string;
|
||||||
|
|
||||||
sslKey: string;
|
sslKey: string;
|
||||||
|
|
||||||
sslCert: string;
|
sslCert: string;
|
||||||
|
|
||||||
presetCredentialsLoaded: boolean;
|
presetCredentialsLoaded: boolean;
|
||||||
|
@ -163,7 +208,6 @@ class App {
|
||||||
this.endpointPresetCredentials = config.get('credentials.overwrite.endpoint') as string;
|
this.endpointPresetCredentials = config.get('credentials.overwrite.endpoint') as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current epoch time
|
* Returns the current epoch time
|
||||||
*
|
*
|
||||||
|
@ -174,9 +218,7 @@ class App {
|
||||||
return new Date();
|
return new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async config(): Promise<void> {
|
async config(): Promise<void> {
|
||||||
|
|
||||||
this.versions = await GenericHelpers.getVersions();
|
this.versions = await GenericHelpers.getVersions();
|
||||||
|
|
||||||
// Compress the response data
|
// Compress the response data
|
||||||
|
@ -191,49 +233,63 @@ class App {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Support application/json type post data
|
// Support application/json type post data
|
||||||
this.app.use(bodyParser.json({
|
this.app.use(
|
||||||
limit: '16mb', verify: (req, res, buf) => {
|
bodyParser.json({
|
||||||
// @ts-ignore
|
limit: '16mb',
|
||||||
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,
|
|
||||||
verify: (req, res, buf) => {
|
verify: (req, res, buf) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
req.rawBody = buf;
|
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) => {
|
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
// Allow access also from frontend when developing
|
// Allow access also from frontend when developing
|
||||||
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
|
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-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();
|
next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (Db.collections.Workflow === null) {
|
if (Db.collections.Workflow === null) {
|
||||||
const error = new ResponseHelper.ResponseError('Database is not ready!', undefined, 503);
|
const error = new ResponseHelper.ResponseError('Database is not ready!', undefined, 503);
|
||||||
|
@ -243,25 +299,22 @@ class App {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Healthcheck
|
// Healthcheck
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
|
|
||||||
// Does very basic health check
|
// Does very basic health check
|
||||||
this.app.get('/healthz', async (req: express.Request, res: express.Response) => {
|
this.app.get('/healthz', async (req: express.Request, res: express.Response) => {
|
||||||
|
|
||||||
const connection = getConnectionManager().get();
|
const connection = getConnectionManager().get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (connection.isConnected === false) {
|
if (!connection.isConnected) {
|
||||||
// Connection is not active
|
// Connection is not active
|
||||||
throw new Error('No active database connection!');
|
throw new Error('No active database connection!');
|
||||||
}
|
}
|
||||||
// DB ping
|
// DB ping
|
||||||
await connection.query('SELECT 1');
|
await connection.query('SELECT 1');
|
||||||
|
// eslint-disable-next-line id-denylist
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = new ResponseHelper.ResponseError('No Database connection!', undefined, 503);
|
const error = new ResponseHelper.ResponseError('No Database connection!', undefined, 503);
|
||||||
return ResponseHelper.sendErrorResponse(res, error);
|
return ResponseHelper.sendErrorResponse(res, error);
|
||||||
|
@ -276,9 +329,7 @@ class App {
|
||||||
});
|
});
|
||||||
|
|
||||||
registerProductionWebhooks.apply(this);
|
registerProductionWebhooks.apply(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function start(): Promise<void> {
|
export async function start(): Promise<void> {
|
||||||
|
@ -292,12 +343,14 @@ export async function start(): Promise<void> {
|
||||||
let server;
|
let server;
|
||||||
|
|
||||||
if (app.protocol === 'https' && app.sslKey && app.sslCert) {
|
if (app.protocol === 'https' && app.sslKey && app.sslCert) {
|
||||||
|
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const privateKey = readFileSync(app.sslKey, 'utf8');
|
const privateKey = readFileSync(app.sslKey, 'utf8');
|
||||||
const cert = readFileSync(app.sslCert, 'utf8');
|
const cert = readFileSync(app.sslCert, 'utf8');
|
||||||
const credentials = { key: privateKey, cert };
|
const credentials = { key: privateKey, cert };
|
||||||
server = https.createServer(credentials, app.app);
|
server = https.createServer(credentials, app.app);
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
server = http.createServer(app.app);
|
server = http.createServer(app.app);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import {
|
/* eslint-disable no-prototype-builtins */
|
||||||
Db,
|
import { INode, IWorkflowCredentials } from 'n8n-workflow';
|
||||||
} from './';
|
// eslint-disable-next-line import/no-cycle
|
||||||
import {
|
import { Db } from '.';
|
||||||
INode,
|
|
||||||
IWorkflowCredentials
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCredentials> {
|
export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCredentials> {
|
||||||
// Go through all nodes to find which credentials are needed to execute the workflow
|
// Go through all nodes to find which credentials are needed to execute the workflow
|
||||||
const returnCredentials: IWorkflowCredentials = {};
|
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) {
|
for (node of nodes) {
|
||||||
if (node.disabled === true || !node.credentials) {
|
if (node.disabled === true || !node.credentials) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (type of Object.keys(node.credentials)) {
|
for (type of Object.keys(node.credentials)) {
|
||||||
if (!returnCredentials.hasOwnProperty(type)) {
|
if (!returnCredentials.hasOwnProperty(type)) {
|
||||||
returnCredentials[type] = {};
|
returnCredentials[type] = {};
|
||||||
|
@ -24,14 +27,15 @@ export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCred
|
||||||
name = node.credentials[type];
|
name = node.credentials[type];
|
||||||
|
|
||||||
if (!returnCredentials[type].hasOwnProperty(name)) {
|
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 });
|
foundCredentials = await Db.collections.Credentials!.find({ name, type });
|
||||||
if (!foundCredentials.length) {
|
if (!foundCredentials.length) {
|
||||||
throw new Error(`Could not find credentials for type "${type}" with name "${name}".`);
|
throw new Error(`Could not find credentials for type "${type}" with name "${name}".`);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
returnCredentials[type][name] = foundCredentials[0];
|
returnCredentials[type][name] = foundCredentials[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnCredentials;
|
return returnCredentials;
|
||||||
|
|
|
@ -1,27 +1,20 @@
|
||||||
import {
|
/* eslint-disable no-restricted-syntax */
|
||||||
ActiveExecutions,
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
CredentialsHelper,
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
Db,
|
/* eslint-disable @typescript-eslint/await-thenable */
|
||||||
ExternalHooks,
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
IExecutionDb,
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
IExecutionFlattedDb,
|
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||||
IExecutionResponse,
|
/* eslint-disable no-param-reassign */
|
||||||
IPushDataExecutionFinished,
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
IWorkflowBase,
|
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||||
IWorkflowExecuteProcess,
|
/* eslint-disable id-denylist */
|
||||||
IWorkflowExecutionDataProcess,
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
NodeTypes,
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
Push,
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
ResponseHelper,
|
/* eslint-disable func-names */
|
||||||
WebhookHelpers,
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
WorkflowCredentials,
|
import { UserSettings, WorkflowExecute } from 'n8n-core';
|
||||||
WorkflowHelpers,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
import {
|
|
||||||
UserSettings,
|
|
||||||
WorkflowExecute,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
@ -43,10 +36,29 @@ import {
|
||||||
WorkflowHooks,
|
WorkflowHooks,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import * as config from '../config';
|
|
||||||
|
|
||||||
import { LessThanOrEqual } from 'typeorm';
|
import { LessThanOrEqual } from 'typeorm';
|
||||||
import { DateUtils } from 'typeorm/util/DateUtils';
|
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;
|
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 {WorkflowExecuteMode} mode The mode in which the workflow got started in
|
||||||
* @param {string} [executionId] The id the execution got saved as
|
* @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
|
// 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) {
|
if (executionId !== undefined) {
|
||||||
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}execution/${executionId}`;
|
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}execution/${executionId}`;
|
||||||
}
|
}
|
||||||
|
@ -78,20 +96,42 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
||||||
retryOf,
|
retryOf,
|
||||||
},
|
},
|
||||||
workflow: {
|
workflow: {
|
||||||
id: workflowData.id !== undefined ? workflowData.id.toString() as string : undefined,
|
id: workflowData.id !== undefined ? workflowData.id.toString() : undefined,
|
||||||
name: workflowData.name,
|
name: workflowData.name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the error workflow
|
// 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.
|
// 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())) {
|
if (
|
||||||
Logger.verbose(`Start external error workflow`, { executionId, errorWorkflowId: workflowData.settings.errorWorkflow.toString(), workflowId: workflowData.id });
|
// 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
|
// If a specific error workflow is set run only that one
|
||||||
WorkflowHelpers.executeErrorWorkflow(workflowData.settings.errorWorkflow as string, workflowErrorData);
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
} else if (mode !== 'error' && workflowData.id !== undefined && workflowData.nodes.some((node) => node.type === ERROR_TRIGGER_TYPE)) {
|
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 });
|
Logger.verbose(`Start internal error workflow`, { executionId, workflowId: workflowData.id });
|
||||||
// If the workflow contains
|
// If the workflow contains
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
WorkflowHelpers.executeErrorWorkflow(workflowData.id.toString(), workflowErrorData);
|
WorkflowHelpers.executeErrorWorkflow(workflowData.id.toString(), workflowErrorData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,23 +154,34 @@ function pruneExecutionData(this: WorkflowHooks): void {
|
||||||
date.setHours(date.getHours() - maxAge);
|
date.setHours(date.getHours() - maxAge);
|
||||||
|
|
||||||
// date reformatting needed - see https://github.com/typeorm/typeorm/issues/2286
|
// 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);
|
const utcDate = DateUtils.mixedDateToUtcDatetimeString(date);
|
||||||
|
|
||||||
// throttle just on success to allow for self healing on failure
|
// throttle just on success to allow for self healing on failure
|
||||||
Db.collections.Execution!.delete({ stoppedAt: LessThanOrEqual(utcDate) })
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
.then(data =>
|
Db.collections
|
||||||
|
.Execution!.delete({ stoppedAt: LessThanOrEqual(utcDate) })
|
||||||
|
.then((data) =>
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
throttling = false;
|
throttling = false;
|
||||||
}, timeout * 1000)
|
}, timeout * 1000),
|
||||||
).catch(error => {
|
)
|
||||||
|
.catch((error) => {
|
||||||
throttling = false;
|
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
|
* Returns hook functions to push data to Editor-UI
|
||||||
*
|
*
|
||||||
|
@ -145,13 +196,21 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
||||||
if (this.sessionId === undefined) {
|
if (this.sessionId === undefined) {
|
||||||
return;
|
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();
|
const pushInstance = Push.getInstance();
|
||||||
pushInstance.send('nodeExecuteBefore', {
|
pushInstance.send(
|
||||||
|
'nodeExecuteBefore',
|
||||||
|
{
|
||||||
executionId: this.executionId,
|
executionId: this.executionId,
|
||||||
nodeName,
|
nodeName,
|
||||||
}, this.sessionId);
|
},
|
||||||
|
this.sessionId,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
nodeExecuteAfter: [
|
nodeExecuteAfter: [
|
||||||
|
@ -160,37 +219,62 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
||||||
if (this.sessionId === undefined) {
|
if (this.sessionId === undefined) {
|
||||||
return;
|
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();
|
const pushInstance = Push.getInstance();
|
||||||
pushInstance.send('nodeExecuteAfter', {
|
pushInstance.send(
|
||||||
|
'nodeExecuteAfter',
|
||||||
|
{
|
||||||
executionId: this.executionId,
|
executionId: this.executionId,
|
||||||
nodeName,
|
nodeName,
|
||||||
data,
|
data,
|
||||||
}, this.sessionId);
|
},
|
||||||
|
this.sessionId,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
workflowExecuteBefore: [
|
workflowExecuteBefore: [
|
||||||
async function (this: WorkflowHooks): Promise<void> {
|
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
|
// Push data to session which started the workflow
|
||||||
if (this.sessionId === undefined) {
|
if (this.sessionId === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pushInstance = Push.getInstance();
|
const pushInstance = Push.getInstance();
|
||||||
pushInstance.send('executionStarted', {
|
pushInstance.send(
|
||||||
|
'executionStarted',
|
||||||
|
{
|
||||||
executionId: this.executionId,
|
executionId: this.executionId,
|
||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
retryOf: this.retryOf,
|
retryOf: this.retryOf,
|
||||||
workflowId: this.workflowData.id, sessionId: this.sessionId as string,
|
workflowId: this.workflowData.id,
|
||||||
|
sessionId: this.sessionId,
|
||||||
workflowName: this.workflowData.name,
|
workflowName: this.workflowData.name,
|
||||||
}, this.sessionId);
|
},
|
||||||
|
this.sessionId,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
workflowExecuteAfter: [
|
workflowExecuteAfter: [
|
||||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
async function (
|
||||||
Logger.debug(`Executing hook (hookFunctionsPush)`, { executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
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
|
// Push data to session which started the workflow
|
||||||
if (this.sessionId === undefined) {
|
if (this.sessionId === undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -211,7 +295,10 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Push data to editor-ui once workflow finished
|
// 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
|
// TODO: Look at this again
|
||||||
const sendData: IPushDataExecutionFinished = {
|
const sendData: IPushDataExecutionFinished = {
|
||||||
executionId: this.executionId,
|
executionId: this.executionId,
|
||||||
|
@ -226,7 +313,6 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowExecuteHooks {
|
export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
const externalHooks = ExternalHooks();
|
const externalHooks = ExternalHooks();
|
||||||
|
|
||||||
|
@ -237,20 +323,32 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
nodeExecuteAfter: [
|
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 !== undefined) {
|
||||||
if (this.workflowData.settings.saveExecutionProgress === false) {
|
if (this.workflowData.settings.saveExecutionProgress === false) {
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
} else if (!config.get('executions.saveExecutionProgress') as boolean) {
|
} else if (!config.get('executions.saveExecutionProgress')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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);
|
const execution = await Db.collections.Execution!.findOne(this.executionId);
|
||||||
|
|
||||||
if (execution === undefined) {
|
if (execution === undefined) {
|
||||||
|
@ -258,7 +356,8 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
||||||
// This check is here mostly to make typescript happy.
|
// This check is here mostly to make typescript happy.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fullExecutionData: IExecutionResponse = ResponseHelper.unflattenExecutionData(execution);
|
const fullExecutionData: IExecutionResponse =
|
||||||
|
ResponseHelper.unflattenExecutionData(execution);
|
||||||
|
|
||||||
if (fullExecutionData.finished) {
|
if (fullExecutionData.finished) {
|
||||||
// We already received ´workflowExecuteAfter´ webhook, so this is just an async call
|
// 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);
|
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) {
|
} catch (err) {
|
||||||
// TODO: Improve in the future!
|
// TODO: Improve in the future!
|
||||||
// Errors here might happen because of database access
|
// Errors here might happen because of database access
|
||||||
// For busy machines, we may get "Database is locked" errors.
|
// For busy machines, we may get "Database is locked" errors.
|
||||||
|
|
||||||
// We do this to prevent crashes and executions ending in `unknown` state.
|
// 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
|
* Returns hook functions to save workflow execution and call error workflow
|
||||||
*
|
*
|
||||||
|
@ -323,8 +432,15 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
nodeExecuteAfter: [],
|
nodeExecuteAfter: [],
|
||||||
workflowExecuteBefore: [],
|
workflowExecuteBefore: [],
|
||||||
workflowExecuteAfter: [
|
workflowExecuteAfter: [
|
||||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
async function (
|
||||||
Logger.debug(`Executing hook (hookFunctionsSave)`, { executionId: this.executionId, workflowId: this.workflowData.id });
|
this: WorkflowHooks,
|
||||||
|
fullRunData: IRun,
|
||||||
|
newStaticData: IDataObject,
|
||||||
|
): Promise<void> {
|
||||||
|
Logger.debug(`Executing hook (hookFunctionsSave)`, {
|
||||||
|
executionId: this.executionId,
|
||||||
|
workflowId: this.workflowData.id,
|
||||||
|
});
|
||||||
|
|
||||||
// Prune old execution data
|
// Prune old execution data
|
||||||
if (config.get('executions.pruneData')) {
|
if (config.get('executions.pruneData')) {
|
||||||
|
@ -334,23 +450,37 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
const isManualMode = [this.mode, parentProcessMode].includes('manual');
|
const isManualMode = [this.mode, parentProcessMode].includes('manual');
|
||||||
|
|
||||||
try {
|
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
|
// Workflow is saved so update in database
|
||||||
try {
|
try {
|
||||||
await WorkflowHelpers.saveStaticDataById(this.workflowData.id as string, newStaticData);
|
await WorkflowHelpers.saveStaticDataById(
|
||||||
|
this.workflowData.id as string,
|
||||||
|
newStaticData,
|
||||||
|
);
|
||||||
} catch (e) {
|
} 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;
|
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
|
// Apply to workflow override
|
||||||
saveManualExecutions = this.workflowData.settings.saveManualExecutions as boolean;
|
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
|
// 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);
|
await Db.collections.Execution!.delete(this.executionId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -359,17 +489,28 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||||
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
||||||
if (this.workflowData.settings !== undefined) {
|
if (this.workflowData.settings !== undefined) {
|
||||||
saveDataErrorExecution = (this.workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
saveDataErrorExecution =
|
||||||
saveDataSuccessExecution = (this.workflowData.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
|
(this.workflowData.settings.saveDataErrorExecution as string) ||
|
||||||
|
saveDataErrorExecution;
|
||||||
|
saveDataSuccessExecution =
|
||||||
|
(this.workflowData.settings.saveDataSuccessExecution as string) ||
|
||||||
|
saveDataSuccessExecution;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
||||||
if (workflowDidSucceed === true && saveDataSuccessExecution === 'none' ||
|
if (
|
||||||
workflowDidSucceed === false && saveDataErrorExecution === 'none'
|
(workflowDidSucceed && saveDataSuccessExecution === 'none') ||
|
||||||
|
(!workflowDidSucceed && saveDataErrorExecution === 'none')
|
||||||
) {
|
) {
|
||||||
if (!fullRunData.waitTill) {
|
if (!fullRunData.waitTill) {
|
||||||
if (!isManualMode) {
|
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
|
// Data is always saved, so we remove from database
|
||||||
await Db.collections.Execution!.delete(this.executionId);
|
await Db.collections.Execution!.delete(this.executionId);
|
||||||
|
@ -391,7 +532,10 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
fullExecutionData.retryOf = this.retryOf.toString();
|
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();
|
fullExecutionData.workflowId = this.workflowData.id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,16 +550,27 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||||
|
|
||||||
// Save the Execution in DB
|
// 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 (fullRunData.finished === true && this.retryOf !== undefined) {
|
||||||
// If the retry was successful save the reference it on the original execution
|
// 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!.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) {
|
if (!isManualMode) {
|
||||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, this.executionId, this.retryOf);
|
executeErrorWorkflow(
|
||||||
|
this.workflowData,
|
||||||
|
fullRunData,
|
||||||
|
this.mode,
|
||||||
|
this.executionId,
|
||||||
|
this.retryOf,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(`Failed saving execution data to DB on execution ID ${this.executionId}`, {
|
Logger.error(`Failed saving execution data to DB on execution ID ${this.executionId}`, {
|
||||||
|
@ -425,7 +580,13 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isManualMode) {
|
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
|
* Returns hook functions to save workflow execution and call error workflow
|
||||||
* for running with queues. Manual executions should never run on queues as
|
* for running with queues. Manual executions should never run on queues as
|
||||||
|
@ -447,20 +607,36 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
||||||
nodeExecuteAfter: [],
|
nodeExecuteAfter: [],
|
||||||
workflowExecuteBefore: [],
|
workflowExecuteBefore: [],
|
||||||
workflowExecuteAfter: [
|
workflowExecuteAfter: [
|
||||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
async function (
|
||||||
|
this: WorkflowHooks,
|
||||||
|
fullRunData: IRun,
|
||||||
|
newStaticData: IDataObject,
|
||||||
|
): Promise<void> {
|
||||||
try {
|
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
|
// Workflow is saved so update in database
|
||||||
try {
|
try {
|
||||||
await WorkflowHelpers.saveStaticDataById(this.workflowData.id as string, newStaticData);
|
await WorkflowHelpers.saveStaticDataById(
|
||||||
|
this.workflowData.id as string,
|
||||||
|
newStaticData,
|
||||||
|
);
|
||||||
} catch (e) {
|
} 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;
|
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
||||||
if (workflowDidSucceed === false) {
|
if (!workflowDidSucceed) {
|
||||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
executeErrorWorkflow(
|
||||||
|
this.workflowData,
|
||||||
|
fullRunData,
|
||||||
|
this.mode,
|
||||||
|
undefined,
|
||||||
|
this.retryOf,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullExecutionData: IExecutionDb = {
|
const fullExecutionData: IExecutionDb = {
|
||||||
|
@ -477,18 +653,26 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
||||||
fullExecutionData.retryOf = this.retryOf.toString();
|
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();
|
fullExecutionData.workflowId = this.workflowData.id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||||
|
|
||||||
// Save the Execution in DB
|
// 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 (fullRunData.finished === true && this.retryOf !== undefined) {
|
||||||
// If the retry was successful save the reference it on the original execution
|
// 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) {
|
} catch (error) {
|
||||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
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';
|
const mode = 'integrated';
|
||||||
|
|
||||||
// Find Start-Node
|
// Find Start-Node
|
||||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
||||||
let startNode: INode | undefined;
|
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)) {
|
if (requiredNodeTypes.includes(node.type)) {
|
||||||
startNode = node;
|
startNode = node;
|
||||||
break;
|
break;
|
||||||
|
@ -525,18 +713,15 @@ export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeE
|
||||||
|
|
||||||
// Initialize the incoming data
|
// Initialize the incoming data
|
||||||
const nodeExecutionStack: IExecuteData[] = [];
|
const nodeExecutionStack: IExecuteData[] = [];
|
||||||
nodeExecutionStack.push(
|
nodeExecutionStack.push({
|
||||||
{
|
|
||||||
node: startNode,
|
node: startNode,
|
||||||
data: {
|
data: {
|
||||||
main: [inputData],
|
main: [inputData],
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const runExecutionData: IRunExecutionData = {
|
const runExecutionData: IRunExecutionData = {
|
||||||
startData: {
|
startData: {},
|
||||||
},
|
|
||||||
resultData: {
|
resultData: {
|
||||||
runData: {},
|
runData: {},
|
||||||
},
|
},
|
||||||
|
@ -557,13 +742,14 @@ export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeE
|
||||||
return runData;
|
return runData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promise<IWorkflowBase> {
|
export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promise<IWorkflowBase> {
|
||||||
if (workflowInfo.id === undefined && workflowInfo.code === undefined) {
|
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
|
// The first time executeWorkflow gets called the Database has
|
||||||
// to get initialized first
|
// to get initialized first
|
||||||
await Db.init();
|
await Db.init();
|
||||||
|
@ -571,7 +757,7 @@ export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promi
|
||||||
|
|
||||||
let workflowData: IWorkflowBase | undefined;
|
let workflowData: IWorkflowBase | undefined;
|
||||||
if (workflowInfo.id !== undefined) {
|
if (workflowInfo.id !== undefined) {
|
||||||
workflowData = await Db.collections!.Workflow!.findOne(workflowInfo.id);
|
workflowData = await Db.collections.Workflow!.findOne(workflowInfo.id);
|
||||||
if (workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
throw new Error(`The workflow with the id "${workflowInfo.id}" does not exist.`);
|
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!;
|
return workflowData!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the workflow with the given ID
|
* Executes the workflow with the given ID
|
||||||
*
|
*
|
||||||
|
@ -592,25 +777,45 @@ export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promi
|
||||||
* @param {INodeExecutionData[]} [inputData]
|
* @param {INodeExecutionData[]} [inputData]
|
||||||
* @returns {(Promise<Array<INodeExecutionData[] | null>>)}
|
* @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();
|
const externalHooks = ExternalHooks();
|
||||||
await externalHooks.init();
|
await externalHooks.init();
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
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 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;
|
let executionId;
|
||||||
|
|
||||||
if (parentExecutionId !== undefined) {
|
if (parentExecutionId !== undefined) {
|
||||||
executionId = parentExecutionId;
|
executionId = parentExecutionId;
|
||||||
} else {
|
} else {
|
||||||
executionId = parentExecutionId !== undefined ? parentExecutionId : await ActiveExecutions.getInstance().add(runData);
|
executionId =
|
||||||
|
parentExecutionId !== undefined
|
||||||
|
? parentExecutionId
|
||||||
|
: await ActiveExecutions.getInstance().add(runData);
|
||||||
}
|
}
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
|
@ -618,18 +823,29 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||||
// Create new additionalData to have different workflow loaded and to call
|
// Create new additionalData to have different workflow loaded and to call
|
||||||
// different webooks
|
// different webooks
|
||||||
const additionalDataIntegrated = await getBase();
|
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
|
// Make sure we pass on the original executeWorkflow function we received
|
||||||
// This one already contains changes to talk to parent process
|
// This one already contains changes to talk to parent process
|
||||||
// and get executionID from `activeExecutions` running on main process
|
// and get executionID from `activeExecutions` running on main process
|
||||||
additionalDataIntegrated.executeWorkflow = additionalData.executeWorkflow;
|
additionalDataIntegrated.executeWorkflow = additionalData.executeWorkflow;
|
||||||
|
|
||||||
let subworkflowTimeout = additionalData.executionTimeoutTimestamp;
|
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
|
// 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 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.
|
// 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;
|
additionalDataIntegrated.executionTimeoutTimestamp = subworkflowTimeout;
|
||||||
|
@ -637,7 +853,11 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||||
const runExecutionData = runData.executionData as IRunExecutionData;
|
const runExecutionData = runData.executionData as IRunExecutionData;
|
||||||
|
|
||||||
// Execute the workflow
|
// Execute the workflow
|
||||||
const workflowExecute = new WorkflowExecute(additionalDataIntegrated, runData.executionMode, runExecutionData);
|
const workflowExecute = new WorkflowExecute(
|
||||||
|
additionalDataIntegrated,
|
||||||
|
runData.executionMode,
|
||||||
|
runExecutionData,
|
||||||
|
);
|
||||||
if (parentExecutionId !== undefined) {
|
if (parentExecutionId !== undefined) {
|
||||||
// Must be changed to become typed
|
// Must be changed to become typed
|
||||||
return {
|
return {
|
||||||
|
@ -678,7 +898,7 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||||
await Db.collections.Execution!.update(executionId, executionData as IExecutionFlattedDb);
|
await Db.collections.Execution!.update(executionId, executionData as IExecutionFlattedDb);
|
||||||
throw {
|
throw {
|
||||||
...error,
|
...error,
|
||||||
stack: error!.stack,
|
stack: error.stack,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,19 +910,19 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||||
await ActiveExecutions.getInstance().remove(executionId, data);
|
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||||
return returnData!.data!.main;
|
return returnData!.data!.main;
|
||||||
} else {
|
}
|
||||||
await ActiveExecutions.getInstance().remove(executionId, data);
|
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||||
// Workflow did fail
|
// Workflow did fail
|
||||||
const { error } = data.data.resultData;
|
const { error } = data.data.resultData;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||||
throw {
|
throw {
|
||||||
...error,
|
...error,
|
||||||
stack: error!.stack,
|
stack: error!.stack,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function sendMessageToUI(source: string, message: any) { // tslint:disable-line:no-any
|
export function sendMessageToUI(source: string, message: any) {
|
||||||
if (this.sessionId === undefined) {
|
if (this.sessionId === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -710,16 +930,19 @@ export function sendMessageToUI(source: string, message: any) { // tslint:disabl
|
||||||
// Push data to session which started workflow
|
// Push data to session which started workflow
|
||||||
try {
|
try {
|
||||||
const pushInstance = Push.getInstance();
|
const pushInstance = Push.getInstance();
|
||||||
pushInstance.send('sendConsoleMessage', {
|
pushInstance.send(
|
||||||
|
'sendConsoleMessage',
|
||||||
|
{
|
||||||
source: `Node: "${source}"`,
|
source: `Node: "${source}"`,
|
||||||
message,
|
message,
|
||||||
}, this.sessionId);
|
},
|
||||||
|
this.sessionId,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.warn(`There was a problem sending messsage to UI: ${error.message}`);
|
Logger.warn(`There was a problem sending messsage to UI: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the base additional data without webhooks
|
* Returns the base additional data without webhooks
|
||||||
*
|
*
|
||||||
|
@ -728,13 +951,16 @@ export function sendMessageToUI(source: string, message: any) { // tslint:disabl
|
||||||
* @param {INodeParameters} currentNodeParameters
|
* @param {INodeParameters} currentNodeParameters
|
||||||
* @returns {Promise<IWorkflowExecuteAdditionalData>}
|
* @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 urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||||
|
|
||||||
const timezone = config.get('generic.timezone') as string;
|
const timezone = config.get('generic.timezone') as string;
|
||||||
const webhookBaseUrl = urlBaseWebhook + config.get('endpoints.webhook') as string;
|
const webhookBaseUrl = urlBaseWebhook + config.get('endpoints.webhook');
|
||||||
const webhookWaitingBaseUrl = urlBaseWebhook + config.get('endpoints.webhookWaiting') as string;
|
const webhookWaitingBaseUrl = urlBaseWebhook + config.get('endpoints.webhookWaiting');
|
||||||
const webhookTestBaseUrl = urlBaseWebhook + config.get('endpoints.webhookTest') as string;
|
const webhookTestBaseUrl = urlBaseWebhook + config.get('endpoints.webhookTest');
|
||||||
|
|
||||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||||
if (encryptionKey === undefined) {
|
if (encryptionKey === undefined) {
|
||||||
|
@ -745,7 +971,7 @@ export async function getBase(currentNodeParameters?: INodeParameters, execution
|
||||||
credentialsHelper: new CredentialsHelper(encryptionKey),
|
credentialsHelper: new CredentialsHelper(encryptionKey),
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
executeWorkflow,
|
executeWorkflow,
|
||||||
restApiUrl: urlBaseWebhook + config.get('endpoints.rest') as string,
|
restApiUrl: urlBaseWebhook + config.get('endpoints.rest'),
|
||||||
timezone,
|
timezone,
|
||||||
webhookBaseUrl,
|
webhookBaseUrl,
|
||||||
webhookWaitingBaseUrl,
|
webhookWaitingBaseUrl,
|
||||||
|
@ -755,12 +981,16 @@ export async function getBase(currentNodeParameters?: INodeParameters, execution
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns WorkflowHooks instance for running integrated workflows
|
* Returns WorkflowHooks instance for running integrated workflows
|
||||||
* (Workflows which get started inside of another workflow)
|
* (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 || {};
|
optionalParameters = optionalParameters || {};
|
||||||
const hookFunctions = hookFunctionsSave(optionalParameters.parentProcessMode);
|
const hookFunctions = hookFunctionsSave(optionalParameters.parentProcessMode);
|
||||||
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
||||||
|
@ -777,7 +1007,12 @@ export function getWorkflowHooksIntegrated(mode: WorkflowExecuteMode, executionI
|
||||||
* Returns WorkflowHooks instance for running integrated workflows
|
* Returns WorkflowHooks instance for running integrated workflows
|
||||||
* (Workflows which get started inside of another workflow)
|
* (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 || {};
|
optionalParameters = optionalParameters || {};
|
||||||
const hookFunctions = hookFunctionsSaveWorker();
|
const hookFunctions = hookFunctionsSaveWorker();
|
||||||
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
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
|
* 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 || {};
|
optionalParameters = optionalParameters || {};
|
||||||
const hookFunctions = hookFunctionsPush();
|
const hookFunctions = hookFunctionsPush();
|
||||||
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
||||||
|
@ -812,7 +1052,6 @@ export function getWorkflowHooksWorkerMain(mode: WorkflowExecuteMode, executionI
|
||||||
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters);
|
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns WorkflowHooks instance for running the main workflow
|
* Returns WorkflowHooks instance for running the main workflow
|
||||||
*
|
*
|
||||||
|
@ -821,7 +1060,11 @@ export function getWorkflowHooksWorkerMain(mode: WorkflowExecuteMode, executionI
|
||||||
* @param {string} executionId
|
* @param {string} executionId
|
||||||
* @returns {WorkflowHooks}
|
* @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 hookFunctions = hookFunctionsSave();
|
||||||
const pushFunctions = hookFunctionsPush();
|
const pushFunctions = hookFunctionsPush();
|
||||||
for (const key of Object.keys(pushFunctions)) {
|
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 {
|
import {
|
||||||
CredentialTypes,
|
CredentialTypes,
|
||||||
Db,
|
Db,
|
||||||
|
@ -7,28 +28,17 @@ import {
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
WorkflowCredentials,
|
WorkflowCredentials,
|
||||||
WorkflowRunner,
|
WorkflowRunner,
|
||||||
} from './';
|
} from '.';
|
||||||
|
|
||||||
import {
|
|
||||||
IDataObject,
|
|
||||||
IExecuteData,
|
|
||||||
INode,
|
|
||||||
IRun,
|
|
||||||
IRunExecutionData,
|
|
||||||
ITaskData,
|
|
||||||
IWorkflowCredentials,
|
|
||||||
LoggerProxy as Logger,
|
|
||||||
Workflow,} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
|
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
|
||||||
import { validate } from 'class-validator';
|
|
||||||
|
|
||||||
const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data of the last executed node
|
* 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)}
|
* @returns {(ITaskData | undefined)}
|
||||||
*/
|
*/
|
||||||
export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefined {
|
export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefined {
|
||||||
const runData = inputData.data.resultData.runData;
|
const { runData } = inputData.data.resultData;
|
||||||
const lastNodeExecuted = inputData.data.resultData.lastNodeExecuted;
|
const { lastNodeExecuted } = inputData.data.resultData;
|
||||||
|
|
||||||
if (lastNodeExecuted === undefined) {
|
if (lastNodeExecuted === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -51,8 +61,6 @@ export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefi
|
||||||
return runData[lastNodeExecuted][runData[lastNodeExecuted].length - 1];
|
return runData[lastNodeExecuted][runData[lastNodeExecuted].length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the given id is a valid workflow id
|
* Returns if the given id is a valid workflow id
|
||||||
*
|
*
|
||||||
|
@ -60,20 +68,18 @@ export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefi
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
* @memberof App
|
* @memberof App
|
||||||
*/
|
*/
|
||||||
export function isWorkflowIdValid (id: string | null | undefined | number): boolean {
|
export function isWorkflowIdValid(id: string | null | undefined | number): boolean {
|
||||||
if (typeof id === 'string') {
|
if (typeof id === 'string') {
|
||||||
id = parseInt(id, 10);
|
id = parseInt(id, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
if (isNaN(id as number)) {
|
if (isNaN(id as number)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the error workflow
|
* Executes the error workflow
|
||||||
*
|
*
|
||||||
|
@ -82,21 +88,37 @@ export function isWorkflowIdValid (id: string | null | undefined | number): bool
|
||||||
* @param {IWorkflowErrorData} workflowErrorData The error data
|
* @param {IWorkflowErrorData} workflowErrorData The error data
|
||||||
* @returns {Promise<void>}
|
* @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
|
// Wrap everything in try/catch to make sure that no errors bubble up and all get caught here
|
||||||
try {
|
try {
|
||||||
const workflowData = await Db.collections.Workflow!.findOne({ id: Number(workflowId) });
|
const workflowData = await Db.collections.Workflow!.findOne({ id: Number(workflowId) });
|
||||||
|
|
||||||
if (workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
// The error workflow could not be found
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionMode = 'error';
|
const executionMode = 'error';
|
||||||
const nodeTypes = NodeTypes();
|
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 node: INode;
|
||||||
let workflowStartNode: INode | undefined;
|
let workflowStartNode: INode | undefined;
|
||||||
|
@ -108,7 +130,9 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflowStartNode === undefined) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,8 +140,7 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
||||||
|
|
||||||
// Initialize the data of the webhook node
|
// Initialize the data of the webhook node
|
||||||
const nodeExecutionStack: IExecuteData[] = [];
|
const nodeExecutionStack: IExecuteData[] = [];
|
||||||
nodeExecutionStack.push(
|
nodeExecutionStack.push({
|
||||||
{
|
|
||||||
node: workflowStartNode,
|
node: workflowStartNode,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
|
@ -128,12 +151,10 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const runExecutionData: IRunExecutionData = {
|
const runExecutionData: IRunExecutionData = {
|
||||||
startData: {
|
startData: {},
|
||||||
},
|
|
||||||
resultData: {
|
resultData: {
|
||||||
runData: {},
|
runData: {},
|
||||||
},
|
},
|
||||||
|
@ -153,12 +174,13 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
||||||
const workflowRunner = new WorkflowRunner();
|
const workflowRunner = new WorkflowRunner();
|
||||||
await workflowRunner.run(runData);
|
await workflowRunner.run(runData);
|
||||||
} catch (error) {
|
} 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
|
* Returns all the defined NodeTypes
|
||||||
*
|
*
|
||||||
|
@ -185,8 +207,6 @@ export function getAllNodeTypeData(): ITransferNodeTypes {
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data of the node types that are needed
|
* Returns the data of the node types that are needed
|
||||||
* to execute the given nodes
|
* to execute the given nodes
|
||||||
|
@ -199,6 +219,7 @@ export function getNodeTypeData(nodes: INode[]): ITransferNodeTypes {
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
|
|
||||||
// Check which node-types have to be loaded
|
// Check which node-types have to be loaded
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
const neededNodeTypes = getNeededNodeTypes(nodes);
|
const neededNodeTypes = getNeededNodeTypes(nodes);
|
||||||
|
|
||||||
// Get all the data of the needed node types that they
|
// Get all the data of the needed node types that they
|
||||||
|
@ -218,8 +239,6 @@ export function getNodeTypeData(nodes: INode[]): ITransferNodeTypes {
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the credentials data of the given type and its parent types
|
* Returns the credentials data of the given type and its parent types
|
||||||
* it extends
|
* it extends
|
||||||
|
@ -251,8 +270,6 @@ export function getCredentialsDataWithParents(type: string): ICredentialsTypeDat
|
||||||
return credentialTypeData;
|
return credentialTypeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the credentialTypes which are needed to resolve
|
* Returns all the credentialTypes which are needed to resolve
|
||||||
* the given workflow credentials
|
* the given workflow credentials
|
||||||
|
@ -262,14 +279,13 @@ export function getCredentialsDataWithParents(type: string): ICredentialsTypeDat
|
||||||
* @returns {ICredentialsTypeData}
|
* @returns {ICredentialsTypeData}
|
||||||
*/
|
*/
|
||||||
export function getCredentialsDataByNodes(nodes: INode[]): ICredentialsTypeData {
|
export function getCredentialsDataByNodes(nodes: INode[]): ICredentialsTypeData {
|
||||||
|
|
||||||
const credentialTypeData: ICredentialsTypeData = {};
|
const credentialTypeData: ICredentialsTypeData = {};
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const credentialsUsedByThisNode = node.credentials;
|
const credentialsUsedByThisNode = node.credentials;
|
||||||
if (credentialsUsedByThisNode) {
|
if (credentialsUsedByThisNode) {
|
||||||
// const credentialTypesUsedByThisNode = Object.keys(credentialsUsedByThisNode!);
|
// const credentialTypesUsedByThisNode = Object.keys(credentialsUsedByThisNode!);
|
||||||
for (const credentialType of Object.keys(credentialsUsedByThisNode!)) {
|
for (const credentialType of Object.keys(credentialsUsedByThisNode)) {
|
||||||
if (credentialTypeData[credentialType] !== undefined) {
|
if (credentialTypeData[credentialType] !== undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -277,14 +293,11 @@ export function getCredentialsDataByNodes(nodes: INode[]): ICredentialsTypeData
|
||||||
Object.assign(credentialTypeData, getCredentialsDataWithParents(credentialType));
|
Object.assign(credentialTypeData, getCredentialsDataWithParents(credentialType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return credentialTypeData;
|
return credentialTypeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the names of the NodeTypes which are are needed
|
* Returns the names of the NodeTypes which are are needed
|
||||||
* to execute the gives nodes
|
* to execute the gives nodes
|
||||||
|
@ -305,8 +318,6 @@ export function getNeededNodeTypes(nodes: INode[]): string[] {
|
||||||
return neededNodeTypes;
|
return neededNodeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the static data if it changed
|
* Saves the static data if it changed
|
||||||
*
|
*
|
||||||
|
@ -314,23 +325,25 @@ export function getNeededNodeTypes(nodes: INode[]): string[] {
|
||||||
* @param {Workflow} workflow
|
* @param {Workflow} workflow
|
||||||
* @returns {Promise <void>}
|
* @returns {Promise <void>}
|
||||||
*/
|
*/
|
||||||
export async function saveStaticData(workflow: Workflow): Promise <void> {
|
export async function saveStaticData(workflow: Workflow): Promise<void> {
|
||||||
if (workflow.staticData.__dataChanged === true) {
|
if (workflow.staticData.__dataChanged === true) {
|
||||||
// Static data of workflow changed and so has to be saved
|
// 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
|
// Workflow is saved so update in database
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
await saveStaticDataById(workflow.id!, workflow.staticData);
|
await saveStaticDataById(workflow.id!, workflow.staticData);
|
||||||
workflow.staticData.__dataChanged = false;
|
workflow.staticData.__dataChanged = false;
|
||||||
} catch (e) {
|
} 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
|
* 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
|
* @param {IDataObject} newStaticData The static data to save
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export async function saveStaticDataById(workflowId: string | number, newStaticData: IDataObject): Promise<void> {
|
export async function saveStaticDataById(
|
||||||
await Db.collections.Workflow!
|
workflowId: string | number,
|
||||||
.update(workflowId, {
|
newStaticData: IDataObject,
|
||||||
|
): Promise<void> {
|
||||||
|
await Db.collections.Workflow!.update(workflowId, {
|
||||||
staticData: newStaticData,
|
staticData: newStaticData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the static data of workflow
|
* 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
|
* @param {(string | number)} workflowId The id of the workflow to get static data of
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export async function getStaticDataById(workflowId: string | number) {
|
export async function getStaticDataById(workflowId: string | number) {
|
||||||
const workflowData = await Db.collections.Workflow!
|
const workflowData = await Db.collections.Workflow!.findOne(workflowId, {
|
||||||
.findOne(workflowId, { select: ['staticData']});
|
select: ['staticData'],
|
||||||
|
});
|
||||||
|
|
||||||
if (workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
return workflowData.staticData || {};
|
return workflowData.staticData || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Deduplicate `validateWorkflow` and `throwDuplicateEntryError` with TagHelpers?
|
// TODO: Deduplicate `validateWorkflow` and `throwDuplicateEntryError` with TagHelpers?
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export async function validateWorkflow(newWorkflow: WorkflowEntity) {
|
export async function validateWorkflow(newWorkflow: WorkflowEntity) {
|
||||||
const errors = await validate(newWorkflow);
|
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) {
|
export function throwDuplicateEntryError(error: Error) {
|
||||||
const errorMessage = error.message.toLowerCase();
|
const errorMessage = error.message.toLowerCase();
|
||||||
if (errorMessage.includes('unique') || errorMessage.includes('duplicate')) {
|
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);
|
throw new ResponseHelper.ResponseError(errorMessage, undefined, 400);
|
||||||
|
@ -391,6 +412,5 @@ export type WorkflowNameRequest = Express.Request & {
|
||||||
query: {
|
query: {
|
||||||
name?: string;
|
name?: string;
|
||||||
offset?: 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 {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
CredentialsOverwrites,
|
CredentialsOverwrites,
|
||||||
|
@ -20,38 +54,17 @@ import {
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
} from './';
|
} 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';
|
|
||||||
import * as Queue from './Queue';
|
import * as Queue from './Queue';
|
||||||
|
|
||||||
export class WorkflowRunner {
|
export class WorkflowRunner {
|
||||||
activeExecutions: ActiveExecutions.ActiveExecutions;
|
activeExecutions: ActiveExecutions.ActiveExecutions;
|
||||||
credentialsOverwrites: ICredentialsOverwrite;
|
|
||||||
push: Push.Push;
|
|
||||||
jobQueue: Bull.Queue;
|
|
||||||
|
|
||||||
|
credentialsOverwrites: ICredentialsOverwrite;
|
||||||
|
|
||||||
|
push: Push.Push;
|
||||||
|
|
||||||
|
jobQueue: Bull.Queue;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.push = Push.getInstance();
|
this.push = Push.getInstance();
|
||||||
|
@ -65,7 +78,6 @@ export class WorkflowRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The process did send a hook message so execute the appropiate hook
|
* The process did send a hook message so execute the appropiate hook
|
||||||
*
|
*
|
||||||
|
@ -74,10 +86,10 @@ export class WorkflowRunner {
|
||||||
* @memberof WorkflowRunner
|
* @memberof WorkflowRunner
|
||||||
*/
|
*/
|
||||||
processHookMessage(workflowHooks: WorkflowHooks, hookData: IProcessMessageDataHook) {
|
processHookMessage(workflowHooks: WorkflowHooks, hookData: IProcessMessageDataHook) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters);
|
workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The process did error
|
* The process did error
|
||||||
*
|
*
|
||||||
|
@ -87,7 +99,13 @@ export class WorkflowRunner {
|
||||||
* @param {string} executionId
|
* @param {string} executionId
|
||||||
* @memberof WorkflowRunner
|
* @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 = {
|
const fullRunData: IRun = {
|
||||||
data: {
|
data: {
|
||||||
resultData: {
|
resultData: {
|
||||||
|
@ -123,7 +141,12 @@ export class WorkflowRunner {
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
* @memberof WorkflowRunner
|
* @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 executionsProcess = config.get('executions.process') as string;
|
||||||
const executionsMode = config.get('executions.mode') as string;
|
const executionsMode = config.get('executions.mode') as string;
|
||||||
|
|
||||||
|
@ -139,11 +162,12 @@ export class WorkflowRunner {
|
||||||
|
|
||||||
const externalHooks = ExternalHooks();
|
const externalHooks = ExternalHooks();
|
||||||
if (externalHooks.exists('workflow.postExecute')) {
|
if (externalHooks.exists('workflow.postExecute')) {
|
||||||
this.activeExecutions.getPostExecutePromise(executionId)
|
this.activeExecutions
|
||||||
|
.getPostExecutePromise(executionId)
|
||||||
.then(async (executionData) => {
|
.then(async (executionData) => {
|
||||||
await externalHooks.run('workflow.postExecute', [executionData, data.workflowData]);
|
await externalHooks.run('workflow.postExecute', [executionData, data.workflowData]);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error('There was a problem running hook "workflow.postExecute"', error);
|
console.error('There was a problem running hook "workflow.postExecute"', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -151,7 +175,6 @@ export class WorkflowRunner {
|
||||||
return executionId;
|
return executionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the workflow in current process
|
* Run the workflow in current process
|
||||||
*
|
*
|
||||||
|
@ -161,9 +184,15 @@ export class WorkflowRunner {
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
* @memberof WorkflowRunner
|
* @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) {
|
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();
|
const nodeTypes = NodeTypes();
|
||||||
|
@ -174,67 +203,120 @@ export class WorkflowRunner {
|
||||||
let executionTimeout: NodeJS.Timeout;
|
let executionTimeout: NodeJS.Timeout;
|
||||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||||
if (data.workflowData.settings && data.workflowData.settings.executionTimeout) {
|
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) {
|
if (workflowTimeout > 0) {
|
||||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
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 workflow = new Workflow({
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
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
|
// 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;
|
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>;
|
let workflowExecution: PCancelable<IRun>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Logger.verbose(`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`, { executionId });
|
Logger.verbose(
|
||||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId, true);
|
`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`,
|
||||||
additionalData.sendMessageToUI = WorkflowExecuteAdditionalData.sendMessageToUI.bind({sessionId: data.sessionId});
|
{ executionId },
|
||||||
|
);
|
||||||
|
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(
|
||||||
|
data,
|
||||||
|
executionId,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
additionalData.sendMessageToUI = WorkflowExecuteAdditionalData.sendMessageToUI.bind({
|
||||||
|
sessionId: data.sessionId,
|
||||||
|
});
|
||||||
|
|
||||||
if (data.executionData !== undefined) {
|
if (data.executionData !== undefined) {
|
||||||
Logger.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, {executionId});
|
Logger.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, {
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, data.executionMode, data.executionData);
|
executionId,
|
||||||
|
});
|
||||||
|
const workflowExecute = new WorkflowExecute(
|
||||||
|
additionalData,
|
||||||
|
data.executionMode,
|
||||||
|
data.executionData,
|
||||||
|
);
|
||||||
workflowExecution = workflowExecute.processRunExecutionData(workflow);
|
workflowExecution = workflowExecute.processRunExecutionData(workflow);
|
||||||
} else if (data.runData === undefined || data.startNodes === undefined || data.startNodes.length === 0 || data.destinationNode === undefined) {
|
} else if (
|
||||||
Logger.debug(`Execution ID ${executionId} will run executing all nodes.`, {executionId});
|
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
|
// Execute all nodes
|
||||||
|
|
||||||
// Can execute without webhook so go on
|
// Can execute without webhook so go on
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, data.executionMode);
|
const workflowExecute = new WorkflowExecute(additionalData, data.executionMode);
|
||||||
workflowExecution = workflowExecute.run(workflow, undefined, data.destinationNode);
|
workflowExecution = workflowExecute.run(workflow, undefined, data.destinationNode);
|
||||||
} else {
|
} 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
|
// Execute only the nodes between start and destination nodes
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, data.executionMode);
|
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);
|
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
||||||
|
|
||||||
if (workflowTimeout > 0) {
|
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(() => {
|
executionTimeout = setTimeout(() => {
|
||||||
this.activeExecutions.stopExecution(executionId, 'timeout');
|
this.activeExecutions.stopExecution(executionId, 'timeout');
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
workflowExecution.then((fullRunData) => {
|
workflowExecution
|
||||||
|
.then((fullRunData) => {
|
||||||
clearTimeout(executionTimeout);
|
clearTimeout(executionTimeout);
|
||||||
if (workflowExecution.isCanceled) {
|
if (workflowExecution.isCanceled) {
|
||||||
fullRunData.finished = false;
|
fullRunData.finished = false;
|
||||||
}
|
}
|
||||||
this.activeExecutions.remove(executionId, fullRunData);
|
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) {
|
} 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;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -242,8 +324,12 @@ export class WorkflowRunner {
|
||||||
return executionId;
|
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
|
// TODO: If "loadStaticData" is set to true it has to load data new on worker
|
||||||
|
|
||||||
// Register the active execution
|
// Register the active execution
|
||||||
|
@ -271,9 +357,14 @@ export class WorkflowRunner {
|
||||||
try {
|
try {
|
||||||
job = await this.jobQueue.add(jobData, jobOptions);
|
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
|
// Normally also workflow should be supplied here but as it only used for sending
|
||||||
// data to editor-UI is not needed.
|
// data to editor-UI is not needed.
|
||||||
|
@ -281,19 +372,30 @@ export class WorkflowRunner {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
||||||
// "workflowExecuteAfter" which we require.
|
// "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);
|
await this.processError(error, new Date(), data.executionMode, executionId, hooks);
|
||||||
throw error;
|
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.shouldReject = false;
|
||||||
onCancel(async () => {
|
onCancel(async () => {
|
||||||
await Queue.getInstance().stopJob(job);
|
await Queue.getInstance().stopJob(job);
|
||||||
|
|
||||||
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
||||||
// "workflowExecuteAfter" which we require.
|
// "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!');
|
const error = new WorkflowOperationError('Workflow-Execution has been canceled!');
|
||||||
await this.processError(error, new Date(), data.executionMode, executionId, hooksWorker);
|
await this.processError(error, new Date(), data.executionMode, executionId, hooksWorker);
|
||||||
|
@ -309,7 +411,7 @@ export class WorkflowRunner {
|
||||||
|
|
||||||
let clearWatchdogInterval;
|
let clearWatchdogInterval;
|
||||||
if (queueRecoveryInterval > 0) {
|
if (queueRecoveryInterval > 0) {
|
||||||
/*************************************************
|
/** ***********************************************
|
||||||
* Long explanation about what this solves: *
|
* Long explanation about what this solves: *
|
||||||
* This only happens in a very specific scenario *
|
* This only happens in a very specific scenario *
|
||||||
* when Redis crashes and recovers shortly *
|
* when Redis crashes and recovers shortly *
|
||||||
|
@ -320,7 +422,7 @@ export class WorkflowRunner {
|
||||||
* the queue that allows us to identify that the *
|
* the queue that allows us to identify that the *
|
||||||
* execution finished and get information from *
|
* execution finished and get information from *
|
||||||
* the database. *
|
* the database. *
|
||||||
*************************************************/
|
************************************************ */
|
||||||
let watchDogInterval: NodeJS.Timeout | undefined;
|
let watchDogInterval: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
const watchDog: Promise<object> = new Promise((res) => {
|
const watchDog: Promise<object> = new Promise((res) => {
|
||||||
|
@ -329,7 +431,7 @@ export class WorkflowRunner {
|
||||||
// When null means job is finished (not found in queue)
|
// When null means job is finished (not found in queue)
|
||||||
if (currentJob === null) {
|
if (currentJob === null) {
|
||||||
// Mimic worker's success message
|
// Mimic worker's success message
|
||||||
res({success: true});
|
res({ success: true });
|
||||||
}
|
}
|
||||||
}, queueRecoveryInterval * 1000);
|
}, queueRecoveryInterval * 1000);
|
||||||
});
|
});
|
||||||
|
@ -352,7 +454,12 @@ export class WorkflowRunner {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
|
||||||
// "workflowExecuteAfter" which we require.
|
// "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.`);
|
Logger.error(`Problem with execution ${executionId}: ${error.message}. Aborting.`);
|
||||||
if (clearWatchdogInterval !== undefined) {
|
if (clearWatchdogInterval !== undefined) {
|
||||||
clearWatchdogInterval();
|
clearWatchdogInterval();
|
||||||
|
@ -362,8 +469,10 @@ export class WorkflowRunner {
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionDb = await Db.collections.Execution!.findOne(executionId) as IExecutionFlattedDb;
|
const executionDb = (await Db.collections.Execution!.findOne(
|
||||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb) as IExecutionResponse;
|
executionId,
|
||||||
|
)) as IExecutionFlattedDb;
|
||||||
|
const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb);
|
||||||
const runData = {
|
const runData = {
|
||||||
data: fullExecutionData.data,
|
data: fullExecutionData.data,
|
||||||
finished: fullExecutionData.finished,
|
finished: fullExecutionData.finished,
|
||||||
|
@ -382,29 +491,35 @@ export class WorkflowRunner {
|
||||||
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||||
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
||||||
if (data.workflowData.settings !== undefined) {
|
if (data.workflowData.settings !== undefined) {
|
||||||
saveDataErrorExecution = (data.workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
saveDataErrorExecution =
|
||||||
saveDataSuccessExecution = (data.workflowData.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
|
(data.workflowData.settings.saveDataErrorExecution as string) ||
|
||||||
|
saveDataErrorExecution;
|
||||||
|
saveDataSuccessExecution =
|
||||||
|
(data.workflowData.settings.saveDataSuccessExecution as string) ||
|
||||||
|
saveDataSuccessExecution;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowDidSucceed = !runData.data.resultData.error;
|
const workflowDidSucceed = !runData.data.resultData.error;
|
||||||
if (workflowDidSucceed === true && saveDataSuccessExecution === 'none' ||
|
if (
|
||||||
workflowDidSucceed === false && saveDataErrorExecution === 'none'
|
(workflowDidSucceed && saveDataSuccessExecution === 'none') ||
|
||||||
|
(!workflowDidSucceed && saveDataErrorExecution === 'none')
|
||||||
) {
|
) {
|
||||||
await Db.collections.Execution!.delete(executionId);
|
await Db.collections.Execution!.delete(executionId);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line id-denylist
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// We don't want errors here to crash n8n. Just log and proceed.
|
// We don't want errors here to crash n8n. Just log and proceed.
|
||||||
console.log('Error removing saved execution from database. More details: ', err);
|
console.log('Error removing saved execution from database. More details: ', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(runData);
|
resolve(runData);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
||||||
return executionId;
|
return executionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the workflow
|
* Run the workflow
|
||||||
*
|
*
|
||||||
|
@ -414,12 +529,18 @@ export class WorkflowRunner {
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
* @memberof WorkflowRunner
|
* @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();
|
let startedAt = new Date();
|
||||||
const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js'));
|
const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js'));
|
||||||
|
|
||||||
if (loadStaticData === true && data.workflowData.id) {
|
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
|
// Register the active execution
|
||||||
|
@ -437,8 +558,9 @@ export class WorkflowRunner {
|
||||||
}
|
}
|
||||||
let nodeTypeData: ITransferNodeTypes;
|
let nodeTypeData: ITransferNodeTypes;
|
||||||
let credentialTypeData: ICredentialsTypeData;
|
let credentialTypeData: ICredentialsTypeData;
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
let credentialsOverwrites = this.credentialsOverwrites;
|
let credentialsOverwrites = this.credentialsOverwrites;
|
||||||
if (loadAllNodeTypes === true) {
|
if (loadAllNodeTypes) {
|
||||||
// Supply all nodeTypes and credentialTypes
|
// Supply all nodeTypes and credentialTypes
|
||||||
nodeTypeData = WorkflowHelpers.getAllNodeTypeData();
|
nodeTypeData = WorkflowHelpers.getAllNodeTypeData();
|
||||||
const credentialTypes = CredentialTypes();
|
const credentialTypes = CredentialTypes();
|
||||||
|
@ -458,8 +580,10 @@ export class WorkflowRunner {
|
||||||
|
|
||||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
||||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
|
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
|
||||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = this.credentialsOverwrites;
|
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite =
|
||||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData = credentialTypeData;
|
this.credentialsOverwrites;
|
||||||
|
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData =
|
||||||
|
credentialTypeData;
|
||||||
|
|
||||||
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
||||||
|
|
||||||
|
@ -475,7 +599,7 @@ export class WorkflowRunner {
|
||||||
let executionTimeout: NodeJS.Timeout;
|
let executionTimeout: NodeJS.Timeout;
|
||||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||||
if (data.workflowData.settings && data.workflowData.settings.executionTimeout) {
|
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) => {
|
const processTimeoutFunction = (timeout: number) => {
|
||||||
|
@ -484,11 +608,16 @@ export class WorkflowRunner {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (workflowTimeout > 0) {
|
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.
|
// 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
|
// 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.
|
// 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
|
// Create a list of child spawned executions
|
||||||
|
@ -498,7 +627,10 @@ export class WorkflowRunner {
|
||||||
|
|
||||||
// Listen to data from the subprocess
|
// Listen to data from the subprocess
|
||||||
subprocess.on('message', async (message: IProcessMessage) => {
|
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') {
|
if (message.type === 'start') {
|
||||||
// Now that the execution actually started set the timeout again so that does not time out to early.
|
// Now that the execution actually started set the timeout again so that does not time out to early.
|
||||||
startedAt = new Date();
|
startedAt = new Date();
|
||||||
|
@ -506,18 +638,25 @@ export class WorkflowRunner {
|
||||||
clearTimeout(executionTimeout);
|
clearTimeout(executionTimeout);
|
||||||
executionTimeout = setTimeout(processTimeoutFunction, workflowTimeout, workflowTimeout);
|
executionTimeout = setTimeout(processTimeoutFunction, workflowTimeout, workflowTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (message.type === 'end') {
|
} else if (message.type === 'end') {
|
||||||
clearTimeout(executionTimeout);
|
clearTimeout(executionTimeout);
|
||||||
this.activeExecutions.remove(executionId!, message.data.runData);
|
this.activeExecutions.remove(executionId, message.data.runData);
|
||||||
|
|
||||||
} else if (message.type === 'sendMessageToUI') {
|
} 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') {
|
} else if (message.type === 'processError') {
|
||||||
clearTimeout(executionTimeout);
|
clearTimeout(executionTimeout);
|
||||||
const executionError = message.data.executionError as ExecutionError;
|
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') {
|
} else if (message.type === 'processHook') {
|
||||||
this.processHookMessage(workflowHooks, message.data as IProcessMessageDataHook);
|
this.processHookMessage(workflowHooks, message.data as IProcessMessageDataHook);
|
||||||
} else if (message.type === 'timeout') {
|
} else if (message.type === 'timeout') {
|
||||||
|
@ -529,43 +668,61 @@ export class WorkflowRunner {
|
||||||
} else if (message.type === 'startExecution') {
|
} else if (message.type === 'startExecution') {
|
||||||
const executionId = await this.activeExecutions.add(message.data.runData);
|
const executionId = await this.activeExecutions.add(message.data.runData);
|
||||||
childExecutionIds.push(executionId);
|
childExecutionIds.push(executionId);
|
||||||
subprocess.send({ type: 'executionId', data: {executionId} } as IProcessMessage);
|
subprocess.send({ type: 'executionId', data: { executionId } } as IProcessMessage);
|
||||||
} else if (message.type === 'finishExecution') {
|
} else if (message.type === 'finishExecution') {
|
||||||
const executionIdIndex = childExecutionIds.indexOf(message.data.executionId);
|
const executionIdIndex = childExecutionIds.indexOf(message.data.executionId);
|
||||||
if (executionIdIndex !== -1) {
|
if (executionIdIndex !== -1) {
|
||||||
childExecutionIds.splice(executionIdIndex, 1);
|
childExecutionIds.splice(executionIdIndex, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||||
await this.activeExecutions.remove(message.data.executionId, message.data.result);
|
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
|
// Also get informed when the processes does exit especially when it did crash or timed out
|
||||||
subprocess.on('exit', async (code, signal) => {
|
subprocess.on('exit', async (code, signal) => {
|
||||||
if (signal === 'SIGTERM'){
|
if (signal === 'SIGTERM') {
|
||||||
Logger.debug(`Subprocess for execution ID ${executionId} timed out.`, {executionId});
|
Logger.debug(`Subprocess for execution ID ${executionId} timed out.`, { executionId });
|
||||||
// Execution timed out and its process has been terminated
|
// Execution timed out and its process has been terminated
|
||||||
const timeoutError = new WorkflowOperationError('Workflow execution timed out!');
|
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) {
|
} 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.
|
// 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
|
// When the child process exits, if we still have
|
||||||
// pending child executions, we mark them as finished
|
// pending child executions, we mark them as finished
|
||||||
// They will display as unknown to the user
|
// They will display as unknown to the user
|
||||||
// Instead of pending forever as executing when it
|
// Instead of pending forever as executing when it
|
||||||
// actually isn't anymore.
|
// actually isn't anymore.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/await-thenable, no-await-in-loop
|
||||||
await this.activeExecutions.remove(executionId);
|
await this.activeExecutions.remove(executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
clearTimeout(executionTimeout);
|
clearTimeout(executionTimeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
import {
|
/* eslint-disable consistent-return */
|
||||||
CredentialsOverwrites,
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
CredentialTypes,
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
Db,
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
ExternalHooks,
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
IWorkflowExecuteProcess,
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
IWorkflowExecutionDataProcessWithExecution,
|
import { IProcessMessage, WorkflowExecute } from 'n8n-core';
|
||||||
NodeTypes,
|
|
||||||
WorkflowExecuteAdditionalData,
|
|
||||||
WorkflowHelpers,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IProcessMessage,
|
|
||||||
WorkflowExecute,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExecutionError,
|
ExecutionError,
|
||||||
|
@ -34,24 +25,41 @@ import {
|
||||||
WorkflowHooks,
|
WorkflowHooks,
|
||||||
WorkflowOperationError,
|
WorkflowOperationError,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getLogger,
|
CredentialsOverwrites,
|
||||||
} from '../src/Logger';
|
CredentialTypes,
|
||||||
|
Db,
|
||||||
|
ExternalHooks,
|
||||||
|
IWorkflowExecuteProcess,
|
||||||
|
IWorkflowExecutionDataProcessWithExecution,
|
||||||
|
NodeTypes,
|
||||||
|
WorkflowExecuteAdditionalData,
|
||||||
|
WorkflowHelpers,
|
||||||
|
} from '.';
|
||||||
|
|
||||||
|
import { getLogger } from './Logger';
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
|
||||||
export class WorkflowRunnerProcess {
|
export class WorkflowRunnerProcess {
|
||||||
data: IWorkflowExecutionDataProcessWithExecution | undefined;
|
data: IWorkflowExecutionDataProcessWithExecution | undefined;
|
||||||
|
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
|
|
||||||
startedAt = new Date();
|
startedAt = new Date();
|
||||||
|
|
||||||
workflow: Workflow | undefined;
|
workflow: Workflow | undefined;
|
||||||
|
|
||||||
workflowExecute: WorkflowExecute | undefined;
|
workflowExecute: WorkflowExecute | undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||||
executionIdCallback: (executionId: string) => void | undefined;
|
executionIdCallback: (executionId: string) => void | undefined;
|
||||||
|
|
||||||
childExecutions: {
|
childExecutions: {
|
||||||
[key: string]: IWorkflowExecuteProcess,
|
[key: string]: IWorkflowExecuteProcess;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
static async stopProcess() {
|
static async stopProcess() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Attempt a graceful shutdown, giving executions 30 seconds to finish
|
// Attempt a graceful shutdown, giving executions 30 seconds to finish
|
||||||
|
@ -59,17 +67,20 @@ export class WorkflowRunnerProcess {
|
||||||
}, 30000);
|
}, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async runWorkflow(inputData: IWorkflowExecutionDataProcessWithExecution): Promise<IRun> {
|
async runWorkflow(inputData: IWorkflowExecutionDataProcessWithExecution): Promise<IRun> {
|
||||||
process.on('SIGTERM', WorkflowRunnerProcess.stopProcess);
|
process.on('SIGTERM', WorkflowRunnerProcess.stopProcess);
|
||||||
process.on('SIGINT', 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);
|
LoggerProxy.init(logger);
|
||||||
|
|
||||||
this.data = inputData;
|
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 className: string;
|
||||||
let tempNode: INodeType;
|
let tempNode: INodeType;
|
||||||
|
@ -78,13 +89,16 @@ export class WorkflowRunnerProcess {
|
||||||
this.startedAt = new Date();
|
this.startedAt = new Date();
|
||||||
|
|
||||||
const nodeTypesData: INodeTypeData = {};
|
const nodeTypesData: INodeTypeData = {};
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const nodeTypeName of Object.keys(this.data.nodeTypeData)) {
|
for (const nodeTypeName of Object.keys(this.data.nodeTypeData)) {
|
||||||
className = this.data.nodeTypeData[nodeTypeName].className;
|
className = this.data.nodeTypeData[nodeTypeName].className;
|
||||||
|
|
||||||
filePath = this.data.nodeTypeData[nodeTypeName].sourcePath;
|
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);
|
const tempModule = require(filePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||||
tempNode = new tempModule[className]() as INodeType;
|
tempNode = new tempModule[className]() as INodeType;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error loading node "${nodeTypeName}" from: "${filePath}"`);
|
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
|
// We check if any node uses credentials. If it does, then
|
||||||
// init database.
|
// init database.
|
||||||
let shouldInitializaDb = false;
|
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) {
|
if (Object.keys(node.credentials === undefined ? {} : node.credentials).length > 0) {
|
||||||
shouldInitializaDb = true;
|
shouldInitializaDb = true;
|
||||||
}
|
}
|
||||||
|
@ -126,45 +141,77 @@ export class WorkflowRunnerProcess {
|
||||||
if (shouldInitializaDb) {
|
if (shouldInitializaDb) {
|
||||||
// initialize db as we need to load credentials
|
// initialize db as we need to load credentials
|
||||||
await Db.init();
|
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
|
// Workflow settings specifying it should save
|
||||||
await Db.init();
|
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
|
// Workflow settings not saying anything about saving but default settings says so
|
||||||
await Db.init();
|
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
|
// Workflow settings not saying anything about saving but default settings says so
|
||||||
await Db.init();
|
await Db.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start timeout for the execution
|
// Start timeout for the execution
|
||||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
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) {
|
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) {
|
if (workflowTimeout > 0) {
|
||||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
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 });
|
this.workflow = new Workflow({
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
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.hooks = this.getProcessForwardHooks();
|
||||||
additionalData.executionId = inputData.executionId;
|
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') {
|
if (workflowRunner.data!.executionMode !== 'manual') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
await sendToParentProcess('sendMessageToUI', { source, message });
|
await sendToParentProcess('sendMessageToUI', { source, message });
|
||||||
} catch (error) {
|
} 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;
|
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 workflowData = await WorkflowExecuteAdditionalData.getWorkflowData(workflowInfo);
|
||||||
const runData = await WorkflowExecuteAdditionalData.getRunData(workflowData, inputData);
|
const runData = await WorkflowExecuteAdditionalData.getRunData(workflowData, inputData);
|
||||||
await sendToParentProcess('startExecution', { runData });
|
await sendToParentProcess('startExecution', { runData });
|
||||||
|
@ -175,11 +222,18 @@ export class WorkflowRunnerProcess {
|
||||||
});
|
});
|
||||||
let result: IRun;
|
let result: IRun;
|
||||||
try {
|
try {
|
||||||
const executeWorkflowFunctionOutput = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData) as {workflowExecute: WorkflowExecute, workflow: Workflow} as IWorkflowExecuteProcess;
|
const executeWorkflowFunctionOutput = (await executeWorkflowFunction(
|
||||||
const workflowExecute = executeWorkflowFunctionOutput.workflowExecute;
|
workflowInfo,
|
||||||
|
additionalData,
|
||||||
|
inputData,
|
||||||
|
executionId,
|
||||||
|
workflowData,
|
||||||
|
runData,
|
||||||
|
)) as { workflowExecute: WorkflowExecute; workflow: Workflow } as IWorkflowExecuteProcess;
|
||||||
|
const { workflowExecute } = executeWorkflowFunctionOutput;
|
||||||
this.childExecutions[executionId] = executeWorkflowFunctionOutput;
|
this.childExecutions[executionId] = executeWorkflowFunctionOutput;
|
||||||
const workflow = executeWorkflowFunctionOutput.workflow;
|
const { workflow } = executeWorkflowFunctionOutput;
|
||||||
result = await workflowExecute.processRunExecutionData(workflow) as IRun;
|
result = await workflowExecute.processRunExecutionData(workflow);
|
||||||
await externalHooks.run('workflow.postExecute', [result, workflowData]);
|
await externalHooks.run('workflow.postExecute', [result, workflowData]);
|
||||||
await sendToParentProcess('finishExecution', { executionId, result });
|
await sendToParentProcess('finishExecution', { executionId, result });
|
||||||
delete this.childExecutions[executionId];
|
delete this.childExecutions[executionId];
|
||||||
|
@ -197,21 +251,34 @@ export class WorkflowRunnerProcess {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.data.executionData !== undefined) {
|
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);
|
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
|
// Execute all nodes
|
||||||
|
|
||||||
// Can execute without webhook so go on
|
// Can execute without webhook so go on
|
||||||
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode);
|
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode);
|
||||||
return this.workflowExecute.run(this.workflow, undefined, this.data.destinationNode);
|
return this.workflowExecute.run(this.workflow, undefined, this.data.destinationNode);
|
||||||
} else {
|
}
|
||||||
// Execute only the nodes between start and destination nodes
|
// Execute only the nodes between start and destination nodes
|
||||||
this.workflowExecute = new WorkflowExecute(additionalData, this.data.executionMode);
|
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
|
* Sends hook data to the parent process that it executes them
|
||||||
|
@ -220,18 +287,18 @@ export class WorkflowRunnerProcess {
|
||||||
* @param {any[]} parameters
|
* @param {any[]} parameters
|
||||||
* @memberof WorkflowRunnerProcess
|
* @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 {
|
try {
|
||||||
await sendToParentProcess('processHook', {
|
await sendToParentProcess('processHook', {
|
||||||
hook,
|
hook,
|
||||||
parameters,
|
parameters,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} 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
|
* Create a wrapper for hooks which simply forwards the data to
|
||||||
* the parent process where they then can be executed with access
|
* the parent process where they then can be executed with access
|
||||||
|
@ -264,6 +331,7 @@ export class WorkflowRunnerProcess {
|
||||||
};
|
};
|
||||||
|
|
||||||
const preExecuteFunctions = WorkflowExecuteAdditionalData.hookFunctionsPreExecute();
|
const preExecuteFunctions = WorkflowExecuteAdditionalData.hookFunctionsPreExecute();
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const key of Object.keys(preExecuteFunctions)) {
|
for (const key of Object.keys(preExecuteFunctions)) {
|
||||||
if (hookFunctions[key] === undefined) {
|
if (hookFunctions[key] === undefined) {
|
||||||
hookFunctions[key] = [];
|
hookFunctions[key] = [];
|
||||||
|
@ -271,13 +339,16 @@ export class WorkflowRunnerProcess {
|
||||||
hookFunctions[key]!.push.apply(hookFunctions[key], preExecuteFunctions[key]);
|
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
|
* Sends data to parent process
|
||||||
*
|
*
|
||||||
|
@ -285,25 +356,27 @@ export class WorkflowRunnerProcess {
|
||||||
* @param {*} data The data
|
* @param {*} data The data
|
||||||
* @returns {Promise<void>}
|
* @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) => {
|
return new Promise((resolve, reject) => {
|
||||||
process.send!({
|
process.send!(
|
||||||
|
{
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
}, (error: Error) => {
|
},
|
||||||
|
(error: Error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const workflowRunner = new WorkflowRunnerProcess();
|
const workflowRunner = new WorkflowRunnerProcess();
|
||||||
|
|
||||||
|
|
||||||
// Listen to messages from parent process which send the data of
|
// Listen to messages from parent process which send the data of
|
||||||
// the worflow to process
|
// the worflow to process
|
||||||
process.on('message', async (message: IProcessMessage) => {
|
process.on('message', async (message: IProcessMessage) => {
|
||||||
|
@ -324,25 +397,42 @@ process.on('message', async (message: IProcessMessage) => {
|
||||||
let runData: IRun;
|
let runData: IRun;
|
||||||
|
|
||||||
if (workflowRunner.workflowExecute !== undefined) {
|
if (workflowRunner.workflowExecute !== undefined) {
|
||||||
|
|
||||||
const executionIds = Object.keys(workflowRunner.childExecutions);
|
const executionIds = Object.keys(workflowRunner.childExecutions);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const executionId of executionIds) {
|
for (const executionId of executionIds) {
|
||||||
const childWorkflowExecute = workflowRunner.childExecutions[executionId];
|
const childWorkflowExecute = workflowRunner.childExecutions[executionId];
|
||||||
runData = childWorkflowExecute.workflowExecute.getFullRunData(workflowRunner.childExecutions[executionId].startedAt);
|
runData = childWorkflowExecute.workflowExecute.getFullRunData(
|
||||||
const timeOutError = message.type === 'timeout' ? new WorkflowOperationError('Workflow execution timed out!') : new WorkflowOperationError('Workflow-Execution has been canceled!');
|
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
|
// 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
|
// Workflow started already executing
|
||||||
runData = workflowRunner.workflowExecute.getFullRunData(workflowRunner.startedAt);
|
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
|
// 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 {
|
} else {
|
||||||
// Workflow did not get started yet
|
// Workflow did not get started yet
|
||||||
runData = {
|
runData = {
|
||||||
|
@ -352,11 +442,14 @@ process.on('message', async (message: IProcessMessage) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
finished: false,
|
finished: false,
|
||||||
mode: workflowRunner.data ? workflowRunner.data!.executionMode : 'own' as WorkflowExecuteMode,
|
mode: workflowRunner.data
|
||||||
|
? workflowRunner.data.executionMode
|
||||||
|
: ('own' as WorkflowExecuteMode),
|
||||||
startedAt: workflowRunner.startedAt,
|
startedAt: workflowRunner.startedAt,
|
||||||
stoppedAt: new Date(),
|
stoppedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
workflowRunner.sendHookToParentProcess('workflowExecuteAfter', [runData]);
|
workflowRunner.sendHookToParentProcess('workflowExecuteAfter', [runData]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,16 +460,16 @@ process.on('message', async (message: IProcessMessage) => {
|
||||||
// Stop process
|
// Stop process
|
||||||
process.exit();
|
process.exit();
|
||||||
} else if (message.type === 'executionId') {
|
} else if (message.type === 'executionId') {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
workflowRunner.executionIdCallback(message.data.executionId);
|
workflowRunner.executionIdCallback(message.data.executionId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
// Catch all uncaught errors and forward them to parent process
|
// Catch all uncaught errors and forward them to parent process
|
||||||
const executionError = {
|
const executionError = {
|
||||||
...error,
|
...error,
|
||||||
name: error!.name || 'Error',
|
name: error.name || 'Error',
|
||||||
message: error!.message,
|
message: error.message,
|
||||||
stack: error!.stack,
|
stack: error.stack,
|
||||||
} as ExecutionError;
|
} as ExecutionError;
|
||||||
|
|
||||||
await sendToParentProcess('processError', {
|
await sendToParentProcess('processError', {
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
ICredentialNodeAccess,
|
/* eslint-disable import/no-cycle */
|
||||||
} from 'n8n-workflow';
|
import { ICredentialNodeAccess } from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
|
||||||
getTimestampSyntax,
|
|
||||||
resolveDataType
|
|
||||||
} from '../utils';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ICredentialsDb,
|
|
||||||
} from '../..';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BeforeUpdate,
|
BeforeUpdate,
|
||||||
|
@ -20,10 +11,12 @@ import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
import { getTimestampSyntax, resolveDataType } from '../utils';
|
||||||
|
|
||||||
|
import { ICredentialsDb } from '../..';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class CredentialsEntity implements ICredentialsDb {
|
export class CredentialsEntity implements ICredentialsDb {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
@ -47,7 +40,11 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||||
@CreateDateColumn({ precision: 3, default: () => getTimestampSyntax() })
|
@CreateDateColumn({ precision: 3, default: () => getTimestampSyntax() })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
@UpdateDateColumn({ precision: 3, default: () => getTimestampSyntax(), onUpdate: getTimestampSyntax() })
|
@UpdateDateColumn({
|
||||||
|
precision: 3,
|
||||||
|
default: () => getTimestampSyntax(),
|
||||||
|
onUpdate: getTimestampSyntax(),
|
||||||
|
})
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
@BeforeUpdate()
|
@BeforeUpdate()
|
||||||
|
|
|
@ -1,27 +1,13 @@
|
||||||
import {
|
/* eslint-disable import/no-cycle */
|
||||||
WorkflowExecuteMode,
|
import { WorkflowExecuteMode } from 'n8n-workflow';
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
import { Column, ColumnOptions, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
IExecutionFlattedDb,
|
import { IExecutionFlattedDb, IWorkflowDb } from '../..';
|
||||||
IWorkflowDb,
|
|
||||||
} from '../../';
|
|
||||||
|
|
||||||
import {
|
import { resolveDataType } from '../utils';
|
||||||
resolveDataType
|
|
||||||
} from '../utils';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Column,
|
|
||||||
ColumnOptions,
|
|
||||||
Entity,
|
|
||||||
Index,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
} from 'typeorm';
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class ExecutionEntity implements IExecutionFlattedDb {
|
export class ExecutionEntity implements IExecutionFlattedDb {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
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 { IsDate, IsOptional, IsString, Length } from 'class-validator';
|
||||||
|
|
||||||
import { ITagDb } from '../../Interfaces';
|
import { ITagDb } from '../../Interfaces';
|
||||||
|
@ -7,7 +18,6 @@ import { getTimestampSyntax } from '../utils';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class TagEntity implements ITagDb {
|
export class TagEntity implements ITagDb {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
@ -22,12 +32,16 @@ export class TagEntity implements ITagDb {
|
||||||
@IsDate()
|
@IsDate()
|
||||||
createdAt: Date;
|
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
|
@IsOptional() // ignored by validation because set at DB level
|
||||||
@IsDate()
|
@IsDate()
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
@ManyToMany(() => WorkflowEntity, workflow => workflow.tags)
|
@ManyToMany(() => WorkflowEntity, (workflow) => workflow.tags)
|
||||||
workflows: WorkflowEntity[];
|
workflows: WorkflowEntity[];
|
||||||
|
|
||||||
@BeforeUpdate()
|
@BeforeUpdate()
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import {
|
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||||
Column,
|
|
||||||
Entity,
|
|
||||||
Index,
|
|
||||||
PrimaryColumn,
|
|
||||||
} from 'typeorm';
|
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line import/no-cycle
|
||||||
IWebhookDb,
|
import { IWebhookDb } from '../../Interfaces';
|
||||||
} from '../../Interfaces';
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Index(['webhookId', 'method', 'pathLength'])
|
@Index(['webhookId', 'method', 'pathLength'])
|
||||||
export class WebhookEntity implements IWebhookDb {
|
export class WebhookEntity implements IWebhookDb {
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
workflowId: number;
|
workflowId: number;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
Length,
|
/* eslint-disable import/no-cycle */
|
||||||
} from 'class-validator';
|
import { Length } from 'class-validator';
|
||||||
|
|
||||||
import {
|
import { IConnections, IDataObject, INode, IWorkflowSettings } from 'n8n-workflow';
|
||||||
IConnections,
|
|
||||||
IDataObject,
|
|
||||||
INode,
|
|
||||||
IWorkflowSettings,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BeforeUpdate,
|
BeforeUpdate,
|
||||||
|
@ -22,22 +17,14 @@ import {
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import {
|
import { IWorkflowDb } from '../..';
|
||||||
IWorkflowDb,
|
|
||||||
} from '../../';
|
|
||||||
|
|
||||||
import {
|
import { getTimestampSyntax, resolveDataType } from '../utils';
|
||||||
getTimestampSyntax,
|
|
||||||
resolveDataType
|
|
||||||
} from '../utils';
|
|
||||||
|
|
||||||
import {
|
import { TagEntity } from './TagEntity';
|
||||||
TagEntity,
|
|
||||||
} from './TagEntity';
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorkflowEntity implements IWorkflowDb {
|
export class WorkflowEntity implements IWorkflowDb {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
@ -58,7 +45,11 @@ export class WorkflowEntity implements IWorkflowDb {
|
||||||
@CreateDateColumn({ precision: 3, default: () => getTimestampSyntax() })
|
@CreateDateColumn({ precision: 3, default: () => getTimestampSyntax() })
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
@UpdateDateColumn({ precision: 3, default: () => getTimestampSyntax(), onUpdate: getTimestampSyntax() })
|
@UpdateDateColumn({
|
||||||
|
precision: 3,
|
||||||
|
default: () => getTimestampSyntax(),
|
||||||
|
onUpdate: getTimestampSyntax(),
|
||||||
|
})
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
|
@ -73,16 +64,16 @@ export class WorkflowEntity implements IWorkflowDb {
|
||||||
})
|
})
|
||||||
staticData?: IDataObject;
|
staticData?: IDataObject;
|
||||||
|
|
||||||
@ManyToMany(() => TagEntity, tag => tag.workflows)
|
@ManyToMany(() => TagEntity, (tag) => tag.workflows)
|
||||||
@JoinTable({
|
@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: {
|
joinColumn: {
|
||||||
name: "workflowId",
|
name: 'workflowId',
|
||||||
referencedColumnName: "id",
|
referencedColumnName: 'id',
|
||||||
},
|
},
|
||||||
inverseJoinColumn: {
|
inverseJoinColumn: {
|
||||||
name: "tagId",
|
name: 'tagId',
|
||||||
referencedColumnName: "id",
|
referencedColumnName: 'id',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
tags: TagEntity[];
|
tags: TagEntity[];
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
/* eslint-disable import/no-cycle */
|
||||||
import { CredentialsEntity } from './CredentialsEntity';
|
import { CredentialsEntity } from './CredentialsEntity';
|
||||||
import { ExecutionEntity } from './ExecutionEntity';
|
import { ExecutionEntity } from './ExecutionEntity';
|
||||||
import { WorkflowEntity } from './WorkflowEntity';
|
import { WorkflowEntity } from './WorkflowEntity';
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
/* eslint-disable import/no-cycle */
|
||||||
DatabaseType,
|
import { DatabaseType } from '../index';
|
||||||
} from '../index';
|
import { getConfigValueSync } from '../GenericHelpers';
|
||||||
import { getConfigValueSync } from '../../src/GenericHelpers';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the data type for the used database type
|
* Resolves the data type for the used database type
|
||||||
|
@ -10,6 +9,7 @@ import { getConfigValueSync } from '../../src/GenericHelpers';
|
||||||
* @param {string} dataType
|
* @param {string} dataType
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export function resolveDataType(dataType: string) {
|
export function resolveDataType(dataType: string) {
|
||||||
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
||||||
|
|
||||||
|
@ -27,16 +27,16 @@ export function resolveDataType(dataType: string) {
|
||||||
return typeMap[dbType][dataType] ?? dataType;
|
return typeMap[dbType][dataType] ?? dataType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export function getTimestampSyntax() {
|
export function getTimestampSyntax() {
|
||||||
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
const dbType = getConfigValueSync('database.type') as DatabaseType;
|
||||||
|
|
||||||
const map: { [key in DatabaseType]: string } = {
|
const map: { [key in DatabaseType]: string } = {
|
||||||
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
|
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
|
||||||
postgresdb: "CURRENT_TIMESTAMP(3)",
|
postgresdb: 'CURRENT_TIMESTAMP(3)',
|
||||||
mysqldb: "CURRENT_TIMESTAMP(3)",
|
mysqldb: 'CURRENT_TIMESTAMP(3)',
|
||||||
mariadb: "CURRENT_TIMESTAMP(3)",
|
mariadb: 'CURRENT_TIMESTAMP(3)',
|
||||||
};
|
};
|
||||||
|
|
||||||
return map[dbType];
|
return map[dbType];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable import/first */
|
||||||
|
/* eslint-disable import/no-cycle */
|
||||||
export * from './CredentialsHelper';
|
export * from './CredentialsHelper';
|
||||||
export * from './CredentialTypes';
|
export * from './CredentialTypes';
|
||||||
export * from './CredentialsOverwrites';
|
export * from './CredentialsOverwrites';
|
||||||
|
@ -22,6 +24,7 @@ import * as WebhookHelpers from './WebhookHelpers';
|
||||||
import * as WebhookServer from './WebhookServer';
|
import * as WebhookServer from './WebhookServer';
|
||||||
import * as WorkflowExecuteAdditionalData from './WorkflowExecuteAdditionalData';
|
import * as WorkflowExecuteAdditionalData from './WorkflowExecuteAdditionalData';
|
||||||
import * as WorkflowHelpers from './WorkflowHelpers';
|
import * as WorkflowHelpers from './WorkflowHelpers';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
ActiveWorkflowRunner,
|
ActiveWorkflowRunner,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
describe('Placeholder', () => {
|
describe('Placeholder', () => {
|
||||||
|
|
||||||
test('example', () => {
|
test('example', () => {
|
||||||
expect(1 + 1).toEqual(2);
|
expect(1 + 1).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"dev": "npm run watch",
|
"dev": "npm run watch",
|
||||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/core/**/**.ts --write",
|
||||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
"lint": "cd ../.. && node_modules/eslint/bin/eslint.js packages/core",
|
||||||
|
"lintfix": "cd ../.. && node_modules/eslint/bin/eslint.js packages/core --fix",
|
||||||
"watch": "tsc --watch",
|
"watch": "tsc --watch",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
|
@ -38,7 +39,7 @@
|
||||||
"source-map-support": "^0.5.9",
|
"source-map-support": "^0.5.9",
|
||||||
"ts-jest": "^26.3.0",
|
"ts-jest": "^26.3.0",
|
||||||
"tslint": "^6.1.2",
|
"tslint": "^6.1.2",
|
||||||
"typescript": "~3.9.7"
|
"typescript": "~4.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"client-oauth2": "^4.2.5",
|
"client-oauth2": "^4.2.5",
|
||||||
|
|
|
@ -6,10 +6,8 @@ import {
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line import/no-cycle
|
||||||
NodeExecuteFunctions,
|
import { NodeExecuteFunctions } from '.';
|
||||||
} from './';
|
|
||||||
|
|
||||||
|
|
||||||
export class ActiveWebhooks {
|
export class ActiveWebhooks {
|
||||||
private workflowWebhooks: {
|
private workflowWebhooks: {
|
||||||
|
@ -22,7 +20,6 @@ export class ActiveWebhooks {
|
||||||
|
|
||||||
testWebhooks = false;
|
testWebhooks = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new webhook
|
* Adds a new webhook
|
||||||
*
|
*
|
||||||
|
@ -31,19 +28,31 @@ export class ActiveWebhooks {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWebhooks
|
* @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) {
|
if (workflow.id === undefined) {
|
||||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
||||||
}
|
}
|
||||||
if (webhookData.path.endsWith('/')) {
|
if (webhookData.path.endsWith('/')) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
webhookData.path = webhookData.path.slice(0, -1);
|
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) {
|
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) {
|
if (this.workflowWebhooks[webhookData.workflowId] === undefined) {
|
||||||
|
@ -58,18 +67,33 @@ export class ActiveWebhooks {
|
||||||
this.webhookUrls[webhookKey].push(webhookData);
|
this.webhookUrls[webhookKey].push(webhookData);
|
||||||
|
|
||||||
try {
|
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 (webhookExists !== true) {
|
||||||
// If webhook does not exist yet create it
|
// 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) {
|
} catch (error) {
|
||||||
// If there was a problem unregister the webhook again
|
// If there was a problem unregister the webhook again
|
||||||
if (this.webhookUrls[webhookKey].length <= 1) {
|
if (this.webhookUrls[webhookKey].length <= 1) {
|
||||||
delete this.webhookUrls[webhookKey];
|
delete this.webhookUrls[webhookKey];
|
||||||
} else {
|
} 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;
|
throw error;
|
||||||
|
@ -77,7 +101,6 @@ export class ActiveWebhooks {
|
||||||
this.workflowWebhooks[webhookData.workflowId].push(webhookData);
|
this.workflowWebhooks[webhookData.workflowId].push(webhookData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns webhookData if a webhook with matches is currently registered
|
* Returns webhookData if a webhook with matches is currently registered
|
||||||
*
|
*
|
||||||
|
@ -98,9 +121,9 @@ export class ActiveWebhooks {
|
||||||
const pathElementsSet = new Set(path.split('/'));
|
const pathElementsSet = new Set(path.split('/'));
|
||||||
// check if static elements match in path
|
// check if static elements match in path
|
||||||
// if more results have been returned choose the one with the most static-route matches
|
// if more results have been returned choose the one with the most static-route matches
|
||||||
this.webhookUrls[webhookKey].forEach(dynamicWebhook => {
|
this.webhookUrls[webhookKey].forEach((dynamicWebhook) => {
|
||||||
const staticElements = dynamicWebhook.path.split('/').filter(ele => !ele.startsWith(':'));
|
const staticElements = dynamicWebhook.path.split('/').filter((ele) => !ele.startsWith(':'));
|
||||||
const allStaticExist = staticElements.every(staticEle => pathElementsSet.has(staticEle));
|
const allStaticExist = staticElements.every((staticEle) => pathElementsSet.has(staticEle));
|
||||||
|
|
||||||
if (allStaticExist && staticElements.length > maxMatches) {
|
if (allStaticExist && staticElements.length > maxMatches) {
|
||||||
maxMatches = staticElements.length;
|
maxMatches = staticElements.length;
|
||||||
|
@ -120,11 +143,12 @@ export class ActiveWebhooks {
|
||||||
* @param path
|
* @param path
|
||||||
*/
|
*/
|
||||||
getWebhookMethods(path: string): string[] {
|
getWebhookMethods(path: string): string[] {
|
||||||
const methods : string[] = [];
|
const methods: string[] = [];
|
||||||
|
|
||||||
Object.keys(this.webhookUrls)
|
Object.keys(this.webhookUrls)
|
||||||
.filter(key => key.includes(path))
|
.filter((key) => key.includes(path))
|
||||||
.map(key => {
|
// eslint-disable-next-line array-callback-return
|
||||||
|
.map((key) => {
|
||||||
methods.push(key.split('|')[0]);
|
methods.push(key.split('|')[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -141,7 +165,6 @@ export class ActiveWebhooks {
|
||||||
return Object.keys(this.workflowWebhooks);
|
return Object.keys(this.workflowWebhooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns key to uniquely identify a webhook
|
* Returns key to uniquely identify a webhook
|
||||||
*
|
*
|
||||||
|
@ -155,6 +178,7 @@ export class ActiveWebhooks {
|
||||||
if (webhookId) {
|
if (webhookId) {
|
||||||
if (path.startsWith(webhookId)) {
|
if (path.startsWith(webhookId)) {
|
||||||
const cutFromIndex = path.indexOf('/') + 1;
|
const cutFromIndex = path.indexOf('/') + 1;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
path = path.slice(cutFromIndex);
|
path = path.slice(cutFromIndex);
|
||||||
}
|
}
|
||||||
return `${httpMethod}|${webhookId}|${path.split('/').length}`;
|
return `${httpMethod}|${webhookId}|${path.split('/').length}`;
|
||||||
|
@ -162,7 +186,6 @@ export class ActiveWebhooks {
|
||||||
return `${httpMethod}|${path}`;
|
return `${httpMethod}|${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all webhooks of a workflow
|
* Removes all webhooks of a workflow
|
||||||
*
|
*
|
||||||
|
@ -171,6 +194,7 @@ export class ActiveWebhooks {
|
||||||
* @memberof ActiveWebhooks
|
* @memberof ActiveWebhooks
|
||||||
*/
|
*/
|
||||||
async removeWorkflow(workflow: Workflow): Promise<boolean> {
|
async removeWorkflow(workflow: Workflow): Promise<boolean> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const workflowId = workflow.id!.toString();
|
const workflowId = workflow.id!.toString();
|
||||||
|
|
||||||
if (this.workflowWebhooks[workflowId] === undefined) {
|
if (this.workflowWebhooks[workflowId] === undefined) {
|
||||||
|
@ -183,10 +207,21 @@ export class ActiveWebhooks {
|
||||||
const mode = 'internal';
|
const mode = 'internal';
|
||||||
|
|
||||||
// Go through all the registered webhooks of the workflow and remove them
|
// Go through all the registered webhooks of the workflow and remove them
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const webhookData of webhooks) {
|
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
|
// Remove also the workflow-webhook entry
|
||||||
|
@ -195,18 +230,16 @@ export class ActiveWebhooks {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all the webhooks of the given workflows
|
* Removes all the webhooks of the given workflows
|
||||||
*/
|
*/
|
||||||
async removeAll(workflows: Workflow[]): Promise<void> {
|
async removeAll(workflows: Workflow[]): Promise<void> {
|
||||||
const removePromises = [];
|
const removePromises = [];
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const workflow of workflows) {
|
for (const workflow of workflows) {
|
||||||
removePromises.push(this.removeWorkflow(workflow));
|
removePromises.push(this.removeWorkflow(workflow));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(removePromises);
|
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 { CronJob } from 'cron';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -13,18 +16,14 @@ import {
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line import/no-cycle
|
||||||
ITriggerTime,
|
import { ITriggerTime, IWorkflowData } from '.';
|
||||||
IWorkflowData,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
|
|
||||||
export class ActiveWorkflows {
|
export class ActiveWorkflows {
|
||||||
private workflowData: {
|
private workflowData: {
|
||||||
[key: string]: IWorkflowData;
|
[key: string]: IWorkflowData;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the workflow is active
|
* Returns if the workflow is active
|
||||||
*
|
*
|
||||||
|
@ -33,10 +32,10 @@ export class ActiveWorkflows {
|
||||||
* @memberof ActiveWorkflows
|
* @memberof ActiveWorkflows
|
||||||
*/
|
*/
|
||||||
isActive(id: string): boolean {
|
isActive(id: string): boolean {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
return this.workflowData.hasOwnProperty(id);
|
return this.workflowData.hasOwnProperty(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ids of the currently active workflows
|
* Returns the ids of the currently active workflows
|
||||||
*
|
*
|
||||||
|
@ -47,7 +46,6 @@ export class ActiveWorkflows {
|
||||||
return Object.keys(this.workflowData);
|
return Object.keys(this.workflowData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Workflow data for the workflow with
|
* Returns the Workflow data for the workflow with
|
||||||
* the given id if it is currently active
|
* the given id if it is currently active
|
||||||
|
@ -60,7 +58,6 @@ export class ActiveWorkflows {
|
||||||
return this.workflowData[id];
|
return this.workflowData[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a workflow active
|
* Makes a workflow active
|
||||||
*
|
*
|
||||||
|
@ -70,16 +67,31 @@ export class ActiveWorkflows {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWorkflows
|
* @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] = {};
|
this.workflowData[id] = {};
|
||||||
const triggerNodes = workflow.getTriggerNodes();
|
const triggerNodes = workflow.getTriggerNodes();
|
||||||
|
|
||||||
let triggerResponse: ITriggerResponse | undefined;
|
let triggerResponse: ITriggerResponse | undefined;
|
||||||
this.workflowData[id].triggerResponses = [];
|
this.workflowData[id].triggerResponses = [];
|
||||||
for (const triggerNode of triggerNodes) {
|
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 (triggerResponse !== undefined) {
|
||||||
// If a response was given save it
|
// If a response was given save it
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,12 +100,21 @@ export class ActiveWorkflows {
|
||||||
if (pollNodes.length) {
|
if (pollNodes.length) {
|
||||||
this.workflowData[id].pollResponses = [];
|
this.workflowData[id].pollResponses = [];
|
||||||
for (const pollNode of pollNodes) {
|
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
|
* Activates polling for the given node
|
||||||
*
|
*
|
||||||
|
@ -104,7 +125,14 @@ export class ActiveWorkflows {
|
||||||
* @returns {Promise<IPollResponse>}
|
* @returns {Promise<IPollResponse>}
|
||||||
* @memberof ActiveWorkflows
|
* @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 pollFunctions = getPollFunctions(workflow, node, additionalData, mode, activation);
|
||||||
|
|
||||||
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
|
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
|
// The trigger function to execute when the cron-time got reached
|
||||||
const executeTrigger = async () => {
|
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);
|
const pollResponse = await workflow.runPoll(node, pollFunctions);
|
||||||
|
|
||||||
if (pollResponse !== null) {
|
if (pollResponse !== null) {
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
pollFunctions.__emit(pollResponse);
|
pollFunctions.__emit(pollResponse);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -180,6 +213,7 @@ export class ActiveWorkflows {
|
||||||
|
|
||||||
// Start the cron-jobs
|
// Start the cron-jobs
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
for (const cronTime of cronTimes) {
|
for (const cronTime of cronTimes) {
|
||||||
const cronTimeParts = cronTime.split(' ');
|
const cronTimeParts = cronTime.split(' ');
|
||||||
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
|
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
|
||||||
|
@ -201,7 +235,6 @@ export class ActiveWorkflows {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a workflow inactive
|
* Makes a workflow inactive
|
||||||
*
|
*
|
||||||
|
@ -212,7 +245,9 @@ export class ActiveWorkflows {
|
||||||
async remove(id: string): Promise<void> {
|
async remove(id: string): Promise<void> {
|
||||||
if (!this.isActive(id)) {
|
if (!this.isActive(id)) {
|
||||||
// Workflow is currently not registered
|
// 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];
|
const workflowData = this.workflowData[id];
|
||||||
|
@ -235,5 +270,4 @@ export class ActiveWorkflows {
|
||||||
|
|
||||||
delete this.workflowData[id];
|
delete this.workflowData[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,13 @@ import {
|
||||||
|
|
||||||
import { AES, enc } from 'crypto-js';
|
import { AES, enc } from 'crypto-js';
|
||||||
|
|
||||||
|
|
||||||
export class Credentials extends ICredentials {
|
export class Credentials extends ICredentials {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the given nodeType has access to data
|
* Returns if the given nodeType has access to data
|
||||||
*/
|
*/
|
||||||
hasNodeAccess(nodeType: string): boolean {
|
hasNodeAccess(nodeType: string): boolean {
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const accessData of this.nodesAccess) {
|
for (const accessData of this.nodesAccess) {
|
||||||
|
|
||||||
if (accessData.nodeType === nodeType) {
|
if (accessData.nodeType === nodeType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +22,6 @@ export class Credentials extends ICredentials {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets new credential object
|
* Sets new credential object
|
||||||
*/
|
*/
|
||||||
|
@ -33,7 +29,6 @@ export class Credentials extends ICredentials {
|
||||||
this.data = AES.encrypt(JSON.stringify(data), encryptionKey).toString();
|
this.data = AES.encrypt(JSON.stringify(data), encryptionKey).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets new credentials for given key
|
* Sets new credentials for given key
|
||||||
*/
|
*/
|
||||||
|
@ -50,13 +45,14 @@ export class Credentials extends ICredentials {
|
||||||
return this.setData(fullData, encryptionKey);
|
return this.setData(fullData, encryptionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the decrypted credential object
|
* Returns the decrypted credential object
|
||||||
*/
|
*/
|
||||||
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
|
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
|
||||||
if (nodeType && !this.hasNodeAccess(nodeType)) {
|
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) {
|
if (this.data === undefined) {
|
||||||
|
@ -66,13 +62,15 @@ export class Credentials extends ICredentials {
|
||||||
const decryptedData = AES.decrypt(this.data, encryptionKey);
|
const decryptedData = AES.decrypt(this.data, encryptionKey);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return JSON.parse(decryptedData.toString(enc.Utf8));
|
return JSON.parse(decryptedData.toString(enc.Utf8));
|
||||||
} catch (e) {
|
} 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
|
* Returns the decrypted credentials for given key
|
||||||
*/
|
*/
|
||||||
|
@ -83,6 +81,7 @@ export class Credentials extends ICredentials {
|
||||||
throw new Error(`No data was set.`);
|
throw new Error(`No data was set.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
if (!fullData.hasOwnProperty(key)) {
|
if (!fullData.hasOwnProperty(key)) {
|
||||||
throw new Error(`No data for key "${key}" exists.`);
|
throw new Error(`No data for key "${key}" exists.`);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +89,6 @@ export class Credentials extends ICredentials {
|
||||||
return fullData[key];
|
return fullData[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the encrypted credentials to be saved
|
* Returns the encrypted credentials to be saved
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,10 +5,10 @@ export interface IDeferredPromise<T> {
|
||||||
resolve: (result: T) => void;
|
resolve: (result: T) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDeferredPromise<T>(): Promise<IDeferredPromise<T>> {
|
export async function createDeferredPromise<T>(): Promise<IDeferredPromise<T>> {
|
||||||
return new Promise<IDeferredPromise<T>>(resolveCreate => {
|
return new Promise<IDeferredPromise<T>>((resolveCreate) => {
|
||||||
const promise = new Promise<T>((resolve, reject) => {
|
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 {
|
import {
|
||||||
IAllExecuteFunctions,
|
IAllExecuteFunctions,
|
||||||
IBinaryData,
|
IBinaryData,
|
||||||
|
@ -16,70 +17,116 @@ import {
|
||||||
ITriggerResponse,
|
ITriggerResponse,
|
||||||
IWebhookFunctions as IWebhookFunctionsBase,
|
IWebhookFunctions as IWebhookFunctionsBase,
|
||||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
||||||
import * as requestPromise from 'request-promise-native';
|
import * as requestPromise from 'request-promise-native';
|
||||||
|
|
||||||
interface Constructable<T> {
|
interface Constructable<T> {
|
||||||
new(): T;
|
new (): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProcessMessage {
|
export interface IProcessMessage {
|
||||||
data?: any; // tslint:disable-line:no-any
|
data?: any;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
||||||
helpers: {
|
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>;
|
getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>;
|
||||||
request: requestPromise.RequestPromiseAPI;
|
request: requestPromise.RequestPromiseAPI;
|
||||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>; // tslint:disable-line:no-any
|
requestOAuth2(
|
||||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>; // tslint:disable-line:no-any
|
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[];
|
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
prepareBinaryData(
|
||||||
request: requestPromise.RequestPromiseAPI,
|
binaryData: Buffer,
|
||||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
filePath?: string,
|
||||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
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 {
|
export interface IPollFunctions extends IPollFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
prepareBinaryData(
|
||||||
request: requestPromise.RequestPromiseAPI,
|
binaryData: Buffer,
|
||||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
filePath?: string,
|
||||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
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[];
|
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IResponseError extends Error {
|
export interface IResponseError extends Error {
|
||||||
statusCode?: number;
|
statusCode?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
prepareBinaryData(
|
||||||
request: requestPromise.RequestPromiseAPI,
|
binaryData: Buffer,
|
||||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
filePath?: string,
|
||||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
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[];
|
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface ITriggerTime {
|
export interface ITriggerTime {
|
||||||
mode: string;
|
mode: string;
|
||||||
hour: number;
|
hour: number;
|
||||||
|
@ -89,7 +136,6 @@ export interface ITriggerTime {
|
||||||
[key: string]: string | number;
|
[key: string]: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IUserSettings {
|
export interface IUserSettings {
|
||||||
encryptionKey?: string;
|
encryptionKey?: string;
|
||||||
tunnelSubdomain?: string;
|
tunnelSubdomain?: string;
|
||||||
|
@ -97,28 +143,57 @@ export interface IUserSettings {
|
||||||
|
|
||||||
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
request?: requestPromise.RequestPromiseAPI,
|
request?: requestPromise.RequestPromiseAPI;
|
||||||
requestOAuth2?: (this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options) => Promise<any>, // tslint:disable-line:no-any
|
requestOAuth2?: (
|
||||||
requestOAuth1?(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
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 {
|
export interface IHookFunctions extends IHookFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
request: requestPromise.RequestPromiseAPI,
|
request: requestPromise.RequestPromiseAPI;
|
||||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
requestOAuth2(
|
||||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
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 {
|
export interface IWebhookFunctions extends IWebhookFunctionsBase {
|
||||||
helpers: {
|
helpers: {
|
||||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
prepareBinaryData(
|
||||||
request: requestPromise.RequestPromiseAPI,
|
binaryData: Buffer,
|
||||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
filePath?: string,
|
||||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
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[];
|
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -129,19 +204,16 @@ export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||||
saveManualRuns?: boolean;
|
saveManualRuns?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// New node definition in file
|
// New node definition in file
|
||||||
export interface INodeDefinitionFile {
|
export interface INodeDefinitionFile {
|
||||||
[key: string]: Constructable<INodeType | ICredentialType>;
|
[key: string]: Constructable<INodeType | ICredentialType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Is identical to TaskDataConnections but does not allow null value to be used as input for nodes
|
// Is identical to TaskDataConnections but does not allow null value to be used as input for nodes
|
||||||
export interface INodeInputDataConnections {
|
export interface INodeInputDataConnections {
|
||||||
[key: string]: INodeExecutionData[][];
|
[key: string]: INodeExecutionData[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IWorkflowData {
|
export interface IWorkflowData {
|
||||||
pollResponses?: IPollResponse[];
|
pollResponses?: IPollResponse[];
|
||||||
triggerResponses?: ITriggerResponse[];
|
triggerResponses?: ITriggerResponse[];
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import {
|
import {
|
||||||
INode,
|
INode,
|
||||||
INodeCredentials,
|
INodeCredentials,
|
||||||
|
@ -8,21 +9,24 @@ import {
|
||||||
Workflow,
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line import/no-cycle
|
||||||
NodeExecuteFunctions,
|
import { NodeExecuteFunctions } from '.';
|
||||||
} from './';
|
|
||||||
|
|
||||||
|
|
||||||
const TEMP_NODE_NAME = 'Temp-Node';
|
const TEMP_NODE_NAME = 'Temp-Node';
|
||||||
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
|
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
|
||||||
|
|
||||||
|
|
||||||
export class LoadNodeParameterOptions {
|
export class LoadNodeParameterOptions {
|
||||||
path: string;
|
path: string;
|
||||||
|
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
|
|
||||||
|
constructor(
|
||||||
constructor(nodeTypeName: string, nodeTypes: INodeTypes, path: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) {
|
nodeTypeName: string,
|
||||||
|
nodeTypes: INodeTypes,
|
||||||
|
path: string,
|
||||||
|
currentNodeParameters: INodeParameters,
|
||||||
|
credentials?: INodeCredentials,
|
||||||
|
) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
const nodeType = nodeTypes.getByName(nodeTypeName);
|
const nodeType = nodeTypes.getByName(nodeTypeName);
|
||||||
|
|
||||||
|
@ -35,10 +39,7 @@ export class LoadNodeParameterOptions {
|
||||||
name: TEMP_NODE_NAME,
|
name: TEMP_NODE_NAME,
|
||||||
type: nodeTypeName,
|
type: nodeTypeName,
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
position: [
|
position: [0, 0],
|
||||||
0,
|
|
||||||
0,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
|
@ -46,22 +47,25 @@ export class LoadNodeParameterOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowData = {
|
const workflowData = {
|
||||||
nodes: [
|
nodes: [nodeData],
|
||||||
nodeData,
|
|
||||||
],
|
|
||||||
connections: {},
|
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 data of a fake workflow
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
* @memberof LoadNodeParameterOptions
|
* @memberof LoadNodeParameterOptions
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
getWorkflowData() {
|
getWorkflowData() {
|
||||||
return {
|
return {
|
||||||
name: TEMP_WORKFLOW_NAME,
|
name: TEMP_WORKFLOW_NAME,
|
||||||
|
@ -73,7 +77,6 @@ export class LoadNodeParameterOptions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the available options
|
* Returns the available options
|
||||||
*
|
*
|
||||||
|
@ -82,18 +85,31 @@ export class LoadNodeParameterOptions {
|
||||||
* @returns {Promise<INodePropertyOptions[]>}
|
* @returns {Promise<INodePropertyOptions[]>}
|
||||||
* @memberof LoadNodeParameterOptions
|
* @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 node = this.workflow.getNode(TEMP_NODE_NAME);
|
||||||
|
|
||||||
const nodeType = this.workflow.nodeTypes.getByName(node!.type);
|
const nodeType = this.workflow.nodeTypes.getByName(node!.type);
|
||||||
|
|
||||||
if (nodeType!.methods === undefined || nodeType!.methods.loadOptions === undefined || nodeType!.methods.loadOptions[methodName] === undefined) {
|
if (
|
||||||
throw new Error(`The node-type "${node!.type}" does not have the method "${methodName}" defined!`);
|
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);
|
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 {
|
import {
|
||||||
ENCRYPTION_KEY_ENV_OVERWRITE,
|
ENCRYPTION_KEY_ENV_OVERWRITE,
|
||||||
EXTENSIONS_SUBDIRECTORY,
|
EXTENSIONS_SUBDIRECTORY,
|
||||||
|
@ -7,20 +15,15 @@ import {
|
||||||
USER_SETTINGS_SUBFOLDER,
|
USER_SETTINGS_SUBFOLDER,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { randomBytes } from 'crypto';
|
|
||||||
const { promisify } = require('util');
|
const { promisify } = require('util');
|
||||||
|
|
||||||
const fsAccess = promisify(fs.access);
|
const fsAccess = promisify(fs.access);
|
||||||
const fsReadFile = promisify(fs.readFile);
|
const fsReadFile = promisify(fs.readFile);
|
||||||
const fsMkdir = promisify(fs.mkdir);
|
const fsMkdir = promisify(fs.mkdir);
|
||||||
const fsWriteFile = promisify(fs.writeFile);
|
const fsWriteFile = promisify(fs.writeFile);
|
||||||
|
|
||||||
|
let settingsCache: IUserSettings | undefined;
|
||||||
|
|
||||||
let settingsCache: IUserSettings | undefined = undefined;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the user settings if they do not exist yet
|
* 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');
|
userSettings.encryptionKey = randomBytes(24).toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(`UserSettings were generated and saved to: ${settingsPath}`);
|
console.log(`UserSettings were generated and saved to: ${settingsPath}`);
|
||||||
|
|
||||||
return writeUserSettings(userSettings, settingsPath);
|
return writeUserSettings(userSettings, settingsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the encryption key which is used to encrypt
|
* Returns the encryption key which is used to encrypt
|
||||||
* the credentials.
|
* the credentials.
|
||||||
|
@ -62,6 +65,7 @@ export async function prepareUserSettings(): Promise<IUserSettings> {
|
||||||
* @export
|
* @export
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export async function getEncryptionKey() {
|
export async function getEncryptionKey() {
|
||||||
if (process.env[ENCRYPTION_KEY_ENV_OVERWRITE] !== undefined) {
|
if (process.env[ENCRYPTION_KEY_ENV_OVERWRITE] !== undefined) {
|
||||||
return process.env[ENCRYPTION_KEY_ENV_OVERWRITE];
|
return process.env[ENCRYPTION_KEY_ENV_OVERWRITE];
|
||||||
|
@ -80,7 +84,6 @@ export async function getEncryptionKey() {
|
||||||
return userSettings.encryptionKey;
|
return userSettings.encryptionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds/Overwrite the given settings in the currently
|
* Adds/Overwrite the given settings in the currently
|
||||||
* saved user settings
|
* saved user settings
|
||||||
|
@ -90,7 +93,10 @@ export async function getEncryptionKey() {
|
||||||
* @param {string} [settingsPath] Optional settings file path
|
* @param {string} [settingsPath] Optional settings file path
|
||||||
* @returns {Promise<IUserSettings>}
|
* @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) {
|
if (settingsPath === undefined) {
|
||||||
settingsPath = getUserSettingsPath();
|
settingsPath = getUserSettingsPath();
|
||||||
}
|
}
|
||||||
|
@ -107,7 +113,6 @@ export async function addToUserSettings(addSettings: IUserSettings, settingsPath
|
||||||
return writeUserSettings(userSettings, settingsPath);
|
return writeUserSettings(userSettings, settingsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a user settings file
|
* Writes a user settings file
|
||||||
*
|
*
|
||||||
|
@ -116,7 +121,10 @@ export async function addToUserSettings(addSettings: IUserSettings, settingsPath
|
||||||
* @param {string} [settingsPath] Optional settings file path
|
* @param {string} [settingsPath] Optional settings file path
|
||||||
* @returns {Promise<IUserSettings>}
|
* @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) {
|
if (settingsPath === undefined) {
|
||||||
settingsPath = getUserSettingsPath();
|
settingsPath = getUserSettingsPath();
|
||||||
}
|
}
|
||||||
|
@ -139,14 +147,16 @@ export async function writeUserSettings(userSettings: IUserSettings, settingsPat
|
||||||
return userSettings;
|
return userSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the content of the user settings
|
* Returns the content of the user settings
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
* @returns {UserSettings}
|
* @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) {
|
if (settingsCache !== undefined && ignoreCache !== true) {
|
||||||
return settingsCache;
|
return settingsCache;
|
||||||
}
|
}
|
||||||
|
@ -167,13 +177,14 @@ export async function getUserSettings(settingsPath?: string, ignoreCache?: boole
|
||||||
try {
|
try {
|
||||||
settingsCache = JSON.parse(settingsFile);
|
settingsCache = JSON.parse(settingsFile);
|
||||||
} catch (error) {
|
} 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;
|
return settingsCache as IUserSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the path to the user settings
|
* Returns the path to the user settings
|
||||||
*
|
*
|
||||||
|
@ -186,8 +197,6 @@ export function getUserSettingsPath(): string {
|
||||||
return path.join(n8nFolder, USER_SETTINGS_FILE_NAME);
|
return path.join(n8nFolder, USER_SETTINGS_FILE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retruns the path to the n8n folder in which all n8n
|
* Retruns the path to the n8n folder in which all n8n
|
||||||
* related data gets saved
|
* related data gets saved
|
||||||
|
@ -206,7 +215,6 @@ export function getUserN8nFolderPath(): string {
|
||||||
return path.join(userFolder, USER_SETTINGS_SUBFOLDER);
|
return path.join(userFolder, USER_SETTINGS_SUBFOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the path to the n8n user folder with the custom
|
* Returns the path to the n8n user folder with the custom
|
||||||
* extensions like nodes and credentials
|
* extensions like nodes and credentials
|
||||||
|
@ -218,7 +226,6 @@ export function getUserN8nFolderCustomExtensionPath(): string {
|
||||||
return path.join(getUserN8nFolderPath(), EXTENSIONS_SUBDIRECTORY);
|
return path.join(getUserN8nFolderPath(), EXTENSIONS_SUBDIRECTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the home folder path of the user if
|
* Returns the home folder path of the user if
|
||||||
* none can be found it falls back to the current
|
* 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 * as PCancelable from 'p-cancelable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -20,23 +31,27 @@ import {
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
WorkflowOperationError,
|
WorkflowOperationError,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
NodeExecuteFunctions,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { NodeExecuteFunctions } from '.';
|
||||||
|
|
||||||
export class WorkflowExecute {
|
export class WorkflowExecute {
|
||||||
runExecutionData: IRunExecutionData;
|
runExecutionData: IRunExecutionData;
|
||||||
|
|
||||||
private additionalData: IWorkflowExecuteAdditionalData;
|
private additionalData: IWorkflowExecuteAdditionalData;
|
||||||
|
|
||||||
private mode: WorkflowExecuteMode;
|
private mode: WorkflowExecuteMode;
|
||||||
|
|
||||||
constructor(additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData) {
|
constructor(
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
mode: WorkflowExecuteMode,
|
||||||
|
runExecutionData?: IRunExecutionData,
|
||||||
|
) {
|
||||||
this.additionalData = additionalData;
|
this.additionalData = additionalData;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.runExecutionData = runExecutionData || {
|
this.runExecutionData = runExecutionData || {
|
||||||
startData: {
|
startData: {},
|
||||||
},
|
|
||||||
resultData: {
|
resultData: {
|
||||||
runData: {},
|
runData: {},
|
||||||
},
|
},
|
||||||
|
@ -48,8 +63,6 @@ export class WorkflowExecute {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the given workflow.
|
* Executes the given workflow.
|
||||||
*
|
*
|
||||||
|
@ -59,7 +72,8 @@ export class WorkflowExecute {
|
||||||
* @returns {(Promise<string>)}
|
* @returns {(Promise<string>)}
|
||||||
* @memberof WorkflowExecute
|
* @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
|
// Get the nodes to start workflow execution from
|
||||||
startNode = startNode || workflow.getStartNode(destinationNode);
|
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
|
// 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) {
|
if (destinationNode) {
|
||||||
runNodeFilter = workflow.getParentNodes(destinationNode);
|
runNodeFilter = workflow.getParentNodes(destinationNode);
|
||||||
runNodeFilter.push(destinationNode);
|
runNodeFilter.push(destinationNode);
|
||||||
|
@ -108,8 +122,6 @@ export class WorkflowExecute {
|
||||||
return this.processRunExecutionData(workflow);
|
return this.processRunExecutionData(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the given workflow but only
|
* Executes the given workflow but only
|
||||||
*
|
*
|
||||||
|
@ -121,7 +133,13 @@ export class WorkflowExecute {
|
||||||
* @memberof WorkflowExecute
|
* @memberof WorkflowExecute
|
||||||
*/
|
*/
|
||||||
// @ts-ignore
|
// @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 incomingNodeConnections: INodeConnections | undefined;
|
||||||
let connection: IConnection;
|
let connection: IConnection;
|
||||||
|
|
||||||
|
@ -149,7 +167,8 @@ export class WorkflowExecute {
|
||||||
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
|
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
|
||||||
connection = connections[inputIndex];
|
connection = connections[inputIndex];
|
||||||
incomingData.push(
|
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] = [];
|
waitingExecution[destinationNode][runIndex][connection.type] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runData[connection.node] !== undefined) {
|
||||||
if (runData[connection.node!] !== undefined) {
|
|
||||||
// Input data exists so add as waiting
|
// Input data exists so add as waiting
|
||||||
// incomingDataDestination.push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
|
// 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 {
|
} else {
|
||||||
waitingExecution[destinationNode][runIndex][connection.type].push(null);
|
waitingExecution[destinationNode][runIndex][connection.type].push(null);
|
||||||
}
|
}
|
||||||
|
@ -196,7 +216,8 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only run the parent nodes and no others
|
// 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 = workflow.getParentNodes(destinationNode);
|
||||||
runNodeFilter.push(destinationNode);
|
runNodeFilter.push(destinationNode);
|
||||||
|
|
||||||
|
@ -218,8 +239,6 @@ export class WorkflowExecute {
|
||||||
return this.processRunExecutionData(workflow);
|
return this.processRunExecutionData(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the hook with the given name
|
* Executes the hook with the given name
|
||||||
*
|
*
|
||||||
|
@ -228,22 +247,31 @@ export class WorkflowExecute {
|
||||||
* @returns {Promise<IRun>}
|
* @returns {Promise<IRun>}
|
||||||
* @memberof WorkflowExecute
|
* @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) {
|
if (this.additionalData.hooks === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
return this.additionalData.hooks.executeHookFunctions(hookName, parameters);
|
return this.additionalData.hooks.executeHookFunctions(hookName, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the incoming connection does not receive any data
|
* 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 workflow.connectionsByDestinationNode[nodeToAdd].main[0]) {
|
||||||
for (const inputConnection of inputConnections) {
|
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) {
|
if (nodeIncomingData !== undefined && (nodeIncomingData as object[]).length !== 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -251,79 +279,117 @@ export class WorkflowExecute {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addNodeToBeExecuted(
|
||||||
addNodeToBeExecuted(workflow: Workflow, connectionData: IConnection, outputIndex: number, parentNodeName: string, nodeSuccessData: INodeExecutionData[][], runIndex: number): void {
|
workflow: Workflow,
|
||||||
|
connectionData: IConnection,
|
||||||
|
outputIndex: number,
|
||||||
|
parentNodeName: string,
|
||||||
|
nodeSuccessData: INodeExecutionData[][],
|
||||||
|
runIndex: number,
|
||||||
|
): void {
|
||||||
let stillDataMissing = false;
|
let stillDataMissing = false;
|
||||||
|
|
||||||
// Check if node has multiple inputs as then we have to wait for all input data
|
// 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
|
// 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
|
// Node has multiple inputs
|
||||||
let nodeWasWaiting = true;
|
let nodeWasWaiting = true;
|
||||||
|
|
||||||
// Check if there is already data for the node
|
// 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
|
// Node does not have data yet so create a new empty one
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||||
nodeWasWaiting = false;
|
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
|
// Node does not have data for runIndex yet so create also empty one and init it
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||||
main: [],
|
main: [],
|
||||||
};
|
};
|
||||||
for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
|
for (
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.push(null);
|
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
|
// Add the new data
|
||||||
if (nodeSuccessData === null) {
|
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 {
|
} 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
|
// Check if all data exists now
|
||||||
let thisExecutionData: INodeExecutionData[] | null;
|
let thisExecutionData: INodeExecutionData[] | null;
|
||||||
let allDataFound = true;
|
let allDataFound = true;
|
||||||
for (let i = 0; i < this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.length; i++) {
|
for (
|
||||||
thisExecutionData = this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[i];
|
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) {
|
if (thisExecutionData === null) {
|
||||||
allDataFound = false;
|
allDataFound = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allDataFound === true) {
|
if (allDataFound) {
|
||||||
// All data exists for node to be executed
|
// All data exists for node to be executed
|
||||||
// So add it to the execution stack
|
// So add it to the execution stack
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||||
node: workflow.nodes[connectionData.node],
|
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
|
// Remove the data from waiting
|
||||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
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
|
// No more data left for the node so also delete that one
|
||||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
||||||
}
|
}
|
||||||
return;
|
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
|
// Get a list of all the output nodes that we can check for siblings easier
|
||||||
const checkOutputNodes = [];
|
const checkOutputNodes = [];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||||
for (const outputIndexParent in workflow.connectionsBySourceNode[parentNodeName].main) {
|
for (const outputIndexParent in workflow.connectionsBySourceNode[parentNodeName].main) {
|
||||||
if (!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)) {
|
if (
|
||||||
|
!workflow.connectionsBySourceNode[parentNodeName].main.hasOwnProperty(outputIndexParent)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[outputIndexParent]) {
|
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[
|
||||||
|
outputIndexParent
|
||||||
|
]) {
|
||||||
checkOutputNodes.push(connectionDataCheck.node);
|
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
|
// checked. So we have to go through all the inputs and check if they
|
||||||
// are already on the list to be processed.
|
// are already on the list to be processed.
|
||||||
// If that is not the case add it.
|
// If that is not the case add it.
|
||||||
for (let inputIndex = 0; inputIndex < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; inputIndex++) {
|
for (
|
||||||
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node]['main'][inputIndex]) {
|
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) {
|
if (inputData.node === parentNodeName) {
|
||||||
// Is the node we come from so its data will be available for sure
|
// Is the node we come from so its data will be available for sure
|
||||||
continue;
|
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
|
// Check if that node is also an output connection of the
|
||||||
// previously processed one
|
// previously processed one
|
||||||
|
@ -348,7 +422,13 @@ export class WorkflowExecute {
|
||||||
// will then process this node next. So nothing to do
|
// will then process this node next. So nothing to do
|
||||||
// unless the incoming data of the node is empty
|
// unless the incoming data of the node is empty
|
||||||
// because then it would not be executed
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -401,7 +481,10 @@ export class WorkflowExecute {
|
||||||
nodeToAdd = parentNode;
|
nodeToAdd = parentNode;
|
||||||
}
|
}
|
||||||
const parentNodesNodeToAdd = workflow.getParentNodes(nodeToAdd as string);
|
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
|
// 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
|
// 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.
|
// specifically not run, as it did not receive any data.
|
||||||
|
@ -418,18 +501,21 @@ export class WorkflowExecute {
|
||||||
if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
|
if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
|
||||||
// Add empty item if the node does not have any input connections
|
// Add empty item if the node does not have any input connections
|
||||||
addEmptyItem = true;
|
addEmptyItem = true;
|
||||||
} else {
|
} else if (
|
||||||
if (this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[nodeToAdd].main[0], runIndex)) {
|
this.incomingConnectionIsEmpty(
|
||||||
|
this.runExecutionData.resultData.runData,
|
||||||
|
workflow.connectionsByDestinationNode[nodeToAdd].main[0],
|
||||||
|
runIndex,
|
||||||
|
)
|
||||||
|
) {
|
||||||
// Add empty item also if the input data is empty
|
// Add empty item also if the input data is empty
|
||||||
addEmptyItem = true;
|
addEmptyItem = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (addEmptyItem === true) {
|
if (addEmptyItem) {
|
||||||
// Add only node if it does not have any inputs because else it will
|
// Add only node if it does not have any inputs because else it will
|
||||||
// be added by its input node later anyway.
|
// be added by its input node later anyway.
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.push(
|
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||||
{
|
|
||||||
node: workflow.getNode(nodeToAdd) as INode,
|
node: workflow.getNode(nodeToAdd) as INode,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
|
@ -440,8 +526,7 @@ export class WorkflowExecute {
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,9 +546,11 @@ export class WorkflowExecute {
|
||||||
connectionDataArray[connectionData.index] = nodeSuccessData[outputIndex];
|
connectionDataArray[connectionData.index] = nodeSuccessData[outputIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stillDataMissing === true) {
|
if (stillDataMissing) {
|
||||||
// Additional data is needed to run node so add it to waiting
|
// 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] = {};
|
||||||
}
|
}
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||||
|
@ -480,7 +567,6 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given execution data.
|
* Runs the given execution data.
|
||||||
*
|
*
|
||||||
|
@ -488,14 +574,17 @@ export class WorkflowExecute {
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
* @memberof WorkflowExecute
|
* @memberof WorkflowExecute
|
||||||
*/
|
*/
|
||||||
processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
// @ts-ignore
|
||||||
|
async processRunExecutionData(workflow: Workflow): PCancelable<IRun> {
|
||||||
Logger.verbose('Workflow execution started', { workflowId: workflow.id });
|
Logger.verbose('Workflow execution started', { workflowId: workflow.id });
|
||||||
|
|
||||||
const startedAt = new Date();
|
const startedAt = new Date();
|
||||||
|
|
||||||
const workflowIssues = workflow.checkReadyForExecution();
|
const workflowIssues = workflow.checkReadyForExecution();
|
||||||
if (workflowIssues !== null) {
|
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
|
// Variables which hold temporary data for each node-execution
|
||||||
|
@ -521,7 +610,7 @@ export class WorkflowExecute {
|
||||||
let currentExecutionTry = '';
|
let currentExecutionTry = '';
|
||||||
let lastExecutionTry = '';
|
let lastExecutionTry = '';
|
||||||
|
|
||||||
return new PCancelable((resolve, reject, onCancel) => {
|
return new PCancelable(async (resolve, reject, onCancel) => {
|
||||||
let gotCancel = false;
|
let gotCancel = false;
|
||||||
|
|
||||||
onCancel.shouldReject = false;
|
onCancel.shouldReject = false;
|
||||||
|
@ -533,7 +622,6 @@ export class WorkflowExecute {
|
||||||
try {
|
try {
|
||||||
await this.executeHook('workflowExecuteBefore', [workflow]);
|
await this.executeHook('workflowExecuteBefore', [workflow]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
// Set the error that it can be saved correctly
|
// Set the error that it can be saved correctly
|
||||||
executionError = {
|
executionError = {
|
||||||
...error,
|
...error,
|
||||||
|
@ -542,16 +630,17 @@ export class WorkflowExecute {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the incoming data of the node that it can be saved correctly
|
// 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 = {
|
this.runExecutionData.resultData = {
|
||||||
runData: {
|
runData: {
|
||||||
[executionData.node.name]: [
|
[executionData.node.name]: [
|
||||||
{
|
{
|
||||||
startTime,
|
startTime,
|
||||||
executionTime: (new Date().getTime()) - startTime,
|
executionTime: new Date().getTime() - startTime,
|
||||||
data: ({
|
data: {
|
||||||
'main': executionData.data.main,
|
main: executionData.data.main,
|
||||||
} as ITaskDataConnections),
|
} as ITaskDataConnections,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -562,24 +651,31 @@ export class WorkflowExecute {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
executionLoop:
|
executionLoop: while (
|
||||||
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
this.runExecutionData.executionData!.nodeExecutionStack.length !== 0
|
||||||
|
) {
|
||||||
if (this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp) {
|
if (
|
||||||
|
this.additionalData.executionTimeoutTimestamp !== undefined &&
|
||||||
|
Date.now() >= this.additionalData.executionTimeoutTimestamp
|
||||||
|
) {
|
||||||
gotCancel = true;
|
gotCancel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (gotCancel === true) {
|
if (gotCancel) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeSuccessData = null;
|
nodeSuccessData = null;
|
||||||
executionError = undefined;
|
executionError = undefined;
|
||||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
executionData =
|
||||||
|
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||||
executionNode = executionData.node;
|
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]);
|
await this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||||
|
|
||||||
// Get the index of the current run
|
// 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.');
|
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
|
// 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
|
// 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.
|
// they have the same parent and it executes all child nodes.
|
||||||
|
@ -608,17 +707,24 @@ export class WorkflowExecute {
|
||||||
let inputConnections: IConnection[][];
|
let inputConnections: IConnection[][];
|
||||||
let connectionIndex: number;
|
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++) {
|
for (
|
||||||
if (workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0) {
|
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)
|
// 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
|
// then ignore that it has inputs and simply execute it as it is without
|
||||||
// any data
|
// any data
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!executionData.data!.hasOwnProperty('main')) {
|
if (!executionData.data.hasOwnProperty('main')) {
|
||||||
// ExecutionData does not even have the connection set up so can
|
// ExecutionData does not even have the connection set up so can
|
||||||
// not have that data, so add it again to be executed later
|
// not have that data, so add it again to be executed later
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||||
|
@ -629,7 +735,10 @@ export class WorkflowExecute {
|
||||||
// Check if it has the data for all the inputs
|
// 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
|
// 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.
|
// 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
|
// Does not have the data of the connections so add back to stack
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||||
lastExecutionTry = currentExecutionTry;
|
lastExecutionTry = currentExecutionTry;
|
||||||
|
@ -653,22 +762,25 @@ export class WorkflowExecute {
|
||||||
let waitBetweenTries = 0;
|
let waitBetweenTries = 0;
|
||||||
if (executionData.node.retryOnFail === true) {
|
if (executionData.node.retryOnFail === true) {
|
||||||
// TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
|
// 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++) {
|
for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (gotCancel === true) {
|
if (gotCancel) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (tryIndex !== 0) {
|
if (tryIndex !== 0) {
|
||||||
// Reset executionError from previous error try
|
// Reset executionError from previous error try
|
||||||
executionError = undefined;
|
executionError = undefined;
|
||||||
if (waitBetweenTries !== 0) {
|
if (waitBetweenTries !== 0) {
|
||||||
// TODO: Improve that in the future and check if other nodes can
|
// TODO: Improve that in the future and check if other nodes can
|
||||||
// be executed in the meantime
|
// be executed in the meantime
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
|
@ -677,9 +789,23 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.debug(`Running node "${executionNode.name}" started`, { node: executionNode.name, workflowId: workflow.id });
|
Logger.debug(`Running node "${executionNode.name}" started`, {
|
||||||
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
|
node: executionNode.name,
|
||||||
Logger.debug(`Running node "${executionNode.name}" finished successfully`, { node: executionNode.name, workflowId: workflow.id });
|
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) {
|
if (nodeSuccessData === undefined) {
|
||||||
// Node did not get executed
|
// 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
|
// If null gets returned it means that the node did succeed
|
||||||
// but did not have any data. So the branch should end
|
// but did not have any data. So the branch should end
|
||||||
// (meaning the nodes afterwards should not be processed)
|
// (meaning the nodes afterwards should not be processed)
|
||||||
|
@ -708,7 +834,6 @@ export class WorkflowExecute {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||||
|
|
||||||
executionError = {
|
executionError = {
|
||||||
|
@ -717,7 +842,10 @@ export class WorkflowExecute {
|
||||||
stack: error.stack,
|
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 = {
|
taskData = {
|
||||||
startTime,
|
startTime,
|
||||||
executionTime: (new Date().getTime()) - startTime,
|
executionTime: new Date().getTime() - startTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (executionError !== undefined) {
|
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
|
// Simply get the input data of the node if it has any and pass it through
|
||||||
// to the next node
|
// to the next node
|
||||||
if (executionData.data.main[0] !== null) {
|
if (executionData.data.main[0] !== null) {
|
||||||
nodeSuccessData = [executionData.data.main[0] as INodeExecutionData[]];
|
nodeSuccessData = [executionData.data.main[0]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -751,30 +879,46 @@ export class WorkflowExecute {
|
||||||
// Add the execution data again so that it can get restarted
|
// Add the execution data again so that it can get restarted
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node executed successfully. So add data and go on.
|
// Node executed successfully. So add data and go on.
|
||||||
taskData.data = ({
|
taskData.data = {
|
||||||
'main': nodeSuccessData,
|
main: nodeSuccessData,
|
||||||
} as ITaskDataConnections);
|
} as ITaskDataConnections;
|
||||||
|
|
||||||
this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
|
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
|
// Before stopping, make sure we are executing hooks so
|
||||||
// That frontend is notified for example for manual executions.
|
// 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
|
// If destination node is defined and got executed stop execution
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.runExecutionData.waitTill!!) {
|
if (this.runExecutionData.waitTill!) {
|
||||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
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
|
// Add the node back to the stack that the workflow can start to execute again from that node
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||||
|
@ -786,24 +930,46 @@ export class WorkflowExecute {
|
||||||
// be executed next
|
// be executed next
|
||||||
if (workflow.connectionsBySourceNode.hasOwnProperty(executionNode.name)) {
|
if (workflow.connectionsBySourceNode.hasOwnProperty(executionNode.name)) {
|
||||||
if (workflow.connectionsBySourceNode[executionNode.name].hasOwnProperty('main')) {
|
if (workflow.connectionsBySourceNode[executionNode.name].hasOwnProperty('main')) {
|
||||||
let outputIndex: string, connectionData: IConnection;
|
let outputIndex: string;
|
||||||
|
let connectionData: IConnection;
|
||||||
// Iterate over all the outputs
|
// Iterate over all the outputs
|
||||||
|
|
||||||
// Add the nodes to be executed
|
// Add the nodes to be executed
|
||||||
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name]['main']) {
|
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||||
if (!workflow.connectionsBySourceNode[executionNode.name]['main'].hasOwnProperty(outputIndex)) {
|
for (outputIndex in workflow.connectionsBySourceNode[executionNode.name].main) {
|
||||||
|
if (
|
||||||
|
!workflow.connectionsBySourceNode[executionNode.name].main.hasOwnProperty(
|
||||||
|
outputIndex,
|
||||||
|
)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over all the different connections of this output
|
// 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)) {
|
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
|
// 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
|
// 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
|
// Await is needed to make sure that we don't fall into concurrency problems
|
||||||
// When saving node execution data
|
// 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();
|
return Promise.resolve();
|
||||||
})()
|
})()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
if (gotCancel && executionError === undefined) {
|
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);
|
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||||
})
|
})
|
||||||
|
@ -837,13 +1010,18 @@ export class WorkflowExecute {
|
||||||
|
|
||||||
// Check if static data changed
|
// Check if static data changed
|
||||||
let newStaticData: IDataObject | undefined;
|
let newStaticData: IDataObject | undefined;
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
if (workflow.staticData.__dataChanged === true) {
|
if (workflow.staticData.__dataChanged === true) {
|
||||||
// Static data of workflow changed
|
// Static data of workflow changed
|
||||||
newStaticData = workflow.staticData;
|
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);
|
console.error('There was a problem running hook "workflowExecuteAfter"', error);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return fullRunData;
|
return fullRunData;
|
||||||
});
|
});
|
||||||
|
@ -852,20 +1030,29 @@ export class WorkflowExecute {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async processSuccessExecution(
|
||||||
|
startedAt: Date,
|
||||||
|
workflow: Workflow,
|
||||||
|
executionError?: ExecutionError,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: ExecutionError): PCancelable<IRun> {
|
): PCancelable<IRun> {
|
||||||
const fullRunData = this.getFullRunData(startedAt);
|
const fullRunData = this.getFullRunData(startedAt);
|
||||||
|
|
||||||
if (executionError !== undefined) {
|
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 = {
|
fullRunData.data.resultData.error = {
|
||||||
...executionError,
|
...executionError,
|
||||||
message: executionError.message,
|
message: executionError.message,
|
||||||
stack: executionError.stack,
|
stack: executionError.stack,
|
||||||
} as ExecutionError;
|
} as ExecutionError;
|
||||||
} else if (this.runExecutionData.waitTill!!) {
|
} else if (this.runExecutionData.waitTill!) {
|
||||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, { workflowId: workflow.id });
|
// 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;
|
fullRunData.waitTill = this.runExecutionData.waitTill;
|
||||||
} else {
|
} else {
|
||||||
Logger.verbose(`Workflow execution finished successfully`, { workflowId: workflow.id });
|
Logger.verbose(`Workflow execution finished successfully`, { workflowId: workflow.id });
|
||||||
|
@ -874,6 +1061,7 @@ export class WorkflowExecute {
|
||||||
|
|
||||||
// Check if static data changed
|
// Check if static data changed
|
||||||
let newStaticData: IDataObject | undefined;
|
let newStaticData: IDataObject | undefined;
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
if (workflow.staticData.__dataChanged === true) {
|
if (workflow.staticData.__dataChanged === true) {
|
||||||
// Static data of workflow changed
|
// Static data of workflow changed
|
||||||
newStaticData = workflow.staticData;
|
newStaticData = workflow.staticData;
|
||||||
|
@ -894,5 +1082,4 @@ export class WorkflowExecute {
|
||||||
|
|
||||||
return fullRunData;
|
return fullRunData;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
try {
|
/* eslint-disable import/no-cycle */
|
||||||
require('source-map-support').install();
|
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||||
} catch (error) {
|
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 './ActiveWorkflows';
|
||||||
export * from './ActiveWebhooks';
|
export * from './ActiveWebhooks';
|
||||||
|
@ -13,10 +17,4 @@ export * from './Interfaces';
|
||||||
export * from './LoadNodeParameterOptions';
|
export * from './LoadNodeParameterOptions';
|
||||||
export * from './NodeExecuteFunctions';
|
export * from './NodeExecuteFunctions';
|
||||||
export * from './WorkflowExecute';
|
export * from './WorkflowExecute';
|
||||||
|
export { NodeExecuteFunctions, UserSettings };
|
||||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
|
||||||
import * as UserSettings from './UserSettings';
|
|
||||||
export {
|
|
||||||
NodeExecuteFunctions,
|
|
||||||
UserSettings,
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
|
|
||||||
import { Credentials } from '../src';
|
import { Credentials } from '../src';
|
||||||
|
|
||||||
describe('Credentials', () => {
|
describe('Credentials', () => {
|
||||||
|
|
||||||
describe('without nodeType set', () => {
|
describe('without nodeType set', () => {
|
||||||
|
|
||||||
test('should be able to set and read key data without initial data set', () => {
|
test('should be able to set and read key data without initial data set', () => {
|
||||||
|
|
||||||
const credentials = new Credentials('testName', 'testType', []);
|
const credentials = new Credentials('testName', 'testType', []);
|
||||||
|
|
||||||
const key = 'key1';
|
const key = 'key1';
|
||||||
|
@ -20,7 +16,6 @@ describe('Credentials', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to set and read key data with initial data set', () => {
|
test('should be able to set and read key data with initial data set', () => {
|
||||||
|
|
||||||
const key = 'key2';
|
const key = 'key2';
|
||||||
const password = 'password';
|
const password = 'password';
|
||||||
|
|
||||||
|
@ -39,13 +34,10 @@ describe('Credentials', () => {
|
||||||
// Read the data which got provided encrypted on init
|
// Read the data which got provided encrypted on init
|
||||||
expect(credentials.getDataKey('key1', password)).toEqual(initialData);
|
expect(credentials.getDataKey('key1', password)).toEqual(initialData);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with nodeType set', () => {
|
describe('with nodeType set', () => {
|
||||||
|
|
||||||
test('should be able to set and read key data without initial data set', () => {
|
test('should be able to set and read key data without initial data set', () => {
|
||||||
|
|
||||||
const nodeAccess = [
|
const nodeAccess = [
|
||||||
{
|
{
|
||||||
nodeType: 'base.noOp',
|
nodeType: 'base.noOp',
|
||||||
|
@ -72,7 +64,9 @@ describe('Credentials', () => {
|
||||||
credentials.getDataKey(key, password, 'base.otherNode');
|
credentials.getDataKey(key, password, 'base.otherNode');
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
} catch (e) {
|
} 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
|
// Get the data which will be saved in database
|
||||||
|
@ -81,8 +75,9 @@ describe('Credentials', () => {
|
||||||
expect(dbData.type).toEqual('testType');
|
expect(dbData.type).toEqual('testType');
|
||||||
expect(dbData.nodesAccess).toEqual(nodeAccess);
|
expect(dbData.nodesAccess).toEqual(nodeAccess);
|
||||||
// Compare only the first 6 characters as the rest seems to change with each execution
|
// 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,
|
WorkflowHooks,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import { Credentials, IDeferredPromise, IExecuteFunctions } from '../src';
|
||||||
Credentials,
|
|
||||||
IDeferredPromise,
|
|
||||||
IExecuteFunctions,
|
|
||||||
} from '../src';
|
|
||||||
|
|
||||||
|
|
||||||
export class CredentialsHelper extends ICredentialsHelper {
|
export class CredentialsHelper extends ICredentialsHelper {
|
||||||
getDecrypted(name: string, type: string): Promise<ICredentialDataDecryptedObject> {
|
getDecrypted(name: string, type: string): Promise<ICredentialDataDecryptedObject> {
|
||||||
return new Promise(res => res({}));
|
return new Promise((res) => res({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getCredentials(name: string, type: string): Promise<Credentials> {
|
getCredentials(name: string, type: string): Promise<Credentials> {
|
||||||
return new Promise(res => {
|
return new Promise((res) => {
|
||||||
res(new Credentials('', '', [], ''));
|
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 {
|
class NodeTypesClass implements INodeTypes {
|
||||||
|
|
||||||
nodeTypes: INodeTypeData = {
|
nodeTypes: INodeTypeData = {
|
||||||
'n8n-nodes-base.if': {
|
'n8n-nodes-base.if': {
|
||||||
sourcePath: '',
|
sourcePath: '',
|
||||||
|
@ -161,9 +158,7 @@ class NodeTypesClass implements INodeTypes {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
operation: [
|
operation: ['isEmpty'],
|
||||||
'isEmpty',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: 0,
|
default: 0,
|
||||||
|
@ -229,10 +224,7 @@ class NodeTypesClass implements INodeTypes {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
operation: [
|
operation: ['isEmpty', 'regex'],
|
||||||
'isEmpty',
|
|
||||||
'regex',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -244,9 +236,7 @@ class NodeTypesClass implements INodeTypes {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: [
|
operation: ['regex'],
|
||||||
'regex',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -274,7 +264,8 @@ class NodeTypesClass implements INodeTypes {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default: 'all',
|
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: {
|
const compareOperationFunctions: {
|
||||||
[key: string]: (value1: NodeParameterValue, value2: NodeParameterValue) => boolean;
|
[key: string]: (value1: NodeParameterValue, value2: NodeParameterValue) => boolean;
|
||||||
} = {
|
} = {
|
||||||
contains: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || '').toString().includes((value2 || '').toString()),
|
contains: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||||
notContains: (value1: NodeParameterValue, value2: NodeParameterValue) => !(value1 || '').toString().includes((value2 || '').toString()),
|
(value1 || '').toString().includes((value2 || '').toString()),
|
||||||
endsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 as string).endsWith(value2 as string),
|
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,
|
equal: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 === value2,
|
||||||
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
||||||
larger: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) > (value2 || 0),
|
larger: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||||
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) >= (value2 || 0),
|
(value1 || 0) > (value2 || 0),
|
||||||
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) < (value2 || 0),
|
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||||
smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) <= (value2 || 0),
|
(value1 || 0) >= (value2 || 0),
|
||||||
startsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 as string).startsWith(value2 as string),
|
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||||
isEmpty: (value1: NodeParameterValue) => [undefined, null, ''].includes(value1 as string),
|
(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) => {
|
regex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
||||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
const regexMatch = (value2 || '')
|
||||||
|
.toString()
|
||||||
|
.match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
||||||
|
|
||||||
let regex: RegExp;
|
let regex: RegExp;
|
||||||
if (!regexMatch) {
|
if (!regexMatch) {
|
||||||
|
@ -319,18 +321,13 @@ class NodeTypesClass implements INodeTypes {
|
||||||
};
|
};
|
||||||
|
|
||||||
// The different dataTypes to check the values in
|
// The different dataTypes to check the values in
|
||||||
const dataTypes = [
|
const dataTypes = ['boolean', 'number', 'string'];
|
||||||
'boolean',
|
|
||||||
'number',
|
|
||||||
'string',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Itterate over all items to check which ones should be output as via output "true" and
|
// Itterate over all items to check which ones should be output as via output "true" and
|
||||||
// which ones via output "false"
|
// which ones via output "false"
|
||||||
let dataType: string;
|
let dataType: string;
|
||||||
let compareOperationResult: boolean;
|
let compareOperationResult: boolean;
|
||||||
itemLoop:
|
itemLoop: for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
||||||
item = items[itemIndex];
|
item = items[itemIndex];
|
||||||
|
|
||||||
let compareData: INodeParameters;
|
let compareData: INodeParameters;
|
||||||
|
@ -340,9 +337,16 @@ class NodeTypesClass implements INodeTypes {
|
||||||
// Check all the values of the different dataTypes
|
// Check all the values of the different dataTypes
|
||||||
for (dataType of dataTypes) {
|
for (dataType of dataTypes) {
|
||||||
// Check all the values of the current dataType
|
// 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
|
// 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 (compareOperationResult === true && combineOperation === 'any') {
|
||||||
// If it passes and the operation is "any" we do not have to check 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',
|
name: 'Append',
|
||||||
value: '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',
|
name: 'Pass-through',
|
||||||
value: 'passThrough',
|
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',
|
name: 'Wait',
|
||||||
value: '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',
|
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',
|
displayName: 'Output Data',
|
||||||
|
@ -419,9 +427,7 @@ class NodeTypesClass implements INodeTypes {
|
||||||
type: 'options',
|
type: 'options',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: [
|
mode: ['passThrough'],
|
||||||
'passThrough',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
|
@ -512,7 +518,8 @@ class NodeTypesClass implements INodeTypes {
|
||||||
name: 'keepOnlySet',
|
name: 'keepOnlySet',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
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',
|
displayName: 'Values to Set',
|
||||||
|
@ -534,7 +541,8 @@ class NodeTypesClass implements INodeTypes {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'propertyName',
|
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',
|
displayName: 'Value',
|
||||||
|
@ -554,7 +562,8 @@ class NodeTypesClass implements INodeTypes {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'propertyName',
|
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',
|
displayName: 'Value',
|
||||||
|
@ -574,7 +583,8 @@ class NodeTypesClass implements INodeTypes {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'propertyName',
|
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',
|
displayName: 'Value',
|
||||||
|
@ -610,7 +620,6 @@ class NodeTypesClass implements INodeTypes {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
|
@ -643,31 +652,37 @@ class NodeTypesClass implements INodeTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add boolean values
|
// 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) {
|
if (options.dotNotation === false) {
|
||||||
newItem.json[setItem.name as string] = !!setItem.value;
|
newItem.json[setItem.name as string] = !!setItem.value;
|
||||||
} else {
|
} else {
|
||||||
set(newItem.json, setItem.name as string, !!setItem.value);
|
set(newItem.json, setItem.name as string, !!setItem.value);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Add number values
|
// 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) {
|
if (options.dotNotation === false) {
|
||||||
newItem.json[setItem.name as string] = setItem.value;
|
newItem.json[setItem.name as string] = setItem.value;
|
||||||
} else {
|
} else {
|
||||||
set(newItem.json, setItem.name as string, setItem.value);
|
set(newItem.json, setItem.name as string, setItem.value);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Add string values
|
// 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) {
|
if (options.dotNotation === false) {
|
||||||
newItem.json[setItem.name as string] = setItem.value;
|
newItem.json[setItem.name as string] = setItem.value;
|
||||||
} else {
|
} else {
|
||||||
set(newItem.json, setItem.name as string, setItem.value);
|
set(newItem.json, setItem.name as string, setItem.value);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
returnData.push(newItem);
|
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[] {
|
getAll(): INodeType[] {
|
||||||
return Object.values(this.nodeTypes).map((data) => data.type);
|
return Object.values(this.nodeTypes).map((data) => data.type);
|
||||||
|
@ -715,7 +730,6 @@ class NodeTypesClass implements INodeTypes {
|
||||||
|
|
||||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||||
|
|
||||||
|
|
||||||
export function NodeTypes(): NodeTypesClass {
|
export function NodeTypes(): NodeTypesClass {
|
||||||
if (nodeTypesInstance === undefined) {
|
if (nodeTypesInstance === undefined) {
|
||||||
nodeTypesInstance = new NodeTypesClass();
|
nodeTypesInstance = new NodeTypesClass();
|
||||||
|
@ -725,8 +739,10 @@ export function NodeTypes(): NodeTypesClass {
|
||||||
return nodeTypesInstance;
|
return nodeTypesInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function WorkflowExecuteAdditionalData(
|
||||||
export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun>, nodeExecutionOrder: string[]): IWorkflowExecuteAdditionalData {
|
waitPromise: IDeferredPromise<IRun>,
|
||||||
|
nodeExecutionOrder: string[],
|
||||||
|
): IWorkflowExecuteAdditionalData {
|
||||||
const hookFunctions = {
|
const hookFunctions = {
|
||||||
nodeExecuteAfter: [
|
nodeExecuteAfter: [
|
||||||
async (nodeName: string, data: ITaskData): Promise<void> => {
|
async (nodeName: string, data: ITaskData): Promise<void> => {
|
||||||
|
@ -752,7 +768,7 @@ export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun
|
||||||
return {
|
return {
|
||||||
credentialsHelper: new CredentialsHelper(''),
|
credentialsHelper: new CredentialsHelper(''),
|
||||||
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData),
|
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) => {},
|
sendMessageToUI: (message: string) => {},
|
||||||
restApiUrl: '',
|
restApiUrl: '',
|
||||||
encryptionKey: 'test',
|
encryptionKey: 'test',
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,11 +20,10 @@
|
||||||
"build:storybook": "build-storybook",
|
"build:storybook": "build-storybook",
|
||||||
"storybook": "start-storybook -p 6006",
|
"storybook": "start-storybook -p 6006",
|
||||||
"test:unit": "vue-cli-service test:unit --passWithNoTests",
|
"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",
|
"build:theme": "gulp build:theme",
|
||||||
"watch:theme": "gulp watch:theme",
|
"watch:theme": "gulp watch:theme"
|
||||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
|
||||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "1.x",
|
"@fortawesome/fontawesome-svg-core": "1.x",
|
||||||
|
@ -49,27 +48,27 @@
|
||||||
"@storybook/addon-links": "^6.3.6",
|
"@storybook/addon-links": "^6.3.6",
|
||||||
"@storybook/vue": "^6.3.6",
|
"@storybook/vue": "^6.3.6",
|
||||||
"@types/jest": "^26.0.13",
|
"@types/jest": "^26.0.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||||
"@typescript-eslint/parser": "^4.18.0",
|
"@typescript-eslint/parser": "^4.29.0",
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
"@vue/cli-plugin-eslint": "~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-plugin-unit-jest": "~4.5.0",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~4.5.0",
|
||||||
"@vue/eslint-config-prettier": "^6.0.0",
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
"@vue/eslint-config-typescript": "^7.0.0",
|
"@vue/eslint-config-typescript": "^7.0.0",
|
||||||
"@vue/test-utils": "^1.0.3",
|
"@vue/test-utils": "^1.0.3",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^7.16.0",
|
||||||
"fibers": "^5.0.0",
|
"fibers": "^5.0.0",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.3.2",
|
||||||
"sass": "^1.26.5",
|
"sass": "^1.26.5",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"storybook-addon-designs": "^6.0.1",
|
"storybook-addon-designs": "^6.0.1",
|
||||||
"typescript": "~3.9.7",
|
"typescript": "~4.3.5",
|
||||||
"vue-loader": "^15.9.7",
|
"vue-loader": "^15.9.7",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.11",
|
||||||
"gulp-autoprefixer": "^4.0.0",
|
"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: [
|
// transpileDependencies: [
|
||||||
// /\/node_modules\/quill/
|
// /\/node_modules\/quill/
|
||||||
// ]
|
// ]
|
||||||
|
plugins: [
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
// // https://stackoverflow.com/questions/44625868/es6-babel-class-constructor-cannot-be-invoked-without-new
|
// // https://stackoverflow.com/questions/44625868/es6-babel-class-constructor-cannot-be-invoked-without-new
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cross-env VUE_APP_PUBLIC_PATH=\"/%BASE_PATH%/\" vue-cli-service build",
|
"build": "cross-env VUE_APP_PUBLIC_PATH=\"/%BASE_PATH%/\" vue-cli-service build",
|
||||||
"dev": "npm run serve",
|
"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",
|
"serve": "cross-env VUE_APP_URL_BASE_API=http://localhost:5678/ vue-cli-service serve",
|
||||||
"test": "npm run test:unit",
|
"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:e2e": "vue-cli-service test:e2e",
|
||||||
"test:unit": "vue-cli-service test:unit"
|
"test:unit": "vue-cli-service test:unit"
|
||||||
},
|
},
|
||||||
|
@ -44,11 +44,11 @@
|
||||||
"@types/node": "^14.14.40",
|
"@types/node": "^14.14.40",
|
||||||
"@types/quill": "^2.0.1",
|
"@types/quill": "^2.0.1",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||||
"@typescript-eslint/parser": "^4.18.0",
|
"@typescript-eslint/parser": "^4.29.0",
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
"@vue/cli-plugin-eslint": "~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-plugin-unit-jest": "~4.5.0",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~4.5.0",
|
||||||
"@vue/eslint-config-standard": "^5.0.1",
|
"@vue/eslint-config-standard": "^5.0.1",
|
||||||
|
@ -60,9 +60,9 @@
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"dateformat": "^3.0.3",
|
"dateformat": "^3.0.3",
|
||||||
"element-ui": "~2.13.0",
|
"element-ui": "~2.13.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-import": "^2.19.1",
|
"eslint-plugin-import": "^2.23.4",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^7.16.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"flatted": "^2.0.0",
|
"flatted": "^2.0.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
"string-template-parser": "^1.2.6",
|
"string-template-parser": "^1.2.6",
|
||||||
"ts-jest": "^26.3.0",
|
"ts-jest": "^26.3.0",
|
||||||
"tslint": "^6.1.2",
|
"tslint": "^6.1.2",
|
||||||
"typescript": "~3.9.7",
|
"typescript": "~4.3.5",
|
||||||
"uuid": "^8.3.0",
|
"uuid": "^8.3.0",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<template functional>
|
<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">
|
<a v-if="props.version" :set="version = props.version" :href="version.documentationUrl" target="_blank" :class="$style.card">
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import {
|
import { UserSettings } from 'n8n-core';
|
||||||
UserSettings,
|
|
||||||
} from "n8n-core";
|
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
|
|
||||||
import {
|
import { buildFiles, IBuildOptions } from '../src';
|
||||||
buildFiles,
|
|
||||||
IBuildOptions,
|
|
||||||
} from '../src';
|
|
||||||
|
|
||||||
export class Build extends Command {
|
export class Build extends Command {
|
||||||
static description = 'Builds credentials and nodes and copies it to n8n custom extension folder';
|
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()}]`,
|
description: `The path to copy the compiles files to [default: ${UserSettings.getUserN8nFolderCustomExtensionPath()}]`,
|
||||||
}),
|
}),
|
||||||
watch: flags.boolean({
|
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() {
|
async run() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const { flags } = this.parse(Build);
|
const { flags } = this.parse(Build);
|
||||||
|
|
||||||
this.log('\nBuild credentials and nodes');
|
this.log('\nBuild credentials and nodes');
|
||||||
|
@ -47,13 +45,12 @@ export class Build extends Command {
|
||||||
const outputDirectory = await buildFiles(options);
|
const outputDirectory = await buildFiles(options);
|
||||||
|
|
||||||
this.log(`The nodes got build and saved into the following folder:\n${outputDirectory}`);
|
this.log(`The nodes got build and saved into the following folder:\n${outputDirectory}`);
|
||||||
|
|
||||||
} catch (error) {
|
} 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(`\nGOT ERROR: "${error.message}"`);
|
||||||
this.log('====================================');
|
this.log('====================================');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||||
this.log(error.stack);
|
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 changeCase from 'change-case';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as inquirer from 'inquirer';
|
import * as inquirer from 'inquirer';
|
||||||
import { Command } from '@oclif/command';
|
import { Command } from '@oclif/command';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
const { promisify } = require('util');
|
import { createTemplate } from '../src';
|
||||||
const fsAccess = promisify(fs.access);
|
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
createTemplate
|
const { promisify } = require('util');
|
||||||
} from '../src';
|
|
||||||
|
const fsAccess = promisify(fs.access);
|
||||||
|
|
||||||
export class New extends Command {
|
export class New extends Command {
|
||||||
static description = 'Create new credentials/node';
|
static description = 'Create new credentials/node';
|
||||||
|
|
||||||
static examples = [
|
static examples = [`$ n8n-node-dev new`];
|
||||||
`$ n8n-node-dev new`,
|
|
||||||
];
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
async run() {
|
async run() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.log('\nCreate new credentials/node');
|
this.log('\nCreate new credentials/node');
|
||||||
this.log('=========================');
|
this.log('=========================');
|
||||||
|
@ -30,10 +31,7 @@ export class New extends Command {
|
||||||
type: 'list',
|
type: 'list',
|
||||||
default: 'Node',
|
default: 'Node',
|
||||||
message: 'What do you want to create?',
|
message: 'What do you want to create?',
|
||||||
choices: [
|
choices: ['Credentials', 'Node'],
|
||||||
'Credentials',
|
|
||||||
'Node',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeAnswers = await inquirer.prompt(typeQuestion);
|
const typeAnswers = await inquirer.prompt(typeQuestion);
|
||||||
|
@ -43,7 +41,6 @@ export class New extends Command {
|
||||||
let defaultName = '';
|
let defaultName = '';
|
||||||
let getDescription = false;
|
let getDescription = false;
|
||||||
|
|
||||||
|
|
||||||
if (typeAnswers.type === 'Node') {
|
if (typeAnswers.type === 'Node') {
|
||||||
// Create new node
|
// Create new node
|
||||||
|
|
||||||
|
@ -54,11 +51,7 @@ export class New extends Command {
|
||||||
type: 'list',
|
type: 'list',
|
||||||
default: 'Execute',
|
default: 'Execute',
|
||||||
message: 'What kind of node do you want to create?',
|
message: 'What kind of node do you want to create?',
|
||||||
choices: [
|
choices: ['Execute', 'Trigger', 'Webhook'],
|
||||||
'Execute',
|
|
||||||
'Trigger',
|
|
||||||
'Webhook',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeTypeAnswers = await inquirer.prompt(nodeTypeQuestion);
|
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
|
// Get also a node description
|
||||||
additionalQuestions.push({
|
additionalQuestions.push({
|
||||||
name: 'description',
|
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;
|
const nodeName = additionalAnswers.name;
|
||||||
|
|
||||||
// Define the source file to be used and the location and name of the new
|
// Define the source file to be used and the location and name of the new
|
||||||
// node file
|
// 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);
|
const sourceFilePath = join(__dirname, '../../templates', sourceFolder, sourceFileName);
|
||||||
|
|
||||||
|
@ -150,12 +149,13 @@ export class New extends Command {
|
||||||
this.log('\nExecution was successfull:');
|
this.log('\nExecution was successfull:');
|
||||||
this.log('====================================');
|
this.log('====================================');
|
||||||
|
|
||||||
this.log('Node got created: ' + destinationFilePath);
|
this.log(`Node got created: ${destinationFilePath}`);
|
||||||
} catch (error) {
|
} 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(`\nGOT ERROR: "${error.message}"`);
|
||||||
this.log('====================================');
|
this.log('====================================');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
||||||
this.log(error.stack);
|
this.log(error.stack);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,11 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run watch",
|
"dev": "npm run watch",
|
||||||
"build": "tsc",
|
"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",
|
"postpack": "rm -f oclif.manifest.json",
|
||||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
"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"
|
"watch": "tsc --watch"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -64,7 +65,7 @@
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"replace-in-file": "^6.0.0",
|
"replace-in-file": "^6.0.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"tmp-promise": "^2.0.2",
|
"tmp-promise": "^3.0.2",
|
||||||
"typescript": "~3.9.7"
|
"typescript": "~4.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
import { ChildProcess, spawn } from 'child_process';
|
import { ChildProcess, spawn } from 'child_process';
|
||||||
const copyfiles = require('copyfiles');
|
|
||||||
|
|
||||||
import {
|
import { readFile as fsReadFile } from 'fs/promises';
|
||||||
readFile as fsReadFile,
|
import { write as fsWrite } from 'fs';
|
||||||
} from 'fs/promises';
|
|
||||||
import {
|
|
||||||
write as fsWrite,
|
|
||||||
} from 'fs';
|
|
||||||
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { file } from 'tmp-promise';
|
import { file } from 'tmp-promise';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
||||||
const fsReadFileAsync = promisify(fsReadFile);
|
import { UserSettings } from 'n8n-core';
|
||||||
const fsWriteAsync = promisify(fsWrite);
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
|
||||||
import { IBuildOptions } from '.';
|
import { IBuildOptions } from '.';
|
||||||
|
|
||||||
import {
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
|
||||||
UserSettings,
|
const copyfiles = require('copyfiles');
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
|
// 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
|
* Create a custom tsconfig file as tsc currently has no way to define a base
|
||||||
|
@ -30,23 +27,26 @@ import {
|
||||||
* @export
|
* @export
|
||||||
* @returns
|
* @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
|
// Get path to simple tsconfig file which should be used for build
|
||||||
const tsconfigPath = join(__dirname, '../../src/tsconfig-build.json');
|
const tsconfigPath = join(__dirname, '../../src/tsconfig-build.json');
|
||||||
|
|
||||||
// Read the tsconfi file
|
// 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);
|
const tsConfig = JSON.parse(tsConfigString);
|
||||||
|
|
||||||
// Set absolute include paths
|
// Set absolute include paths
|
||||||
const newIncludeFiles = [];
|
const newIncludeFiles = [];
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const includeFile of tsConfig.include) {
|
for (const includeFile of tsConfig.include) {
|
||||||
newIncludeFiles.push(join(process.cwd(), includeFile));
|
newIncludeFiles.push(join(process.cwd(), includeFile));
|
||||||
}
|
}
|
||||||
tsConfig.include = newIncludeFiles;
|
tsConfig.include = newIncludeFiles;
|
||||||
|
|
||||||
// Write new custom tsconfig file
|
// Write new custom tsconfig file
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
const { fd, path, cleanup } = await file({ dir: process.cwd() });
|
const { fd, path, cleanup } = await file({ dir: process.cwd() });
|
||||||
await fsWriteAsync(fd, Buffer.from(JSON.stringify(tsConfig, null, 2), 'utf8'));
|
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
|
* Builds and copies credentials and nodes
|
||||||
*
|
*
|
||||||
|
@ -64,7 +63,8 @@ export async function createCustomTsconfig () {
|
||||||
* @param {IBuildOptions} [options] Options to overwrite default behaviour
|
* @param {IBuildOptions} [options] Options to overwrite default behaviour
|
||||||
* @returns {Promise<string>}
|
* @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 || {};
|
options = options || {};
|
||||||
|
|
||||||
let typescriptPath;
|
let typescriptPath;
|
||||||
|
@ -79,24 +79,31 @@ export async function buildFiles (options?: IBuildOptions): Promise<string> {
|
||||||
|
|
||||||
const tsconfigData = await createCustomTsconfig();
|
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
|
// Supply a node base path so that it finds n8n-core and n8n-workflow
|
||||||
const nodeModulesPath = join(__dirname, '../../node_modules/');
|
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) {
|
if (options.watch === true) {
|
||||||
buildCommand += ' --watch';
|
buildCommand += ' --watch';
|
||||||
}
|
}
|
||||||
|
|
||||||
let buildProcess: ChildProcess;
|
let buildProcess: ChildProcess;
|
||||||
try {
|
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
|
// Forward the output of the child process to the main one
|
||||||
// that the user can see what is happening
|
// that the user can see what is happening
|
||||||
//@ts-ignore
|
// @ts-ignore
|
||||||
buildProcess.stdout.pipe(process.stdout);
|
buildProcess.stdout.pipe(process.stdout);
|
||||||
//@ts-ignore
|
// @ts-ignore
|
||||||
buildProcess.stderr.pipe(process.stderr);
|
buildProcess.stderr.pipe(process.stderr);
|
||||||
|
|
||||||
// Make sure that the child process gets also always terminated
|
// Make sure that the child process gets also always terminated
|
||||||
|
@ -105,27 +112,33 @@ export async function buildFiles (options?: IBuildOptions): Promise<string> {
|
||||||
buildProcess.kill();
|
buildProcess.kill();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
let errorMessage = error.message;
|
let errorMessage = error.message;
|
||||||
|
|
||||||
if (error.stdout !== undefined) {
|
if (error.stdout !== undefined) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
errorMessage = `${errorMessage}\nGot following output:\n${error.stdout}`;
|
errorMessage = `${errorMessage}\nGot following output:\n${error.stdout}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the tmp tsconfig file
|
// Remove the tmp tsconfig file
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
tsconfigData.cleanup();
|
tsconfigData.cleanup();
|
||||||
|
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
['*.png', '*.node.json'].forEach(filenamePattern => {
|
['*.png', '*.node.json'].forEach((filenamePattern) => {
|
||||||
copyfiles(
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
[join(process.cwd(), `./${filenamePattern}`), outputDirectory],
|
copyfiles([join(process.cwd(), `./${filenamePattern}`), outputDirectory], { up: true }, () =>
|
||||||
{ up: true },
|
resolve(outputDirectory),
|
||||||
() => resolve(outputDirectory));
|
);
|
||||||
});
|
});
|
||||||
buildProcess.on('exit', code => {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
buildProcess.on('exit', (code) => {
|
||||||
// Remove the tmp tsconfig file
|
// Remove the tmp tsconfig file
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
tsconfigData.cleanup();
|
tsconfigData.cleanup();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
|
||||||
import * as fs from 'fs';
|
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 { 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
|
* 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
|
* @param {object} replaceValues The values to replace in the template file
|
||||||
* @returns {Promise<void>}
|
* @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
|
// Copy the file to then replace the values in it
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
await fsCopyFile(sourceFilePath, destinationFilePath);
|
await fsCopyFile(sourceFilePath, destinationFilePath);
|
||||||
|
|
||||||
// Replace the variables in the template file
|
// Replace the variables in the template file
|
||||||
const options: ReplaceInFileConfig = {
|
const options: ReplaceInFileConfig = {
|
||||||
files: [
|
files: [destinationFilePath],
|
||||||
destinationFilePath,
|
|
||||||
],
|
|
||||||
from: [],
|
from: [],
|
||||||
to: [],
|
to: [],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
export * from './Build';
|
export * from './Build';
|
||||||
export * from './Create';
|
export * from './Create';
|
||||||
export * from './Interfaces';
|
export * from './Interfaces';
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import {
|
import { ICredentialType, NodePropertyTypes } from 'n8n-workflow';
|
||||||
ICredentialType,
|
|
||||||
NodePropertyTypes,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
|
|
||||||
export class ClassNameReplace implements ICredentialType {
|
export class ClassNameReplace implements ICredentialType {
|
||||||
name = 'N8nNameReplace';
|
name = 'N8nNameReplace';
|
||||||
|
|
||||||
displayName = 'DisplayNameReplace';
|
displayName = 'DisplayNameReplace';
|
||||||
|
|
||||||
properties = [
|
properties = [
|
||||||
// The credentials to get from user and save encrypted.
|
// The credentials to get from user and save encrypted.
|
||||||
// Properties can be defined exactly in the same way
|
// Properties can be defined exactly in the same way
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import { IExecuteFunctions } from 'n8n-core';
|
import { IExecuteFunctions } from 'n8n-core';
|
||||||
import {
|
import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
||||||
INodeExecutionData,
|
|
||||||
INodeType,
|
|
||||||
INodeTypeDescription,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
|
|
||||||
export class ClassNameReplace implements INodeType {
|
export class ClassNameReplace implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
@ -29,13 +24,11 @@ export class ClassNameReplace implements INodeType {
|
||||||
default: '',
|
default: '',
|
||||||
placeholder: 'Placeholder value',
|
placeholder: 'Placeholder value',
|
||||||
description: 'The description text',
|
description: 'The description text',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
|
|
||||||
let item: INodeExecutionData;
|
let item: INodeExecutionData;
|
||||||
|
@ -48,10 +41,9 @@ export class ClassNameReplace implements INodeType {
|
||||||
myString = this.getNodeParameter('myString', itemIndex, '') as string;
|
myString = this.getNodeParameter('myString', itemIndex, '') as string;
|
||||||
item = items[itemIndex];
|
item = items[itemIndex];
|
||||||
|
|
||||||
item.json['myString'] = myString;
|
item.json.myString = myString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prepareOutputData(items);
|
return this.prepareOutputData(items);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import { ITriggerFunctions } from 'n8n-core';
|
import { ITriggerFunctions } from 'n8n-core';
|
||||||
import {
|
import { INodeType, INodeTypeDescription, ITriggerResponse } from 'n8n-workflow';
|
||||||
INodeType,
|
|
||||||
INodeTypeDescription,
|
|
||||||
ITriggerResponse,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
|
|
||||||
export class ClassNameReplace implements INodeType {
|
export class ClassNameReplace implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
@ -32,12 +27,10 @@ export class ClassNameReplace implements INodeType {
|
||||||
default: 1,
|
default: 1,
|
||||||
description: 'Every how many minutes the workflow should be triggered.',
|
description: 'Every how many minutes the workflow should be triggered.',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||||
|
|
||||||
const interval = this.getNodeParameter('interval', 1) as number;
|
const interval = this.getNodeParameter('interval', 1) as number;
|
||||||
|
|
||||||
if (interval <= 0) {
|
if (interval <= 0) {
|
||||||
|
@ -48,7 +41,7 @@ export class ClassNameReplace implements INodeType {
|
||||||
// Every time the emit function gets called a new workflow
|
// Every time the emit function gets called a new workflow
|
||||||
// executions gets started with the provided entries.
|
// executions gets started with the provided entries.
|
||||||
const entry = {
|
const entry = {
|
||||||
'exampleKey': 'exampleData'
|
exampleKey: 'exampleData',
|
||||||
};
|
};
|
||||||
this.emit([this.helpers.returnJsonArray([entry])]);
|
this.emit([this.helpers.returnJsonArray([entry])]);
|
||||||
};
|
};
|
||||||
|
@ -78,6 +71,5 @@ export class ClassNameReplace implements INodeType {
|
||||||
closeFunction,
|
closeFunction,
|
||||||
manualTriggerFunction,
|
manualTriggerFunction,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
import {
|
import { IWebhookFunctions } from 'n8n-core';
|
||||||
IWebhookFunctions,
|
|
||||||
} from 'n8n-core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IDataObject,
|
|
||||||
INodeTypeDescription,
|
|
||||||
INodeType,
|
|
||||||
IWebhookResponseData,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
|
import { IDataObject, INodeTypeDescription, INodeType, IWebhookResponseData } from 'n8n-workflow';
|
||||||
|
|
||||||
export class ClassNameReplace implements INodeType {
|
export class ClassNameReplace implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
@ -47,25 +39,18 @@ export class ClassNameReplace implements INodeType {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||||
|
|
||||||
// The data to return and so start the workflow with
|
// The data to return and so start the workflow with
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
returnData.push(
|
returnData.push({
|
||||||
{
|
|
||||||
headers: this.getHeaderData(),
|
headers: this.getHeaderData(),
|
||||||
params: this.getParamsData(),
|
params: this.getParamsData(),
|
||||||
query: this.getQueryData(),
|
query: this.getQueryData(),
|
||||||
body: this.getBodyData(),
|
body: this.getBodyData(),
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workflowData: [
|
workflowData: [this.helpers.returnJsonArray(returnData)],
|
||||||
this.helpers.returnJsonArray(returnData)
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ export class Discord implements INodeType {
|
||||||
// Waiting rating limit
|
// Waiting rating limit
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
// @ts-ignore
|
||||||
resolve();
|
resolve();
|
||||||
}, get(error, 'response.body.retry_after', 150));
|
}, get(error, 'response.body.retry_after', 150));
|
||||||
});
|
});
|
||||||
|
|
|
@ -552,6 +552,7 @@ export class Slack implements INodeType {
|
||||||
attachment.fields = attachment.fields.item;
|
attachment.fields = attachment.fields.item;
|
||||||
} else {
|
} else {
|
||||||
// If it does not have any items set remove it
|
// If it does not have any items set remove it
|
||||||
|
// @ts-ignore
|
||||||
delete attachment.fields;
|
delete attachment.fields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -786,6 +787,7 @@ export class Slack implements INodeType {
|
||||||
attachment.fields = attachment.fields.item;
|
attachment.fields = attachment.fields.item;
|
||||||
} else {
|
} else {
|
||||||
// If it does not have any items set remove it
|
// If it does not have any items set remove it
|
||||||
|
// @ts-ignore
|
||||||
delete attachment.fields;
|
delete attachment.fields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ export function connect(conn: snowflake.Connection) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
conn.connect((err, conn) => {
|
conn.connect((err, conn) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
|
// @ts-ignore
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -21,6 +22,7 @@ export function destroy(conn: snowflake.Connection) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
conn.destroy((err, conn) => {
|
conn.destroy((err, conn) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
|
// @ts-ignore
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
|
|
@ -167,6 +167,7 @@ export async function uploadAttachments(this: IExecuteFunctions, binaryPropertie
|
||||||
const { check_after_secs } = (response.processing_info as IDataObject);
|
const { check_after_secs } = (response.processing_info as IDataObject);
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// @ts-ignore
|
||||||
resolve();
|
resolve();
|
||||||
}, (check_after_secs as number) * 1000);
|
}, (check_after_secs as number) * 1000);
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run watch",
|
"dev": "npm run watch",
|
||||||
"build": "tsc && gulp",
|
"build": "tsc && gulp",
|
||||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/nodes-base/**/**.ts --write",
|
||||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||||
|
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||||
"nodelinter": "nodelinter",
|
"nodelinter": "nodelinter",
|
||||||
"watch": "tsc --watch",
|
"watch": "tsc --watch",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
|
@ -644,7 +645,7 @@
|
||||||
"nodelinter": "^0.1.9",
|
"nodelinter": "^0.1.9",
|
||||||
"ts-jest": "^26.3.0",
|
"ts-jest": "^26.3.0",
|
||||||
"tslint": "^6.1.2",
|
"tslint": "^6.1.2",
|
||||||
"typescript": "~3.9.7"
|
"typescript": "~4.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/lossless-json": "^1.0.0",
|
"@types/lossless-json": "^1.0.0",
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run watch",
|
"dev": "npm run watch",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/workflow/**/**.ts --write",
|
||||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
"lint": "cd ../.. && node_modules/eslint/bin/eslint.js packages/workflow",
|
||||||
|
"lintfix": "cd ../.. && node_modules/eslint/bin/eslint.js packages/workflow --fix",
|
||||||
"watch": "tsc --watch",
|
"watch": "tsc --watch",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
|
@ -31,10 +32,18 @@
|
||||||
"@types/lodash.get": "^4.4.6",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/node": "^14.14.40",
|
"@types/node": "^14.14.40",
|
||||||
"@types/xml2js": "^0.4.3",
|
"@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",
|
"jest": "^26.4.2",
|
||||||
|
"prettier": "^2.3.2",
|
||||||
"ts-jest": "^26.3.0",
|
"ts-jest": "^26.3.0",
|
||||||
"tslint": "^6.1.2",
|
"tslint": "^6.1.2",
|
||||||
"typescript": "~3.9.7"
|
"typescript": "~4.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash.get": "^4.4.2",
|
"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 {
|
import {
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
@ -9,28 +11,26 @@ import {
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowDataProxy,
|
WorkflowDataProxy,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from './';
|
} from '.';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as tmpl from 'riot-tmpl';
|
|
||||||
|
|
||||||
// Set it to use double curly brackets instead of single ones
|
// 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('{{ }}');
|
tmpl.brackets.set('{{ }}');
|
||||||
|
|
||||||
// Make sure that it does not always print an error when it could not resolve
|
// Make sure that it does not always print an error when it could not resolve
|
||||||
// a variable
|
// a variable
|
||||||
tmpl.tmpl.errorHandler = () => { };
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
tmpl.tmpl.errorHandler = () => {};
|
||||||
|
|
||||||
export class Expression {
|
export class Expression {
|
||||||
|
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
|
|
||||||
constructor(workflow: Workflow) {
|
constructor(workflow: Workflow) {
|
||||||
this.workflow = workflow;
|
this.workflow = workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an object to a string in a way to make it clear that
|
* Converts an object to a string in a way to make it clear that
|
||||||
* the value comes from an object
|
* the value comes from an object
|
||||||
|
@ -44,8 +44,6 @@ export class Expression {
|
||||||
return `[${typeName}: ${JSON.stringify(value)}]`;
|
return `[${typeName}: ${JSON.stringify(value)}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the paramter value. If it is an expression it will execute it and
|
* 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.
|
* return the result. For everything simply the supplied value will be returned.
|
||||||
|
@ -60,7 +58,19 @@ export class Expression {
|
||||||
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
||||||
* @memberof Workflow
|
* @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
|
// Check if it is an expression
|
||||||
if (typeof parameterValue !== 'string' || parameterValue.charAt(0) !== '=') {
|
if (typeof parameterValue !== 'string' || parameterValue.charAt(0) !== '=') {
|
||||||
// Is no expression so return value
|
// Is no expression so return value
|
||||||
|
@ -70,30 +80,44 @@ export class Expression {
|
||||||
// Is an expression
|
// Is an expression
|
||||||
|
|
||||||
// Remove the equal sign
|
// Remove the equal sign
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
parameterValue = parameterValue.substr(1);
|
parameterValue = parameterValue.substr(1);
|
||||||
|
|
||||||
// Generate a data proxy which allows to query workflow data
|
// 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();
|
const data = dataProxy.getDataProxy();
|
||||||
|
|
||||||
// Execute the expression
|
// Execute the expression
|
||||||
try {
|
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);
|
const returnValue = tmpl.tmpl(parameterValue, data);
|
||||||
if (typeof returnValue === 'function') {
|
if (typeof returnValue === 'function') {
|
||||||
throw new Error('Expression resolved to a function. Please add "()"');
|
throw new Error('Expression resolved to a function. Please add "()"');
|
||||||
} else if (returnValue !== null && typeof returnValue === 'object') {
|
} else if (returnValue !== null && typeof returnValue === 'object') {
|
||||||
if (returnObjectAsString === true) {
|
if (returnObjectAsString) {
|
||||||
return this.convertObjectValueToString(returnValue);
|
return this.convertObjectValueToString(returnValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return returnValue;
|
return returnValue;
|
||||||
} catch (e) {
|
} 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}`);
|
throw new Error(`Expression is not valid: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves value of parameter. But does not work for workflow-data.
|
* Resolves value of parameter. But does not work for workflow-data.
|
||||||
*
|
*
|
||||||
|
@ -103,7 +127,13 @@ export class Expression {
|
||||||
* @returns {(string | undefined)}
|
* @returns {(string | undefined)}
|
||||||
* @memberof Workflow
|
* @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) {
|
if (parameterValue === undefined) {
|
||||||
// Value is not set so return the default
|
// Value is not set so return the default
|
||||||
return defaultValue;
|
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.
|
* 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)}
|
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | undefined)}
|
||||||
* @memberof Workflow
|
* @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) {
|
if (parameterValue === undefined) {
|
||||||
// Value is not set so return the default
|
// Value is not set so return the default
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
@ -150,14 +199,34 @@ export class Expression {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve the "outer" main values
|
// 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
|
// 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
|
* 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
|
* 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[])}
|
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
||||||
* @memberof Workflow
|
* @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
|
// 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';
|
return typeof value === 'object';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function which resolves a parameter value depending on if it is simply or not
|
// 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)) {
|
if (isComplexParameter(value)) {
|
||||||
return this.getParameterValue(value, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, additionalKeys, returnObjectAsString, selfData);
|
return this.getParameterValue(
|
||||||
} else {
|
value,
|
||||||
return this.resolveSimpleParameterValue(value as NodeParameterValue, siblingParameters, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, additionalKeys, returnObjectAsString, selfData);
|
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
|
// Check if it value is a simple one that we can get it resolved directly
|
||||||
if (!isComplexParameter(parameterValue)) {
|
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
|
// The parameter value is complex so resolve depending on type
|
||||||
|
@ -198,28 +317,33 @@ export class Expression {
|
||||||
if (Array.isArray(parameterValue)) {
|
if (Array.isArray(parameterValue)) {
|
||||||
// Data is an array
|
// Data is an array
|
||||||
const returnData = [];
|
const returnData = [];
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const item of parameterValue) {
|
for (const item of parameterValue) {
|
||||||
returnData.push(resolveParameterValue(item, {}));
|
returnData.push(resolveParameterValue(item, {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnObjectAsString === true && typeof returnData === 'object') {
|
if (returnObjectAsString && typeof returnData === 'object') {
|
||||||
return this.convertObjectValueToString(returnData);
|
return this.convertObjectValueToString(returnData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnData as NodeParameterValue[] | INodeParameters[];
|
return returnData as NodeParameterValue[] | INodeParameters[];
|
||||||
} else if (parameterValue === null || parameterValue === undefined) {
|
}
|
||||||
|
if (parameterValue === null || parameterValue === undefined) {
|
||||||
return parameterValue;
|
return parameterValue;
|
||||||
} else {
|
}
|
||||||
// Data is an object
|
// Data is an object
|
||||||
const returnData: INodeParameters = {};
|
const returnData: INodeParameters = {};
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const key of Object.keys(parameterValue)) {
|
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 this.convertObjectValueToString(returnData);
|
||||||
}
|
}
|
||||||
return 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 { Workflow } from './Workflow';
|
||||||
import { WorkflowHooks } from './WorkflowHooks';
|
import { WorkflowHooks } from './WorkflowHooks';
|
||||||
import { WorkflowOperationError } from './WorkflowErrors';
|
import { WorkflowOperationError } from './WorkflowErrors';
|
||||||
import { NodeApiError, NodeOperationError } from './NodeErrors';
|
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 {
|
export interface IBinaryData {
|
||||||
[key: string]: string | undefined;
|
[key: string]: string | undefined;
|
||||||
|
@ -43,8 +55,11 @@ export interface IGetCredentials {
|
||||||
|
|
||||||
export abstract class ICredentials {
|
export abstract class ICredentials {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
type: string;
|
type: string;
|
||||||
|
|
||||||
data: string | undefined;
|
data: string | undefined;
|
||||||
|
|
||||||
nodesAccess: ICredentialNodeAccess[];
|
nodesAccess: ICredentialNodeAccess[];
|
||||||
|
|
||||||
constructor(name: string, type: string, nodesAccess: ICredentialNodeAccess[], data?: string) {
|
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 getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject;
|
||||||
|
|
||||||
abstract getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation;
|
abstract getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation;
|
||||||
|
|
||||||
abstract getDataToSave(): ICredentialsEncrypted;
|
abstract getDataToSave(): ICredentialsEncrypted;
|
||||||
|
|
||||||
abstract hasNodeAccess(nodeType: string): boolean;
|
abstract hasNodeAccess(nodeType: string): boolean;
|
||||||
|
|
||||||
abstract setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void;
|
abstract setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void;
|
||||||
|
|
||||||
abstract setDataKey(key: string, data: CredentialInformation, 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 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 {
|
export interface ICredentialType {
|
||||||
|
@ -116,7 +148,7 @@ export interface ICredentialType {
|
||||||
|
|
||||||
export interface ICredentialTypes {
|
export interface ICredentialTypes {
|
||||||
credentialTypes?: {
|
credentialTypes?: {
|
||||||
[key: string]: ICredentialType
|
[key: string]: ICredentialType;
|
||||||
};
|
};
|
||||||
init(credentialTypes?: { [key: string]: ICredentialType }): Promise<void>;
|
init(credentialTypes?: { [key: string]: ICredentialType }): Promise<void>;
|
||||||
getAll(): ICredentialType[];
|
getAll(): ICredentialType[];
|
||||||
|
@ -133,7 +165,6 @@ export interface ICredentialData {
|
||||||
// The encrypted credentials which the nodes can access
|
// The encrypted credentials which the nodes can access
|
||||||
export type CredentialInformation = string | number | boolean | IDataObject;
|
export type CredentialInformation = string | number | boolean | IDataObject;
|
||||||
|
|
||||||
|
|
||||||
// The encrypted credentials which the nodes can access
|
// The encrypted credentials which the nodes can access
|
||||||
export interface ICredentialDataDecryptedObject {
|
export interface ICredentialDataDecryptedObject {
|
||||||
[key: string]: CredentialInformation;
|
[key: string]: CredentialInformation;
|
||||||
|
@ -159,92 +190,150 @@ export interface IDataObject {
|
||||||
[key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[];
|
[key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IGetExecutePollFunctions {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
export interface IExecuteData {
|
||||||
data: ITaskDataConnections;
|
data: ITaskDataConnections;
|
||||||
node: INode;
|
node: INode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IContextObject = {
|
export type IContextObject = {
|
||||||
[key: string]: any; // tslint:disable-line:no-any
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface IExecuteContextData {
|
export interface IExecuteContextData {
|
||||||
// Keys are: "flow" | "node:<NODE_NAME>"
|
// Keys are: "flow" | "node:<NODE_NAME>"
|
||||||
[key: string]: IContextObject;
|
[key: string]: IContextObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IExecuteFunctions {
|
export interface IExecuteFunctions {
|
||||||
continueOnFail(): boolean;
|
continueOnFail(): boolean;
|
||||||
evaluateExpression(expression: string, itemIndex: number): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
evaluateExpression(
|
||||||
executeWorkflow(workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[]): Promise<any>; // tslint:disable-line:no-any
|
expression: string,
|
||||||
|
itemIndex: number,
|
||||||
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[];
|
||||||
|
executeWorkflow(
|
||||||
|
workflowInfo: IExecuteWorkflowInfo,
|
||||||
|
inputData?: INodeExecutionData[],
|
||||||
|
): Promise<any>;
|
||||||
getContext(type: string): IContextObject;
|
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[];
|
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[];
|
||||||
getMode(): WorkflowExecuteMode;
|
getMode(): WorkflowExecuteMode;
|
||||||
getNode(): INode;
|
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;
|
getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
prepareOutputData(
|
||||||
|
outputData: INodeExecutionData[],
|
||||||
|
outputIndex?: number,
|
||||||
|
): Promise<INodeExecutionData[][]>;
|
||||||
putExecutionToWait(waitTill: Date): Promise<void>;
|
putExecutionToWait(waitTill: Date): Promise<void>;
|
||||||
sendMessageToUI(message: any): void; // tslint:disable-line:no-any
|
sendMessageToUI(message: any): void; // tslint:disable-line:no-any
|
||||||
helpers: {
|
helpers: {
|
||||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
[key: string]: (...args: any[]) => any;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IExecuteSingleFunctions {
|
export interface IExecuteSingleFunctions {
|
||||||
continueOnFail(): boolean;
|
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;
|
getContext(type: string): IContextObject;
|
||||||
getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined>;
|
getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined>;
|
||||||
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData;
|
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData;
|
||||||
getMode(): WorkflowExecuteMode;
|
getMode(): WorkflowExecuteMode;
|
||||||
getNode(): INode;
|
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;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
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 {
|
export interface ILoadOptionsFunctions {
|
||||||
getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined>;
|
getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined>;
|
||||||
getNode(): INode;
|
getNode(): INode;
|
||||||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
getNodeParameter(
|
||||||
getCurrentNodeParameter(parameterName: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined;
|
parameterName: string,
|
||||||
|
fallbackValue?: any,
|
||||||
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||||
|
getCurrentNodeParameter(
|
||||||
|
parameterName: string,
|
||||||
|
):
|
||||||
|
| NodeParameterValue
|
||||||
|
| INodeParameters
|
||||||
|
| NodeParameterValue[]
|
||||||
|
| INodeParameters[]
|
||||||
|
| object
|
||||||
|
| undefined;
|
||||||
getCurrentNodeParameters(): INodeParameters | undefined;
|
getCurrentNodeParameters(): INodeParameters | undefined;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
helpers: {
|
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;
|
getActivationMode(): WorkflowActivateMode;
|
||||||
getNode(): INode;
|
getNode(): INode;
|
||||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
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;
|
getTimezone(): string;
|
||||||
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
||||||
getWebhookName(): string;
|
getWebhookName(): string;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
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;
|
getMode(): WorkflowExecuteMode;
|
||||||
getActivationMode(): WorkflowActivateMode;
|
getActivationMode(): WorkflowActivateMode;
|
||||||
getNode(): INode;
|
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;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
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;
|
getMode(): WorkflowExecuteMode;
|
||||||
getActivationMode(): WorkflowActivateMode;
|
getActivationMode(): WorkflowActivateMode;
|
||||||
getNode(): INode;
|
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;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
helpers: {
|
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;
|
getHeaderData(): object;
|
||||||
getMode(): WorkflowExecuteMode;
|
getMode(): WorkflowExecuteMode;
|
||||||
getNode(): INode;
|
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;
|
getNodeWebhookUrl: (name: string) => string | undefined;
|
||||||
getParamsData(): object;
|
getParamsData(): object;
|
||||||
getQueryData(): object;
|
getQueryData(): object;
|
||||||
|
@ -331,9 +443,12 @@ export interface IWebhookFunctions {
|
||||||
getWebhookName(): string;
|
getWebhookName(): string;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
prepareOutputData(
|
||||||
|
outputData: INodeExecutionData[],
|
||||||
|
outputIndex?: number,
|
||||||
|
): Promise<INodeExecutionData[][]>;
|
||||||
helpers: {
|
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;
|
webhookId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface INodes {
|
export interface INodes {
|
||||||
[key: string]: INode;
|
[key: string]: INode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IObservableObject {
|
export interface IObservableObject {
|
||||||
[key: string]: any; // tslint:disable-line:no-any
|
[key: string]: any;
|
||||||
__dataChanged: boolean;
|
__dataChanged: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IBinaryKeyData {
|
export interface IBinaryKeyData {
|
||||||
[key: string]: IBinaryData;
|
[key: string]: IBinaryData;
|
||||||
}
|
}
|
||||||
|
@ -385,7 +497,6 @@ export interface INodeExecutionData {
|
||||||
binary?: IBinaryKeyData;
|
binary?: IBinaryKeyData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface INodeExecuteFunctions {
|
export interface INodeExecuteFunctions {
|
||||||
getExecutePollFunctions: IGetExecutePollFunctions;
|
getExecutePollFunctions: IGetExecutePollFunctions;
|
||||||
getExecuteTriggerFunctions: IGetExecuteTriggerFunctions;
|
getExecuteTriggerFunctions: IGetExecuteTriggerFunctions;
|
||||||
|
@ -395,7 +506,6 @@ export interface INodeExecuteFunctions {
|
||||||
getExecuteWebhookFunctions: IGetExecuteWebhookFunctions;
|
getExecuteWebhookFunctions: IGetExecuteWebhookFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The values a node property can have
|
// The values a node property can have
|
||||||
export type NodeParameterValue = string | number | boolean | undefined | null;
|
export type NodeParameterValue = string | number | boolean | undefined | null;
|
||||||
|
|
||||||
|
@ -404,7 +514,19 @@ export interface INodeParameters {
|
||||||
[key: string]: NodeParameterValue | INodeParameters | NodeParameterValue[] | 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';
|
export type EditorTypes = 'code';
|
||||||
|
|
||||||
|
@ -435,7 +557,6 @@ export interface IDisplayOptions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface INodeProperties {
|
export interface INodeProperties {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -492,7 +613,7 @@ export interface INodeType {
|
||||||
methods?: {
|
methods?: {
|
||||||
loadOptions?: {
|
loadOptions?: {
|
||||||
[key: string]: (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>;
|
[key: string]: (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>;
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
webhookMethods?: {
|
webhookMethods?: {
|
||||||
[key: string]: IWebhookSetupMethods;
|
[key: string]: IWebhookSetupMethods;
|
||||||
|
@ -501,7 +622,6 @@ export interface INodeType {
|
||||||
|
|
||||||
export type WebhookSetupMethodNames = 'checkExists' | 'create' | 'delete';
|
export type WebhookSetupMethodNames = 'checkExists' | 'create' | 'delete';
|
||||||
|
|
||||||
|
|
||||||
export interface IWebhookSetupMethods {
|
export interface IWebhookSetupMethods {
|
||||||
[key: string]: ((this: IHookFunctions) => Promise<boolean>) | undefined;
|
[key: string]: ((this: IHookFunctions) => Promise<boolean>) | undefined;
|
||||||
checkExists?: (this: IHookFunctions) => Promise<boolean>;
|
checkExists?: (this: IHookFunctions) => Promise<boolean>;
|
||||||
|
@ -509,7 +629,6 @@ export interface IWebhookSetupMethods {
|
||||||
delete?: (this: IHookFunctions) => Promise<boolean>;
|
delete?: (this: IHookFunctions) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface INodeCredentialDescription {
|
export interface INodeCredentialDescription {
|
||||||
name: string;
|
name: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
@ -596,17 +715,17 @@ export interface IWebhookDescription {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowDataProxyData {
|
export interface IWorkflowDataProxyData {
|
||||||
$binary: any; // tslint:disable-line:no-any
|
$binary: any;
|
||||||
$data: any; // tslint:disable-line:no-any
|
$data: any;
|
||||||
$env: any; // tslint:disable-line:no-any
|
$env: any;
|
||||||
$evaluateExpression: any; // tslint:disable-line:no-any
|
$evaluateExpression: any;
|
||||||
$item: any; // tslint:disable-line:no-any
|
$item: any;
|
||||||
$items: any; // tslint:disable-line:no-any
|
$items: any;
|
||||||
$json: any; // tslint:disable-line:no-any
|
$json: any;
|
||||||
$node: any; // tslint:disable-line:no-any
|
$node: any;
|
||||||
$parameter: any; // tslint:disable-line:no-any
|
$parameter: any;
|
||||||
$position: any; // tslint:disable-line:no-any
|
$position: any;
|
||||||
$workflow: any; // tslint:disable-line:no-any
|
$workflow: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowDataProxyAdditionalKeys {
|
export interface IWorkflowDataProxyAdditionalKeys {
|
||||||
|
@ -623,7 +742,7 @@ export type WebhookHttpMethod = 'GET' | 'POST' | 'HEAD' | 'OPTIONS';
|
||||||
|
|
||||||
export interface IWebhookResponseData {
|
export interface IWebhookResponseData {
|
||||||
workflowData?: INodeExecutionData[][];
|
workflowData?: INodeExecutionData[][];
|
||||||
webhookResponse?: any; // tslint:disable-line:no-any
|
webhookResponse?: any;
|
||||||
noWebhookResponse?: boolean;
|
noWebhookResponse?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,7 +756,6 @@ export interface INodeTypes {
|
||||||
getByName(nodeType: string): INodeType | undefined;
|
getByName(nodeType: string): INodeType | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface INodeTypeData {
|
export interface INodeTypeData {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
type: INodeType;
|
type: INodeType;
|
||||||
|
@ -654,7 +772,6 @@ export interface IRun {
|
||||||
stoppedAt?: Date;
|
stoppedAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Contains all the data which is needed to execute a workflow and so also to
|
// Contains all the data which is needed to execute a workflow and so also to
|
||||||
// start restart it again after it did fail.
|
// start restart it again after it did fail.
|
||||||
// The RunData, ExecuteData and WaitForExecution contain often the same data.
|
// The RunData, ExecuteData and WaitForExecution contain often the same data.
|
||||||
|
@ -676,13 +793,11 @@ export interface IRunExecutionData {
|
||||||
waitTill?: Date;
|
waitTill?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IRunData {
|
export interface IRunData {
|
||||||
// node-name: result-data
|
// node-name: result-data
|
||||||
[key: string]: ITaskData[];
|
[key: string]: ITaskData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The data that gets returned when a node runs
|
// The data that gets returned when a node runs
|
||||||
export interface ITaskData {
|
export interface ITaskData {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
|
@ -691,7 +806,6 @@ export interface ITaskData {
|
||||||
error?: ExecutionError;
|
error?: ExecutionError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The data for al the different kind of connectons (like main) and all the indexes
|
// The data for al the different kind of connectons (like main) and all the indexes
|
||||||
export interface ITaskDataConnections {
|
export interface ITaskDataConnections {
|
||||||
// Key for each input type and because there can be multiple inputs of the same type it is an array
|
// 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>;
|
[key: string]: Array<INodeExecutionData[] | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Keeps data while workflow gets executed and allows when provided to restart execution
|
// Keeps data while workflow gets executed and allows when provided to restart execution
|
||||||
export interface IWaitingForExecution {
|
export interface IWaitingForExecution {
|
||||||
// Node name
|
// Node name
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
// Run index
|
// Run index
|
||||||
[key: number]: ITaskDataConnections
|
[key: number]: ITaskDataConnections;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IWorkflowBase {
|
export interface IWorkflowBase {
|
||||||
id?: number | string | any; // tslint:disable-line:no-any
|
id?: number | string | any;
|
||||||
name: string;
|
name: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
@ -732,26 +843,34 @@ export interface IWorkflowCredentials {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IWorkflowExecuteHooks {
|
export interface IWorkflowExecuteHooks {
|
||||||
[key: string]: Array<((...args: any[]) => Promise<void>)> | undefined; // tslint:disable-line:no-any
|
[key: string]: Array<(...args: any[]) => Promise<void>> | undefined;
|
||||||
nodeExecuteAfter?: Array<((nodeName: string, data: ITaskData, executionData: IRunExecutionData) => Promise<void>)>;
|
nodeExecuteAfter?: Array<
|
||||||
nodeExecuteBefore?: Array<((nodeName: string) => Promise<void>)>;
|
(nodeName: string, data: ITaskData, executionData: IRunExecutionData) => Promise<void>
|
||||||
workflowExecuteAfter?: Array<((data: IRun, newStaticData: IDataObject) => Promise<void>)>;
|
>;
|
||||||
workflowExecuteBefore?: Array<((workflow: Workflow, data: 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 {
|
export interface IWorkflowExecuteAdditionalData {
|
||||||
credentialsHelper: ICredentialsHelper;
|
credentialsHelper: ICredentialsHelper;
|
||||||
encryptionKey: string;
|
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;
|
// hooks?: IWorkflowExecuteHooks;
|
||||||
executionId?: string;
|
executionId?: string;
|
||||||
hooks?: WorkflowHooks;
|
hooks?: WorkflowHooks;
|
||||||
httpResponse?: express.Response;
|
httpResponse?: express.Response;
|
||||||
httpRequest?: express.Request;
|
httpRequest?: express.Request;
|
||||||
restApiUrl: string;
|
restApiUrl: string;
|
||||||
sendMessageToUI?: (source: string, message: any) => void; // tslint:disable-line:no-any
|
sendMessageToUI?: (source: string, message: any) => void;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
webhookBaseUrl: string;
|
webhookBaseUrl: string;
|
||||||
webhookWaitingBaseUrl: string;
|
webhookWaitingBaseUrl: string;
|
||||||
|
@ -760,7 +879,15 @@ export interface IWorkflowExecuteAdditionalData {
|
||||||
executionTimeoutTimestamp?: number;
|
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 type WorkflowActivateMode = 'init' | 'create' | 'update' | 'activate' | 'manual';
|
||||||
|
|
||||||
export interface IWorkflowHooksOptionalParameters {
|
export interface IWorkflowHooksOptionalParameters {
|
||||||
|
@ -790,7 +917,7 @@ export interface IStatusCodeMessages {
|
||||||
|
|
||||||
export type CodexData = {
|
export type CodexData = {
|
||||||
categories?: string[];
|
categories?: string[];
|
||||||
subcategories?: {[category: string]: string[]};
|
subcategories?: { [category: string]: string[] };
|
||||||
alias?: string[];
|
alias?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import {
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
ILogger,
|
// eslint-disable-next-line import/no-cycle
|
||||||
LogTypes,
|
import { ILogger, LogTypes } from './Interfaces';
|
||||||
} from './Interfaces';
|
|
||||||
|
|
||||||
|
|
||||||
let logger: ILogger | undefined;
|
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';
|
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.
|
* 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.
|
* 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.
|
* 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 {
|
abstract class NodeError extends Error {
|
||||||
description: string | null | undefined;
|
description: string | null | undefined;
|
||||||
|
|
||||||
cause: Error | JsonObject;
|
cause: Error | JsonObject;
|
||||||
|
|
||||||
node: INode;
|
node: INode;
|
||||||
|
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
|
||||||
constructor(node: INode, error: Error | JsonObject) {
|
constructor(node: INode, error: Error | JsonObject) {
|
||||||
|
@ -95,13 +113,17 @@ abstract class NodeError extends Error {
|
||||||
potentialKeys: string[],
|
potentialKeys: string[],
|
||||||
traversalKeys: string[] = [],
|
traversalKeys: string[] = [],
|
||||||
): string | null {
|
): string | null {
|
||||||
for(const key of potentialKeys) {
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const key of potentialKeys) {
|
||||||
if (error[key]) {
|
if (error[key]) {
|
||||||
if (typeof error[key] === 'string') return error[key] as string;
|
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 (typeof error[key] === 'number') return error[key]!.toString();
|
||||||
if (Array.isArray(error[key])) {
|
if (Array.isArray(error[key])) {
|
||||||
// @ts-ignore
|
// @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 === 'string') return error;
|
||||||
if (typeof error === 'number') return error.toString();
|
if (typeof error === 'number') return error.toString();
|
||||||
if (this.isTraversableObject(error)) {
|
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) {
|
for (const key of traversalKeys) {
|
||||||
if (this.isTraversableObject(error[key])) {
|
if (this.isTraversableObject(error[key])) {
|
||||||
const property = this.findProperty(error[key] as JsonObject, potentialKeys, traversalKeys);
|
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.
|
* 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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return value && typeof value === 'object' && !Array.isArray(value) && !!Object.keys(value).length;
|
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);
|
seen.add(obj);
|
||||||
Object.entries(obj).forEach(([key, value]) => {
|
Object.entries(obj).forEach(([key, value]) => {
|
||||||
if (this.isTraversableObject(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;
|
return;
|
||||||
}
|
}
|
||||||
if (Array.isArray(value)) {
|
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.
|
* Class for instantiating an operational error, e.g. an invalid credentials error.
|
||||||
*/
|
*/
|
||||||
export class NodeOperationError extends NodeError {
|
export class NodeOperationError extends NodeError {
|
||||||
|
|
||||||
constructor(node: INode, error: Error | string) {
|
constructor(node: INode, error: Error | string) {
|
||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
error = new Error(error);
|
error = new Error(error);
|
||||||
|
@ -211,10 +239,16 @@ export class NodeApiError extends NodeError {
|
||||||
constructor(
|
constructor(
|
||||||
node: INode,
|
node: INode,
|
||||||
error: JsonObject,
|
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);
|
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);
|
this.removeCircularRefs(error.error as JsonObject);
|
||||||
}
|
}
|
||||||
if (message) {
|
if (message) {
|
||||||
|
@ -236,11 +270,17 @@ export class NodeApiError extends NodeError {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDescriptionFromXml(xml: string) {
|
private setDescriptionFromXml(xml: string) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
parseString(xml, { explicitArray: false }, (_, result) => {
|
parseString(xml, { explicitArray: false }, (_, result) => {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
const topLevelKey = Object.keys(result)[0];
|
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 {
|
import {
|
||||||
IContextObject,
|
IContextObject,
|
||||||
INode,
|
INode,
|
||||||
|
@ -17,13 +31,7 @@ import {
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
|
|
||||||
import {
|
import { Workflow } from './Workflow';
|
||||||
Workflow
|
|
||||||
} from './Workflow';
|
|
||||||
|
|
||||||
import { get, isEqual } from 'lodash';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets special parameters which should be added to nodeTypes depending
|
* Gets special parameters which should be added to nodeTypes depending
|
||||||
|
@ -99,12 +107,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
},
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
mode: [
|
mode: ['custom', 'everyHour', 'everyMinute', 'everyX'],
|
||||||
'custom',
|
|
||||||
'everyHour',
|
|
||||||
'everyMinute',
|
|
||||||
'everyX',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: 14,
|
default: 14,
|
||||||
|
@ -120,11 +123,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
},
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
mode: [
|
mode: ['custom', 'everyMinute', 'everyX'],
|
||||||
'custom',
|
|
||||||
'everyMinute',
|
|
||||||
'everyX',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: 0,
|
default: 0,
|
||||||
|
@ -136,9 +135,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: [
|
mode: ['everyMonth'],
|
||||||
'everyMonth',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
|
@ -154,9 +151,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
type: 'options',
|
type: 'options',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: [
|
mode: ['everyWeek'],
|
||||||
'everyWeek',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
|
@ -198,13 +193,12 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: [
|
mode: ['custom'],
|
||||||
'custom',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '* * * * * *',
|
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',
|
displayName: 'Value',
|
||||||
|
@ -216,9 +210,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
},
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: [
|
mode: ['everyX'],
|
||||||
'everyX',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: 2,
|
default: 2,
|
||||||
|
@ -230,9 +222,7 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
type: 'options',
|
type: 'options',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: [
|
mode: ['everyX'],
|
||||||
'everyX',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
|
@ -258,7 +248,6 @@ export function getSpecialNodeParameters(nodeType: INodeType) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the parameter should be displayed or not
|
* 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
|
* @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function displayParameter(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, nodeValuesRoot?: INodeParameters) {
|
export function displayParameter(
|
||||||
|
nodeValues: INodeParameters,
|
||||||
|
parameter: INodeProperties | INodeCredentialDescription,
|
||||||
|
nodeValuesRoot?: INodeParameters,
|
||||||
|
) {
|
||||||
if (!parameter.displayOptions) {
|
if (!parameter.displayOptions) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -277,7 +270,8 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
||||||
nodeValuesRoot = nodeValuesRoot || nodeValues;
|
nodeValuesRoot = nodeValuesRoot || nodeValues;
|
||||||
|
|
||||||
let value;
|
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) {
|
if (parameter.displayOptions.show) {
|
||||||
// All the defined rules have to match to display parameter
|
// All the defined rules have to match to display parameter
|
||||||
for (const propertyName of Object.keys(parameter.displayOptions.show)) {
|
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);
|
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;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,7 +321,10 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
||||||
values.push.apply(values, value);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +333,6 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the given parameter should be displayed or not considering the path
|
* Returns if the given parameter should be displayed or not considering the path
|
||||||
* to the properties
|
* to the properties
|
||||||
|
@ -345,28 +344,25 @@ export function displayParameter(nodeValues: INodeParameters, parameter: INodePr
|
||||||
* @param {string} path The path to the property
|
* @param {string} path The path to the property
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function displayParameterPath(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, path: string) {
|
export function displayParameterPath(
|
||||||
|
nodeValues: INodeParameters,
|
||||||
|
parameter: INodeProperties | INodeCredentialDescription,
|
||||||
|
path: string,
|
||||||
|
) {
|
||||||
let resolvedNodeValues = nodeValues;
|
let resolvedNodeValues = nodeValues;
|
||||||
if (path !== '') {
|
if (path !== '') {
|
||||||
resolvedNodeValues = get(
|
resolvedNodeValues = get(nodeValues, path) as INodeParameters;
|
||||||
nodeValues,
|
|
||||||
path,
|
|
||||||
) as INodeParameters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the root parameter data
|
// Get the root parameter data
|
||||||
let nodeValuesRoot = nodeValues;
|
let nodeValuesRoot = nodeValues;
|
||||||
if (path && path.split('.').indexOf('parameters') === 0) {
|
if (path && path.split('.').indexOf('parameters') === 0) {
|
||||||
nodeValuesRoot = get(
|
nodeValuesRoot = get(nodeValues, 'parameters') as INodeParameters;
|
||||||
nodeValues,
|
|
||||||
'parameters',
|
|
||||||
) as INodeParameters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return displayParameter(resolvedNodeValues, parameter, nodeValuesRoot);
|
return displayParameter(resolvedNodeValues, parameter, nodeValuesRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the context data
|
* 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
|
* @param {INode} [node] If type "node" is set the node to return the context of has to be supplied
|
||||||
* @returns {IContextObject}
|
* @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) {
|
if (runExecutionData.executionData === undefined) {
|
||||||
// TODO: Should not happen leave it for test now
|
// TODO: Should not happen leave it for test now
|
||||||
throw new Error('The "executionData" is not initialized!');
|
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) {
|
if (runExecutionData.executionData.contextData[key] === undefined) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
runExecutionData.executionData.contextData[key] = {};
|
runExecutionData.executionData.contextData[key] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return runExecutionData.executionData.contextData[key];
|
return runExecutionData.executionData.contextData[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns which parameters are dependent on which
|
* Returns which parameters are dependent on which
|
||||||
*
|
*
|
||||||
|
@ -409,7 +409,9 @@ export function getContext(runExecutionData: IRunExecutionData, type: string, no
|
||||||
* @param {INodeProperties[]} nodePropertiesArray
|
* @param {INodeProperties[]} nodePropertiesArray
|
||||||
* @returns {IParameterDependencies}
|
* @returns {IParameterDependencies}
|
||||||
*/
|
*/
|
||||||
export function getParamterDependencies(nodePropertiesArray: INodeProperties[]): IParameterDependencies {
|
export function getParamterDependencies(
|
||||||
|
nodePropertiesArray: INodeProperties[],
|
||||||
|
): IParameterDependencies {
|
||||||
const dependencies: IParameterDependencies = {};
|
const dependencies: IParameterDependencies = {};
|
||||||
|
|
||||||
let displayRule: string;
|
let displayRule: string;
|
||||||
|
@ -436,7 +438,6 @@ export function getParamterDependencies(nodePropertiesArray: INodeProperties[]):
|
||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns in which order the parameters should be resolved
|
* Returns in which order the parameters should be resolved
|
||||||
* to have the parameters available they depend on
|
* to have the parameters available they depend on
|
||||||
|
@ -446,7 +447,10 @@ export function getParamterDependencies(nodePropertiesArray: INodeProperties[]):
|
||||||
* @param {IParameterDependencies} parameterDependencies
|
* @param {IParameterDependencies} parameterDependencies
|
||||||
* @returns {number[]}
|
* @returns {number[]}
|
||||||
*/
|
*/
|
||||||
export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[], parameterDependencies: IParameterDependencies): number[] {
|
export function getParamterResolveOrder(
|
||||||
|
nodePropertiesArray: INodeProperties[],
|
||||||
|
parameterDependencies: IParameterDependencies,
|
||||||
|
): number[] {
|
||||||
const executionOrder: number[] = [];
|
const executionOrder: number[] = [];
|
||||||
const indexToResolve = Array.from({ length: nodePropertiesArray.length }, (v, k) => k);
|
const indexToResolve = Array.from({ length: nodePropertiesArray.length }, (v, k) => k);
|
||||||
const resolvedParamters: string[] = [];
|
const resolvedParamters: string[] = [];
|
||||||
|
@ -457,7 +461,7 @@ export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[],
|
||||||
let lastIndexLength = indexToResolve.length;
|
let lastIndexLength = indexToResolve.length;
|
||||||
let lastIndexReduction = -1;
|
let lastIndexReduction = -1;
|
||||||
|
|
||||||
let iterations = 0 ;
|
let iterations = 0;
|
||||||
|
|
||||||
while (indexToResolve.length !== 0) {
|
while (indexToResolve.length !== 0) {
|
||||||
iterations += 1;
|
iterations += 1;
|
||||||
|
@ -495,7 +499,9 @@ export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[],
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iterations > lastIndexReduction + nodePropertiesArray.length) {
|
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;
|
lastIndexLength = indexToResolve.length;
|
||||||
}
|
}
|
||||||
|
@ -503,7 +509,6 @@ export function getParameterResolveOrder(nodePropertiesArray: INodeProperties[],
|
||||||
return executionOrder;
|
return executionOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the node parameter values. Depending on the settings it either just returns the none
|
* Returns the node parameter values. Depending on the settings it either just returns the none
|
||||||
* default values or it applies all the default values.
|
* 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
|
* @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
|
||||||
* @returns {(INodeParameters | null)}
|
* @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) {
|
if (parameterDependencies === undefined) {
|
||||||
parameterDependencies = getParamterDependencies(nodePropertiesArray);
|
parameterDependencies = getParamterDependencies(nodePropertiesArray);
|
||||||
}
|
}
|
||||||
|
@ -541,27 +556,43 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
||||||
const nodeParametersFull: INodeParameters = {};
|
const nodeParametersFull: INodeParameters = {};
|
||||||
|
|
||||||
let nodeValuesDisplayCheck = nodeParametersFull;
|
let nodeValuesDisplayCheck = nodeParametersFull;
|
||||||
if (dataIsResolved !== true && returnNoneDisplayed === false) {
|
if (!dataIsResolved && !returnNoneDisplayed) {
|
||||||
nodeValuesDisplayCheck = getNodeParameters(nodePropertiesArray, nodeValues, true, true, true, true, nodeValuesRoot, parentType, parameterDependencies) as INodeParameters;
|
nodeValuesDisplayCheck = getNodeParameters(
|
||||||
|
nodePropertiesArray,
|
||||||
|
nodeValues,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
nodeValuesRoot,
|
||||||
|
parentType,
|
||||||
|
parameterDependencies,
|
||||||
|
) as INodeParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeValuesRoot = nodeValuesRoot || nodeValuesDisplayCheck;
|
nodeValuesRoot = nodeValuesRoot || nodeValuesDisplayCheck;
|
||||||
|
|
||||||
// Go through the parameters in order of their dependencies
|
// Go through the parameters in order of their dependencies
|
||||||
const parameterItterationOrderIndex = getParameterResolveOrder(nodePropertiesArray, parameterDependencies);
|
const parameterItterationOrderIndex = getParamterResolveOrder(
|
||||||
|
nodePropertiesArray,
|
||||||
|
parameterDependencies,
|
||||||
|
);
|
||||||
|
|
||||||
for (const parameterIndex of parameterItterationOrderIndex) {
|
for (const parameterIndex of parameterItterationOrderIndex) {
|
||||||
const nodeProperties = nodePropertiesArray[parameterIndex];
|
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
|
// The value is not defined so go to the next
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnNoneDisplayed === false && !displayParameter(nodeValuesDisplayCheck, nodeProperties, nodeValuesRoot)) {
|
if (
|
||||||
if (returnNoneDisplayed === false) {
|
!returnNoneDisplayed &&
|
||||||
continue;
|
!displayParameter(nodeValuesDisplayCheck, nodeProperties, nodeValuesRoot)
|
||||||
}
|
) {
|
||||||
if (returnDefaults === false) {
|
if (!returnNoneDisplayed || !returnDefaults) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -575,19 +606,27 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnDefaults === true) {
|
if (returnDefaults) {
|
||||||
// Set also when it has the default value
|
// Set also when it has the default value
|
||||||
if (['boolean', 'number', 'options'].includes(nodeProperties.type)) {
|
if (['boolean', 'number', 'options'].includes(nodeProperties.type)) {
|
||||||
// Boolean, numbers and options are special as false and 0 are valid values
|
// Boolean, numbers and options are special as false and 0 are valid values
|
||||||
// and should not be replaced with default value
|
// 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 {
|
} else {
|
||||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name] || nodeProperties.default;
|
nodeParameters[nodeProperties.name] =
|
||||||
|
nodeValues[nodeProperties.name] || nodeProperties.default;
|
||||||
}
|
}
|
||||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||||
} else if ((nodeValues[nodeProperties.name] !== nodeProperties.default && typeof nodeValues[nodeProperties.name] !== 'object') ||
|
} else if (
|
||||||
(typeof nodeValues[nodeProperties.name] === 'object' && !isEqual(nodeValues[nodeProperties.name], nodeProperties.default)) ||
|
(nodeValues[nodeProperties.name] !== nodeProperties.default &&
|
||||||
(nodeValues[nodeProperties.name] !== undefined && parentType === 'collection')) {
|
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
|
// Set only if it is different to the default value
|
||||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
||||||
nodeParametersFull[nodeProperties.name] = nodeParameters[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.
|
// It is only supposed to resolve the simple types. So continue.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -605,16 +644,21 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
||||||
if (nodeProperties.type === 'collection') {
|
if (nodeProperties.type === 'collection') {
|
||||||
// Is 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
|
// Multiple can be set so will be an array
|
||||||
|
|
||||||
// Return directly the values like they are
|
// Return directly the values like they are
|
||||||
if (nodeValues[nodeProperties.name] !== undefined) {
|
if (nodeValues[nodeProperties.name] !== undefined) {
|
||||||
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
|
||||||
} else if (returnDefaults === true) {
|
} else if (returnDefaults) {
|
||||||
// Does not have values defined but defaults should be returned
|
// Does not have values defined but defaults should be returned
|
||||||
if (Array.isArray(nodeProperties.default)) {
|
if (Array.isArray(nodeProperties.default)) {
|
||||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
nodeParameters[nodeProperties.name] = JSON.parse(
|
||||||
|
JSON.stringify(nodeProperties.default),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// As it is probably wrong for many nodes, do we keep on returning an empty array if
|
// 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
|
// 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];
|
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||||
} else {
|
} else if (nodeValues[nodeProperties.name] !== undefined) {
|
||||||
if (nodeValues[nodeProperties.name] !== undefined) {
|
|
||||||
// Has values defined so get them
|
// 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) {
|
if (tempNodeParameters !== null) {
|
||||||
nodeParameters[nodeProperties.name] = tempNodeParameters;
|
nodeParameters[nodeProperties.name] = tempNodeParameters;
|
||||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||||
}
|
}
|
||||||
} else if (returnDefaults === true) {
|
} else if (returnDefaults) {
|
||||||
// Does not have values defined but defaults should be returned
|
// Does not have values defined but defaults should be returned
|
||||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||||
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (nodeProperties.type === 'fixedCollection') {
|
} else if (nodeProperties.type === 'fixedCollection') {
|
||||||
// Is fixedCollection
|
// Is fixedCollection
|
||||||
|
|
||||||
|
@ -646,7 +697,7 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
||||||
let nodePropertyOptions: INodePropertyCollection | undefined;
|
let nodePropertyOptions: INodePropertyCollection | undefined;
|
||||||
|
|
||||||
let propertyValues = nodeValues[nodeProperties.name];
|
let propertyValues = nodeValues[nodeProperties.name];
|
||||||
if (returnDefaults === true) {
|
if (returnDefaults) {
|
||||||
if (propertyValues === undefined) {
|
if (propertyValues === undefined) {
|
||||||
propertyValues = JSON.parse(JSON.stringify(nodeProperties.default));
|
propertyValues = JSON.parse(JSON.stringify(nodeProperties.default));
|
||||||
}
|
}
|
||||||
|
@ -654,20 +705,39 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
||||||
|
|
||||||
// Iterate over all collections
|
// Iterate over all collections
|
||||||
for (const itemName of Object.keys(propertyValues || {})) {
|
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
|
// Multiple can be set so will be an array
|
||||||
|
|
||||||
const tempArrayValue: INodeParameters[] = [];
|
const tempArrayValue: INodeParameters[] = [];
|
||||||
// Iterate over all items as it contains multiple ones
|
// Iterate over all items as it contains multiple ones
|
||||||
for (const nodeValue of (propertyValues as INodeParameters)[itemName] as INodeParameters[]) {
|
for (const nodeValue of (propertyValues as INodeParameters)[
|
||||||
nodePropertyOptions = nodeProperties!.options!.find((nodePropertyOptions) => nodePropertyOptions.name === itemName) as INodePropertyCollection;
|
itemName
|
||||||
|
] as INodeParameters[]) {
|
||||||
|
nodePropertyOptions = nodeProperties.options!.find(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
(nodePropertyOptions) => nodePropertyOptions.name === itemName,
|
||||||
|
) as INodePropertyCollection;
|
||||||
|
|
||||||
if (nodePropertyOptions === undefined) {
|
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!;
|
tempNodePropertiesArray = nodePropertyOptions.values!;
|
||||||
tempValue = getNodeParameters(tempNodePropertiesArray, nodeValue as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
|
tempValue = getNodeParameters(
|
||||||
|
tempNodePropertiesArray,
|
||||||
|
nodeValue,
|
||||||
|
returnDefaults,
|
||||||
|
returnNoneDisplayed,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nodeValuesRoot,
|
||||||
|
nodeProperties.type,
|
||||||
|
);
|
||||||
if (tempValue !== null) {
|
if (tempValue !== null) {
|
||||||
tempArrayValue.push(tempValue);
|
tempArrayValue.push(tempValue);
|
||||||
}
|
}
|
||||||
|
@ -678,11 +748,23 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
||||||
tempNodeParameters = {};
|
tempNodeParameters = {};
|
||||||
|
|
||||||
// Get the options of the current item
|
// 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) {
|
if (nodePropertyOptions !== undefined) {
|
||||||
tempNodePropertiesArray = (nodePropertyOptions as INodePropertyCollection).values!;
|
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) {
|
if (tempValue !== null) {
|
||||||
Object.assign(tempNodeParameters, tempValue);
|
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
|
// Set only if value got found
|
||||||
|
|
||||||
if (returnDefaults === true) {
|
if (returnDefaults) {
|
||||||
// Set also when it has the default value
|
// Set also when it has the default value
|
||||||
if (collectionValues === undefined) {
|
if (collectionValues === undefined) {
|
||||||
nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
|
nodeParameters[nodeProperties.name] = JSON.parse(
|
||||||
|
JSON.stringify(nodeProperties.default),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
nodeParameters[nodeProperties.name] = collectionValues;
|
nodeParameters[nodeProperties.name] = collectionValues;
|
||||||
}
|
}
|
||||||
|
@ -717,7 +801,6 @@ export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeVa
|
||||||
return nodeParameters;
|
return nodeParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Brings the output data in a format that can be returned from a node
|
* 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]
|
* @param {number} [outputIndex=0]
|
||||||
* @returns {Promise<INodeExecutionData[][]>}
|
* @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
|
// TODO: Check if node has output with that index
|
||||||
const returnData = [];
|
const returnData = [];
|
||||||
|
|
||||||
|
@ -739,8 +825,6 @@ export async function prepareOutputData(outputData: INodeExecutionData[], output
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the webhooks which should be created for the give node
|
* 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
|
* @param {INode} node
|
||||||
* @returns {IWebhookData[]}
|
* @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) {
|
if (node.disabled === true) {
|
||||||
// Node is disabled so webhooks will also not be enabled
|
// Node is disabled so webhooks will also not be enabled
|
||||||
return [];
|
return [];
|
||||||
|
@ -767,15 +856,21 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData:
|
||||||
|
|
||||||
const returnData: IWebhookData[] = [];
|
const returnData: IWebhookData[] = [];
|
||||||
for (const webhookDescription of nodeType.description.webhooks) {
|
for (const webhookDescription of nodeType.description.webhooks) {
|
||||||
|
if (ignoreRestartWehbooks && webhookDescription.restartWebhook === true) {
|
||||||
if (ignoreRestartWehbooks === true && webhookDescription.restartWebhook === true) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodeWebhookPath = workflow.expression.getSimpleParameterValue(node, webhookDescription['path'], mode, {});
|
let nodeWebhookPath = workflow.expression.getSimpleParameterValue(
|
||||||
|
node,
|
||||||
|
webhookDescription.path,
|
||||||
|
mode,
|
||||||
|
{},
|
||||||
|
);
|
||||||
if (nodeWebhookPath === undefined) {
|
if (nodeWebhookPath === undefined) {
|
||||||
// TODO: Use a proper logger
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,15 +883,35 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData:
|
||||||
nodeWebhookPath = nodeWebhookPath.slice(0, -1);
|
nodeWebhookPath = nodeWebhookPath.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['isFullPath'], 'internal', {}, false) as boolean;
|
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(
|
||||||
const restartWebhook: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['restartWebhook'], 'internal', {}, false) as boolean;
|
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 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) {
|
if (httpMethod === undefined) {
|
||||||
// TODO: Use a proper logger
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -838,10 +953,17 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
||||||
|
|
||||||
const returnData: IWebhookData[] = [];
|
const returnData: IWebhookData[] = [];
|
||||||
for (const webhookDescription of nodeType.description.webhooks) {
|
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) {
|
if (nodeWebhookPath === undefined) {
|
||||||
// TODO: Use a proper logger
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,19 +976,32 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
||||||
nodeWebhookPath = nodeWebhookPath.slice(0, -1);
|
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 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) {
|
if (httpMethod === undefined) {
|
||||||
// TODO: Use a proper logger
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
// @ts-ignore
|
||||||
returnData.push({
|
returnData.push({
|
||||||
httpMethod: httpMethod.toString() as WebhookHttpMethod,
|
httpMethod: httpMethod.toString() as WebhookHttpMethod,
|
||||||
node: node.name,
|
node: node.name,
|
||||||
|
@ -879,7 +1014,6 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the webhook path
|
* Returns the webhook path
|
||||||
*
|
*
|
||||||
|
@ -889,11 +1023,18 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {string}
|
* @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 = '';
|
let webhookPath = '';
|
||||||
if (restartWebhook === true) {
|
if (restartWebhook === true) {
|
||||||
return path;
|
return path;
|
||||||
} else if (node.webhookId === undefined) {
|
}
|
||||||
|
if (node.webhookId === undefined) {
|
||||||
webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`;
|
webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`;
|
||||||
} else {
|
} else {
|
||||||
if (isFullPath === true) {
|
if (isFullPath === true) {
|
||||||
|
@ -904,7 +1045,6 @@ export function getNodeWebhookPath(workflowId: string, node: INode, path: string
|
||||||
return webhookPath;
|
return webhookPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the webhook URL
|
* Returns the webhook URL
|
||||||
*
|
*
|
||||||
|
@ -916,7 +1056,13 @@ export function getNodeWebhookPath(workflowId: string, node: INode, path: string
|
||||||
* @param {boolean} isFullPath
|
* @param {boolean} isFullPath
|
||||||
* @returns {string}
|
* @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) {
|
if ((path.startsWith(':') || path.includes('/:')) && node.webhookId) {
|
||||||
// setting this to false to prefix the webhookId
|
// setting this to false to prefix the webhookId
|
||||||
isFullPath = false;
|
isFullPath = false;
|
||||||
|
@ -927,7 +1073,6 @@ export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INo
|
||||||
return `${baseUrl}/${getNodeWebhookPath(workflowId, node, path, isFullPath)}`;
|
return `${baseUrl}/${getNodeWebhookPath(workflowId, node, path, isFullPath)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the parameter-issues of the node
|
* 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
|
* @param {INode} node The data of the node
|
||||||
* @returns {(INodeIssues | null)}
|
* @returns {(INodeIssues | null)}
|
||||||
*/
|
*/
|
||||||
export function getNodeParametersIssues(nodePropertiesArray: INodeProperties[], node: INode): INodeIssues | null {
|
export function getNodeParametersIssues(
|
||||||
|
nodePropertiesArray: INodeProperties[],
|
||||||
|
node: INode,
|
||||||
|
): INodeIssues | null {
|
||||||
const foundIssues: INodeIssues = {};
|
const foundIssues: INodeIssues = {};
|
||||||
let propertyIssues: INodeIssues;
|
let propertyIssues: INodeIssues;
|
||||||
|
|
||||||
|
@ -957,7 +1105,6 @@ export function getNodeParametersIssues(nodePropertiesArray: INodeProperties[],
|
||||||
return foundIssues;
|
return foundIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the issues of the node as string
|
* Returns the issues of the node as string
|
||||||
*
|
*
|
||||||
|
@ -973,12 +1120,10 @@ export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[]
|
||||||
nodeIssues.push(`Execution Error.`);
|
nodeIssues.push(`Execution Error.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectProperties = [
|
const objectProperties = ['parameters', 'credentials'];
|
||||||
'parameters',
|
|
||||||
'credentials',
|
|
||||||
];
|
|
||||||
|
|
||||||
let issueText: string, parameterName: string;
|
let issueText: string;
|
||||||
|
let parameterName: string;
|
||||||
for (const propertyName of objectProperties) {
|
for (const propertyName of objectProperties) {
|
||||||
if (issues[propertyName] !== undefined) {
|
if (issues[propertyName] !== undefined) {
|
||||||
for (parameterName of Object.keys(issues[propertyName] as object)) {
|
for (parameterName of Object.keys(issues[propertyName] as object)) {
|
||||||
|
@ -1000,7 +1145,6 @@ export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[]
|
||||||
return nodeIssues;
|
return nodeIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an issue if the parameter is not defined
|
* 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 {INodeProperties} nodeProperties The properties of the node
|
||||||
* @param {NodeParameterValue} value The value of the parameter
|
* @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
|
// 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 === 'multiOptions' && Array.isArray(value) && value.length === 0) ||
|
||||||
(nodeProperties.type === 'dateTime' && value === undefined)) {
|
(nodeProperties.type === 'dateTime' && value === undefined)
|
||||||
|
) {
|
||||||
// Parameter is requried but empty
|
// Parameter is requried but empty
|
||||||
if (foundIssues.parameters === undefined) {
|
if (foundIssues.parameters === undefined) {
|
||||||
foundIssues.parameters = {};
|
foundIssues.parameters = {};
|
||||||
|
@ -1022,11 +1172,12 @@ export function addToIssuesIfMissing(foundIssues: INodeIssues, nodeProperties: I
|
||||||
foundIssues.parameters[nodeProperties.name] = [];
|
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
|
* Returns the parameter value
|
||||||
*
|
*
|
||||||
|
@ -1036,15 +1187,14 @@ export function addToIssuesIfMissing(foundIssues: INodeIssues, nodeProperties: I
|
||||||
* @param {string} path The path to the properties
|
* @param {string} path The path to the properties
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getParameterValueByPath(nodeValues: INodeParameters, parameterName: string, path: string) {
|
export function getParameterValueByPath(
|
||||||
return get(
|
nodeValues: INodeParameters,
|
||||||
nodeValues,
|
parameterName: string,
|
||||||
path ? path + '.' + parameterName : parameterName,
|
path: string,
|
||||||
);
|
) {
|
||||||
|
return get(nodeValues, path ? `${path}.${parameterName}` : parameterName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the issues with the given node-values
|
* 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
|
* @param {string} path The path to the properties
|
||||||
* @returns {INodeIssues}
|
* @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 = {};
|
const foundIssues: INodeIssues = {};
|
||||||
let value;
|
let value;
|
||||||
|
|
||||||
|
@ -1062,11 +1216,15 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
||||||
if (displayParameterPath(nodeValues, nodeProperties, path)) {
|
if (displayParameterPath(nodeValues, nodeProperties, path)) {
|
||||||
value = getParameterValueByPath(nodeValues, nodeProperties.name, 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
|
// Multiple can be set so will be an array
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
for (const singleValue of value as NodeParameterValue[]) {
|
for (const singleValue of value as NodeParameterValue[]) {
|
||||||
addToIssuesIfMissing(foundIssues, nodeProperties, singleValue as NodeParameterValue);
|
addToIssuesIfMissing(foundIssues, nodeProperties, singleValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1106,7 +1264,7 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (nodeProperties.type === 'fixedCollection') {
|
} else if (nodeProperties.type === 'fixedCollection') {
|
||||||
basePath = basePath ? `${basePath}.` : '' + nodeProperties.name + '.';
|
basePath = basePath ? `${basePath}.` : `${nodeProperties.name}.`;
|
||||||
|
|
||||||
let propertyOptions: INodePropertyCollection;
|
let propertyOptions: INodePropertyCollection;
|
||||||
for (propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
for (propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
||||||
|
@ -1116,14 +1274,18 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
||||||
continue;
|
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
|
// Multiple can be set so will be an array of objects
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
for (let i = 0; i < (value as INodeParameters[]).length; i++) {
|
for (let i = 0; i < (value as INodeParameters[]).length; i++) {
|
||||||
for (const option of propertyOptions.values) {
|
for (const option of propertyOptions.values) {
|
||||||
checkChildNodeProperties.push({
|
checkChildNodeProperties.push({
|
||||||
basePath: `${basePath}${propertyOptions.name}[${i}]`,
|
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) {
|
for (const option of propertyOptions.values) {
|
||||||
checkChildNodeProperties.push({
|
checkChildNodeProperties.push({
|
||||||
basePath: basePath + propertyOptions.name,
|
basePath: basePath + propertyOptions.name,
|
||||||
data: option as INodeProperties,
|
data: option,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1146,14 +1308,13 @@ export function getParameterIssues(nodeProperties: INodeProperties, nodeValues:
|
||||||
let propertyIssues;
|
let propertyIssues;
|
||||||
|
|
||||||
for (const optionData of checkChildNodeProperties) {
|
for (const optionData of checkChildNodeProperties) {
|
||||||
propertyIssues = getParameterIssues(optionData.data as INodeProperties, nodeValues, optionData.basePath);
|
propertyIssues = getParameterIssues(optionData.data, nodeValues, optionData.basePath);
|
||||||
mergeIssues(foundIssues, propertyIssues);
|
mergeIssues(foundIssues, propertyIssues);
|
||||||
}
|
}
|
||||||
|
|
||||||
return foundIssues;
|
return foundIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges multiple NodeIssues together
|
* Merges multiple NodeIssues together
|
||||||
*
|
*
|
||||||
|
@ -1172,10 +1333,7 @@ export function mergeIssues(destination: INodeIssues, source: INodeIssues | null
|
||||||
destination.execution = true;
|
destination.execution = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectProperties = [
|
const objectProperties = ['parameters', 'credentials'];
|
||||||
'parameters',
|
|
||||||
'credentials',
|
|
||||||
];
|
|
||||||
|
|
||||||
let destinationProperty: INodeIssueObjectProperty;
|
let destinationProperty: INodeIssueObjectProperty;
|
||||||
for (const propertyName of objectProperties) {
|
for (const propertyName of objectProperties) {
|
||||||
|
@ -1190,7 +1348,10 @@ export function mergeIssues(destination: INodeIssues, source: INodeIssues | null
|
||||||
if (destinationProperty[parameterName] === undefined) {
|
if (destinationProperty[parameterName] === undefined) {
|
||||||
destinationProperty[parameterName] = [];
|
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
|
* Merges the given node properties
|
||||||
*
|
*
|
||||||
|
@ -1209,10 +1368,13 @@ export function mergeIssues(destination: INodeIssues, source: INodeIssues | null
|
||||||
* @param {INodeProperties[]} mainProperties
|
* @param {INodeProperties[]} mainProperties
|
||||||
* @param {INodeProperties[]} addProperties
|
* @param {INodeProperties[]} addProperties
|
||||||
*/
|
*/
|
||||||
export function mergeNodeProperties(mainProperties: INodeProperties[], addProperties: INodeProperties[]): void {
|
export function mergeNodeProperties(
|
||||||
|
mainProperties: INodeProperties[],
|
||||||
|
addProperties: INodeProperties[],
|
||||||
|
): void {
|
||||||
let existingIndex: number;
|
let existingIndex: number;
|
||||||
for (const property of addProperties) {
|
for (const property of addProperties) {
|
||||||
existingIndex = mainProperties.findIndex(element => element.name === property.name);
|
existingIndex = mainProperties.findIndex((element) => element.name === property.name);
|
||||||
|
|
||||||
if (existingIndex === -1) {
|
if (existingIndex === -1) {
|
||||||
// Property does not exist yet, so add
|
// Property does not exist yet, so add
|
||||||
|
|
|
@ -1,20 +1,35 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
import {
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
IDataObject,
|
/* eslint-disable no-param-reassign */
|
||||||
IObservableObject,
|
/* eslint-disable no-underscore-dangle */
|
||||||
} from './';
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { IDataObject, IObservableObject } from '.';
|
||||||
|
|
||||||
export interface IObservableOptions {
|
export interface IObservableOptions {
|
||||||
ignoreEmptyOnFirstChild?: boolean;
|
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;
|
depth = depth || 0;
|
||||||
|
|
||||||
// Make all the children of target also observeable
|
// Make all the children of target also observeable
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const key in target) {
|
for (const key in target) {
|
||||||
if (typeof target[key] === 'object' && target[key] !== null) {
|
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,
|
writable: true,
|
||||||
});
|
});
|
||||||
return new Proxy(target, {
|
return new Proxy(target, {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
deleteProperty(target, name) {
|
deleteProperty(target, name) {
|
||||||
if (parent === undefined) {
|
if (parent === undefined) {
|
||||||
// If no parent is given mark current data as changed
|
// 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) {
|
set(target, name, value) {
|
||||||
if (parent === undefined) {
|
if (parent === undefined) {
|
||||||
// If no parent is given mark current data as changed
|
// If no parent is given mark current data as changed
|
||||||
if (option !== undefined && option.ignoreEmptyOnFirstChild === true && depth === 0
|
if (
|
||||||
&& target[name.toString()] === undefined && typeof value === 'object' && Object.keys(value).length === 0) {
|
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 {
|
} else {
|
||||||
(target as IObservableObject).__dataChanged = true;
|
(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 {
|
import {
|
||||||
Expression,
|
Expression,
|
||||||
IConnections,
|
IConnections,
|
||||||
|
@ -26,20 +37,27 @@ import {
|
||||||
WebhookSetupMethodNames,
|
WebhookSetupMethodNames,
|
||||||
WorkflowActivateMode,
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from './';
|
} from '.';
|
||||||
|
|
||||||
import { IConnection, IDataObject, IObservableObject } from './Interfaces';
|
import { IConnection, IDataObject, IObservableObject } from './Interfaces';
|
||||||
|
|
||||||
|
|
||||||
export class Workflow {
|
export class Workflow {
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
|
|
||||||
name: string | undefined;
|
name: string | undefined;
|
||||||
|
|
||||||
nodes: INodes = {};
|
nodes: INodes = {};
|
||||||
|
|
||||||
connectionsBySourceNode: IConnections;
|
connectionsBySourceNode: IConnections;
|
||||||
|
|
||||||
connectionsByDestinationNode: IConnections;
|
connectionsByDestinationNode: IConnections;
|
||||||
|
|
||||||
nodeTypes: INodeTypes;
|
nodeTypes: INodeTypes;
|
||||||
|
|
||||||
expression: Expression;
|
expression: Expression;
|
||||||
|
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
|
||||||
settings: IWorkflowSettings;
|
settings: IWorkflowSettings;
|
||||||
|
|
||||||
// To save workflow specific static data like for example
|
// To save workflow specific static data like for example
|
||||||
|
@ -47,7 +65,16 @@ export class Workflow {
|
||||||
staticData: IDataObject;
|
staticData: IDataObject;
|
||||||
|
|
||||||
// constructor(id: string | undefined, nodes: INode[], connections: IConnections, active: boolean, nodeTypes: INodeTypes, staticData?: IDataObject, settings?: IWorkflowSettings) {
|
// 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.id = parameters.id;
|
||||||
this.name = parameters.name;
|
this.name = parameters.name;
|
||||||
this.nodeTypes = parameters.nodeTypes;
|
this.nodeTypes = parameters.nodeTypes;
|
||||||
|
@ -70,7 +97,12 @@ export class Workflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add default values
|
// 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 : {};
|
node.parameters = nodeParameters !== null ? nodeParameters : {};
|
||||||
}
|
}
|
||||||
this.connectionsBySourceNode = parameters.connections;
|
this.connectionsBySourceNode = parameters.connections;
|
||||||
|
@ -80,15 +112,15 @@ export class Workflow {
|
||||||
|
|
||||||
this.active = parameters.active || false;
|
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.settings = parameters.settings || {};
|
||||||
|
|
||||||
this.expression = new Expression(this);
|
this.expression = new Expression(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default connections are by source node. This function rewrites them by destination nodes
|
* The default connections are by source node. This function rewrites them by destination nodes
|
||||||
* to easily find parent nodes.
|
* to easily find parent nodes.
|
||||||
|
@ -140,8 +172,6 @@ export class Workflow {
|
||||||
return returnConnection;
|
return returnConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A workflow can only be activated if it has a node which has either triggers
|
* A workflow can only be activated if it has a node which has either triggers
|
||||||
* or webhooks defined.
|
* or webhooks defined.
|
||||||
|
@ -162,6 +192,7 @@ export class Workflow {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||||
if (ignoreNodeTypes !== undefined && ignoreNodeTypes.includes(node.type)) {
|
if (ignoreNodeTypes !== undefined && ignoreNodeTypes.includes(node.type)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -173,7 +204,11 @@ export class Workflow {
|
||||||
continue;
|
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.
|
// Is a trigger node. So workflow can be activated.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -182,8 +217,6 @@ export class Workflow {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if everything in the workflow is complete
|
* Checks if everything in the workflow is complete
|
||||||
* and ready to be executed. If it returns null everything
|
* and ready to be executed. If it returns null everything
|
||||||
|
@ -216,7 +249,7 @@ export class Workflow {
|
||||||
typeUnknown: true,
|
typeUnknown: true,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.description.properties!, node);
|
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeIssues !== null) {
|
if (nodeIssues !== null) {
|
||||||
|
@ -231,8 +264,6 @@ export class Workflow {
|
||||||
return workflowIssues;
|
return workflowIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the static data of the workflow.
|
* Returns the static data of the workflow.
|
||||||
* It gets saved with the workflow and will be the same for
|
* It gets saved with the workflow and will be the same for
|
||||||
|
@ -249,11 +280,15 @@ export class Workflow {
|
||||||
key = 'global';
|
key = 'global';
|
||||||
} else if (type === 'node') {
|
} else if (type === 'node') {
|
||||||
if (node === undefined) {
|
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}`;
|
key = `node:${node.name}`;
|
||||||
} else {
|
} 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) {
|
if (this.staticData[key] === undefined) {
|
||||||
|
@ -265,8 +300,6 @@ export class Workflow {
|
||||||
return this.staticData[key] as IDataObject;
|
return this.staticData[key] as IDataObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the trigger nodes in the workflow.
|
* Returns all the trigger nodes in the workflow.
|
||||||
*
|
*
|
||||||
|
@ -274,10 +307,9 @@ export class Workflow {
|
||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
getTriggerNodes(): INode[] {
|
getTriggerNodes(): INode[] {
|
||||||
return this.queryNodes((nodeType: INodeType) => !!nodeType.trigger );
|
return this.queryNodes((nodeType: INodeType) => !!nodeType.trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the poll nodes in the workflow
|
* Returns all the poll nodes in the workflow
|
||||||
*
|
*
|
||||||
|
@ -285,10 +317,9 @@ export class Workflow {
|
||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
getPollNodes(): INode[] {
|
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
|
* Returns all the nodes in the workflow for which the given
|
||||||
* checkFunction return true
|
* checkFunction return true
|
||||||
|
@ -321,8 +352,6 @@ export class Workflow {
|
||||||
return returnNodes;
|
return returnNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the node with the given name if it exists else null
|
* Returns the node with the given name if it exists else null
|
||||||
*
|
*
|
||||||
|
@ -338,7 +367,6 @@ export class Workflow {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renames nodes in expressions
|
* Renames nodes in expressions
|
||||||
*
|
*
|
||||||
|
@ -348,7 +376,11 @@ export class Workflow {
|
||||||
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
|
||||||
* @memberof Workflow
|
* @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') {
|
if (typeof parameterValue !== 'object') {
|
||||||
// Reached the actual value
|
// Reached the actual value
|
||||||
if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
|
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
|
// In case some special characters are used in name escape them
|
||||||
const currentNameEscaped = currentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
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)) {
|
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) {
|
for (const currentValue of parameterValue) {
|
||||||
returnArray.push(this.renameNodeInExpressions(currentValue, currentName, newName));
|
returnArray.push(this.renameNodeInExpressions(currentValue, currentName, newName));
|
||||||
|
@ -379,17 +416,21 @@ export class Workflow {
|
||||||
return returnArray;
|
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 || {})) {
|
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;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename a node in the workflow
|
* Rename a node in the workflow
|
||||||
*
|
*
|
||||||
|
@ -398,7 +439,6 @@ export class Workflow {
|
||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
renameNode(currentName: string, newName: string) {
|
renameNode(currentName: string, newName: string) {
|
||||||
|
|
||||||
// Rename the node itself
|
// Rename the node itself
|
||||||
if (this.nodes[currentName] !== undefined) {
|
if (this.nodes[currentName] !== undefined) {
|
||||||
this.nodes[newName] = this.nodes[currentName];
|
this.nodes[newName] = this.nodes[currentName];
|
||||||
|
@ -409,7 +449,11 @@ export class Workflow {
|
||||||
// Update the expressions which reference the node
|
// Update the expressions which reference the node
|
||||||
// with its old name
|
// with its old name
|
||||||
for (const node of Object.values(this.nodes)) {
|
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
|
// Change all source connections
|
||||||
|
@ -419,12 +463,21 @@ export class Workflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change all destination connections
|
// 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 (sourceNode of Object.keys(this.connectionsBySourceNode)) {
|
||||||
for (type of Object.keys(this.connectionsBySourceNode[sourceNode])) {
|
for (type of Object.keys(this.connectionsBySourceNode[sourceNode])) {
|
||||||
for (sourceIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type])) {
|
for (sourceIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type])) {
|
||||||
for (connectionIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)])) {
|
for (connectionIndex of Object.keys(
|
||||||
connectionData = this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)][parseInt(connectionIndex, 10)];
|
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)],
|
||||||
|
)) {
|
||||||
|
connectionData =
|
||||||
|
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)][
|
||||||
|
parseInt(connectionIndex, 10)
|
||||||
|
];
|
||||||
if (connectionData.node === currentName) {
|
if (connectionData.node === currentName) {
|
||||||
connectionData.node = newName;
|
connectionData.node = newName;
|
||||||
}
|
}
|
||||||
|
@ -434,11 +487,11 @@ export class Workflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the updated connections to create updated connections by destionation nodes
|
// 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
|
* Finds the highest parent nodes of the node with the given name
|
||||||
*
|
*
|
||||||
|
@ -448,7 +501,12 @@ export class Workflow {
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
* @memberof Workflow
|
* @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[] = [];
|
const currentHighest: string[] = [];
|
||||||
if (this.nodes[nodeName].disabled === false) {
|
if (this.nodes[nodeName].disabled === false) {
|
||||||
// If the current node is not disabled itself is the highest
|
// If the current node is not disabled itself is the highest
|
||||||
|
@ -467,23 +525,28 @@ export class Workflow {
|
||||||
|
|
||||||
checkedNodes = checkedNodes || [];
|
checkedNodes = checkedNodes || [];
|
||||||
|
|
||||||
if (checkedNodes!.includes(nodeName)) {
|
if (checkedNodes.includes(nodeName)) {
|
||||||
// Node got checked already before
|
// Node got checked already before
|
||||||
return currentHighest;
|
return currentHighest;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkedNodes!.push(nodeName);
|
checkedNodes.push(nodeName);
|
||||||
|
|
||||||
const returnNodes: string[] = [];
|
const returnNodes: string[] = [];
|
||||||
let addNodes: string[];
|
let addNodes: string[];
|
||||||
|
|
||||||
let connectionsByIndex: IConnection[];
|
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 (nodeConnectionIndex !== undefined && nodeConnectionIndex !== connectionIndex) {
|
||||||
// If a connection-index is given ignore all other ones
|
// If a connection-index is given ignore all other ones
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
connectionsByIndex = this.connectionsByDestinationNode[nodeName][type][connectionIndex];
|
connectionsByIndex = this.connectionsByDestinationNode[nodeName][type][connectionIndex];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
connectionsByIndex.forEach((connection) => {
|
connectionsByIndex.forEach((connection) => {
|
||||||
if (checkedNodes!.includes(connection.node)) {
|
if (checkedNodes!.includes(connection.node)) {
|
||||||
// Node got checked already before
|
// Node got checked already before
|
||||||
|
@ -512,8 +575,6 @@ export class Workflow {
|
||||||
return returnNodes;
|
return returnNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the after the given one
|
* Returns all the after the given one
|
||||||
*
|
*
|
||||||
|
@ -527,8 +588,6 @@ export class Workflow {
|
||||||
return this.getConnectedNodes(this.connectionsBySourceNode, nodeName, type, depth);
|
return this.getConnectedNodes(this.connectionsBySourceNode, nodeName, type, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the nodes before the given one
|
* Returns all the nodes before the given one
|
||||||
*
|
*
|
||||||
|
@ -542,8 +601,6 @@ export class Workflow {
|
||||||
return this.getConnectedNodes(this.connectionsByDestinationNode, nodeName, type, depth);
|
return this.getConnectedNodes(this.connectionsByDestinationNode, nodeName, type, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all the nodes which are connected nodes starting from
|
* Gets all the nodes which are connected nodes starting from
|
||||||
* the given one
|
* the given one
|
||||||
|
@ -556,7 +613,13 @@ export class Workflow {
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
* @memberof Workflow
|
* @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;
|
depth = depth === -1 ? -1 : depth;
|
||||||
const newDepth = depth === -1 ? depth : depth - 1;
|
const newDepth = depth === -1 ? depth : depth - 1;
|
||||||
if (depth === 0) {
|
if (depth === 0) {
|
||||||
|
@ -576,12 +639,12 @@ export class Workflow {
|
||||||
|
|
||||||
checkedNodes = checkedNodes || [];
|
checkedNodes = checkedNodes || [];
|
||||||
|
|
||||||
if (checkedNodes!.includes(nodeName)) {
|
if (checkedNodes.includes(nodeName)) {
|
||||||
// Node got checked already before
|
// Node got checked already before
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
checkedNodes!.push(nodeName);
|
checkedNodes.push(nodeName);
|
||||||
|
|
||||||
const returnNodes: string[] = [];
|
const returnNodes: string[] = [];
|
||||||
let addNodes: string[];
|
let addNodes: string[];
|
||||||
|
@ -597,7 +660,13 @@ export class Workflow {
|
||||||
|
|
||||||
returnNodes.unshift(connection.node);
|
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) {
|
for (i = addNodes.length; i--; i > 0) {
|
||||||
// Because nodes can have multiple parents it is possible that
|
// Because nodes can have multiple parents it is possible that
|
||||||
|
@ -620,8 +689,6 @@ export class Workflow {
|
||||||
return returnNodes;
|
return returnNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns via which output of the parent-node the node
|
* Returns via which output of the parent-node the node
|
||||||
* is connected to.
|
* is connected to.
|
||||||
|
@ -634,7 +701,13 @@ export class Workflow {
|
||||||
* @returns {(number | undefined)}
|
* @returns {(number | undefined)}
|
||||||
* @memberof Workflow
|
* @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);
|
const node = this.getNode(parentNodeName);
|
||||||
if (node === null) {
|
if (node === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -665,12 +738,12 @@ export class Workflow {
|
||||||
|
|
||||||
checkedNodes = checkedNodes || [];
|
checkedNodes = checkedNodes || [];
|
||||||
|
|
||||||
if (checkedNodes!.includes(nodeName)) {
|
if (checkedNodes.includes(nodeName)) {
|
||||||
// Node got checked already before
|
// Node got checked already before
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkedNodes!.push(nodeName);
|
checkedNodes.push(nodeName);
|
||||||
|
|
||||||
let outputIndex: number | undefined;
|
let outputIndex: number | undefined;
|
||||||
for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
|
for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
|
||||||
|
@ -679,12 +752,18 @@ export class Workflow {
|
||||||
return connection.index;
|
return connection.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkedNodes!.includes(connection.node)) {
|
if (checkedNodes.includes(connection.node)) {
|
||||||
// Node got checked already before so continue with the next one
|
// Node got checked already before so continue with the next one
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputIndex = this.getNodeConnectionOutputIndex(connection.node, parentNodeName, type, newDepth, checkedNodes);
|
outputIndex = this.getNodeConnectionOutputIndex(
|
||||||
|
connection.node,
|
||||||
|
parentNodeName,
|
||||||
|
type,
|
||||||
|
newDepth,
|
||||||
|
checkedNodes,
|
||||||
|
);
|
||||||
|
|
||||||
if (outputIndex !== undefined) {
|
if (outputIndex !== undefined) {
|
||||||
return outputIndex;
|
return outputIndex;
|
||||||
|
@ -695,9 +774,6 @@ export class Workflow {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns from which of the given nodes the workflow should get started from
|
* Returns from which of the given nodes the workflow should get started from
|
||||||
*
|
*
|
||||||
|
@ -713,7 +789,6 @@ export class Workflow {
|
||||||
node = this.nodes[nodeName];
|
node = this.nodes[nodeName];
|
||||||
nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
||||||
|
|
||||||
|
|
||||||
if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
|
if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
|
||||||
if (node.disabled === true) {
|
if (node.disabled === true) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -734,8 +809,6 @@ export class Workflow {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the start node to start the worfklow from
|
* Returns the start node to start the worfklow from
|
||||||
*
|
*
|
||||||
|
@ -744,7 +817,6 @@ export class Workflow {
|
||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
getStartNode(destinationNode?: string): INode | undefined {
|
getStartNode(destinationNode?: string): INode | undefined {
|
||||||
|
|
||||||
if (destinationNode) {
|
if (destinationNode) {
|
||||||
// Find the highest parent nodes of the given one
|
// Find the highest parent nodes of the given one
|
||||||
const nodeNames = this.getHighestNode(destinationNode);
|
const nodeNames = this.getHighestNode(destinationNode);
|
||||||
|
@ -769,8 +841,6 @@ export class Workflow {
|
||||||
return this.__getStartNode(Object.keys(this.nodes));
|
return this.__getStartNode(Object.keys(this.nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the Webhooks method of the node
|
* Executes the Webhooks method of the node
|
||||||
*
|
*
|
||||||
|
@ -781,11 +851,17 @@ export class Workflow {
|
||||||
* @returns {(Promise<boolean | undefined>)}
|
* @returns {(Promise<boolean | undefined>)}
|
||||||
* @memberof Workflow
|
* @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 node = this.getNode(webhookData.node) as INode;
|
||||||
const nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
const nodeType = this.nodeTypes.getByName(node.type) as INodeType;
|
||||||
|
|
||||||
|
|
||||||
if (nodeType.webhookMethods === undefined) {
|
if (nodeType.webhookMethods === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -798,11 +874,19 @@ export class Workflow {
|
||||||
return;
|
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);
|
return nodeType.webhookMethods[webhookData.webhookDescription.name][method]!.call(thisArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given trigger node so that it can trigger the workflow
|
* Runs the given trigger node so that it can trigger the workflow
|
||||||
* when the node has data.
|
* when the node has data.
|
||||||
|
@ -814,7 +898,13 @@ export class Workflow {
|
||||||
* @returns {(Promise<ITriggerResponse | undefined>)}
|
* @returns {(Promise<ITriggerResponse | undefined>)}
|
||||||
* @memberof Workflow
|
* @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 triggerFunctions = getTriggerFunctions(this, node, additionalData, mode, activation);
|
||||||
|
|
||||||
const nodeType = this.nodeTypes.getByName(node.type);
|
const nodeType = this.nodeTypes.getByName(node.type);
|
||||||
|
@ -824,28 +914,29 @@ export class Workflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nodeType.trigger) {
|
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') {
|
if (mode === 'manual') {
|
||||||
// In manual mode we do not just start the trigger function we also
|
// 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
|
// 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
|
// Add the manual trigger response which resolves when the first time data got emitted
|
||||||
triggerResponse!.manualTriggerResponse = new Promise((resolve) => {
|
triggerResponse!.manualTriggerResponse = new Promise((resolve) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
triggerFunctions.emit = ((resolve) => (data: INodeExecutionData[][]) => {
|
triggerFunctions.emit = ((resolve) => (data: INodeExecutionData[][]) => {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
})(resolve);
|
})(resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
return triggerResponse;
|
return triggerResponse;
|
||||||
} else {
|
}
|
||||||
// In all other modes simply start the trigger
|
// 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
|
* Runs the given trigger node so that it can trigger the workflow
|
||||||
|
@ -856,7 +947,10 @@ export class Workflow {
|
||||||
* @returns
|
* @returns
|
||||||
* @memberof Workflow
|
* @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);
|
const nodeType = this.nodeTypes.getByName(node.type);
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
|
@ -864,13 +958,14 @@ export class Workflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nodeType.poll) {
|
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
|
* Executes the webhook data to see what it should return and if the
|
||||||
* workflow should be started or not
|
* workflow should be started or not
|
||||||
|
@ -882,7 +977,13 @@ export class Workflow {
|
||||||
* @returns {Promise<IWebhookResponseData>}
|
* @returns {Promise<IWebhookResponseData>}
|
||||||
* @memberof Workflow
|
* @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);
|
const nodeType = this.nodeTypes.getByName(node.type);
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new Error(`The type of the webhook node "${node.name}" is not known.`);
|
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.`);
|
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);
|
return nodeType.webhook.call(thisArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the given node.
|
* Executes the given node.
|
||||||
*
|
*
|
||||||
|
@ -908,7 +1014,15 @@ export class Workflow {
|
||||||
* @returns {(Promise<INodeExecutionData[][] | null>)}
|
* @returns {(Promise<INodeExecutionData[][] | null>)}
|
||||||
* @memberof Workflow
|
* @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.disabled === true) {
|
||||||
// If node is disabled simply pass the data through
|
// If node is disabled simply pass the data through
|
||||||
// return NodeRunHelpers.
|
// return NodeRunHelpers.
|
||||||
|
@ -917,7 +1031,7 @@ export class Workflow {
|
||||||
if (inputData.main[0] === null) {
|
if (inputData.main[0] === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return [(inputData.main[0] as INodeExecutionData[])];
|
return [inputData.main[0]];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -935,7 +1049,7 @@ export class Workflow {
|
||||||
|
|
||||||
if (inputData.hasOwnProperty('main') && inputData.main.length > 0) {
|
if (inputData.hasOwnProperty('main') && inputData.main.length > 0) {
|
||||||
// We always use the data of main input and the first input for executeSingle
|
// 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) {
|
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.
|
// 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
|
// 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.
|
// to log the error and display in Editor-UI.
|
||||||
|
@ -959,7 +1076,8 @@ export class Workflow {
|
||||||
connectionInputData = connectionInputData.slice(0, 1);
|
connectionInputData = connectionInputData.slice(0, 1);
|
||||||
const newInputData: ITaskDataConnections = {};
|
const newInputData: ITaskDataConnections = {};
|
||||||
for (const inputName of Object.keys(inputData)) {
|
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);
|
return input && input.slice(0, 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -970,9 +1088,19 @@ export class Workflow {
|
||||||
const returnPromises: Array<Promise<INodeExecutionData>> = [];
|
const returnPromises: Array<Promise<INodeExecutionData>> = [];
|
||||||
|
|
||||||
for (let itemIndex = 0; itemIndex < connectionInputData.length; itemIndex++) {
|
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) {
|
if (returnPromises.length === 0) {
|
||||||
|
@ -990,21 +1118,41 @@ export class Workflow {
|
||||||
return [promiseResults];
|
return [promiseResults];
|
||||||
}
|
}
|
||||||
} else if (nodeType.execute) {
|
} 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);
|
return nodeType.execute.call(thisArgs);
|
||||||
} else if (nodeType.poll) {
|
} else if (nodeType.poll) {
|
||||||
if (mode === 'manual') {
|
if (mode === 'manual') {
|
||||||
// In manual mode run the poll function
|
// 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);
|
return nodeType.poll.call(thisArgs);
|
||||||
} else {
|
}
|
||||||
// In any other mode pass data through as it already contains the result of the poll
|
// In any other mode pass data through as it already contains the result of the poll
|
||||||
return inputData.main as INodeExecutionData[][];
|
return inputData.main as INodeExecutionData[][];
|
||||||
}
|
|
||||||
} else if (nodeType.trigger) {
|
} else if (nodeType.trigger) {
|
||||||
if (mode === 'manual') {
|
if (mode === 'manual') {
|
||||||
// In manual mode start the trigger
|
// 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) {
|
if (triggerResponse === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1027,11 +1175,9 @@ export class Workflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} else {
|
}
|
||||||
// For trigger nodes in any mode except "manual" do we simply pass the data through
|
// For trigger nodes in any mode except "manual" do we simply pass the data through
|
||||||
return inputData.main as INodeExecutionData[][];
|
return inputData.main as INodeExecutionData[][];
|
||||||
}
|
|
||||||
|
|
||||||
} else if (nodeType.webhook) {
|
} else if (nodeType.webhook) {
|
||||||
// For webhook nodes always simply pass the data through
|
// For webhook nodes always simply pass the data through
|
||||||
return inputData.main as INodeExecutionData[][];
|
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