mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-26 05:04:05 -08:00
feat(editor, core): Integrate PostHog (#3865)
* Integrate PostHog - Part 1: Groundwork (#3753) * Integrate PostHog - Part 2: Event capture (#3779) * Integrate PostHog - Part 3: Session recordings (#3789) * Integrate PostHog - Part 4: Experiments (#3825) * Finalize PostHog integration (#3866) * 📦 Update `package-lock.json` * 🐛 Account for absent PH hooks file * ✨ Create new env `EXTERNAL_FRONTEND_HOOKS_FILES` * ⚡ Adjust env used for injecting PostHog * 🐛 Switch to semicolon delimiter * ⚡ Simplify to `externalFrontendHookPath` * Refactor FE hooks flow (#3884) * Add env var for session recordings * inject frontend hooks even when telemetry is off * allow multiple hooks files * cr * 🐛 Handle missing ref errors * 🔥 Remove outdated `continue` * 🎨 Change one-liners to blocks * 📦 Update `package-lock.json` Co-authored-by: Ahsan Virani <ahsan.virani@gmail.com>
This commit is contained in:
parent
2b4f5c6c78
commit
43e054f5ab
118
package-lock.json
generated
118
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.189.1",
|
"version": "0.191.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.189.1",
|
"version": "0.191.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
@ -41214,6 +41214,35 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/posthog-node": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-2+VhqiY/rKIqKIXyvemBFHbeijHE25sP7eKltnqcFqAssUE6+sX6vusN9A4luzToOqHQkUZexiCKxvuGagh7JA==",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "0.24.0",
|
||||||
|
"axios-retry": "^3.1.9",
|
||||||
|
"component-type": "^1.2.1",
|
||||||
|
"join-component": "^1.1.0",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"remove-trailing-slash": "^0.1.1",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"posthog": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/posthog-node/node_modules/axios": {
|
||||||
|
"version": "0.24.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||||
|
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.14.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
|
@ -53609,7 +53638,7 @@
|
||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.189.1",
|
"version": "0.191.0",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-cli": "4.0.0",
|
"@apidevtools/swagger-cli": "4.0.0",
|
||||||
|
@ -53660,10 +53689,10 @@
|
||||||
"lodash.split": "^4.4.2",
|
"lodash.split": "^4.4.2",
|
||||||
"lodash.unset": "^4.5.2",
|
"lodash.unset": "^4.5.2",
|
||||||
"mysql2": "~2.3.0",
|
"mysql2": "~2.3.0",
|
||||||
"n8n-core": "~0.129.0",
|
"n8n-core": "~0.131.0",
|
||||||
"n8n-editor-ui": "~0.155.0",
|
"n8n-editor-ui": "~0.157.0",
|
||||||
"n8n-nodes-base": "~0.187.0",
|
"n8n-nodes-base": "~0.189.0",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"nodemailer": "^6.7.1",
|
"nodemailer": "^6.7.1",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"open": "^7.0.0",
|
"open": "^7.0.0",
|
||||||
|
@ -53673,6 +53702,7 @@
|
||||||
"passport-cookie": "^1.0.9",
|
"passport-cookie": "^1.0.9",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"pg": "^8.3.0",
|
"pg": "^8.3.0",
|
||||||
|
"posthog-node": "^1.3.0",
|
||||||
"prom-client": "^13.1.0",
|
"prom-client": "^13.1.0",
|
||||||
"psl": "^1.8.0",
|
"psl": "^1.8.0",
|
||||||
"shelljs": "^0.8.5",
|
"shelljs": "^0.8.5",
|
||||||
|
@ -53733,7 +53763,7 @@
|
||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "n8n-core",
|
"name": "n8n-core",
|
||||||
"version": "0.129.0",
|
"version": "0.131.0",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
|
@ -53745,7 +53775,7 @@
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"p-cancelable": "^2.0.0",
|
"p-cancelable": "^2.0.0",
|
||||||
"qs": "^6.10.1",
|
"qs": "^6.10.1",
|
||||||
|
@ -53771,7 +53801,7 @@
|
||||||
},
|
},
|
||||||
"packages/design-system": {
|
"packages/design-system": {
|
||||||
"name": "n8n-design-system",
|
"name": "n8n-design-system",
|
||||||
"version": "0.29.0",
|
"version": "0.31.0",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"element-ui": "~2.15.7",
|
"element-ui": "~2.15.7",
|
||||||
|
@ -53847,14 +53877,14 @@
|
||||||
},
|
},
|
||||||
"packages/editor-ui": {
|
"packages/editor-ui": {
|
||||||
"name": "n8n-editor-ui",
|
"name": "n8n-editor-ui",
|
||||||
"version": "0.155.0",
|
"version": "0.157.0",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/open-sans": "^4.5.0",
|
"@fontsource/open-sans": "^4.5.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||||
"luxon": "^2.3.0",
|
"luxon": "^2.3.0",
|
||||||
"monaco-editor": "^0.30.1",
|
"monaco-editor": "^0.30.1",
|
||||||
"n8n-design-system": "~0.29.0",
|
"n8n-design-system": "~0.31.0",
|
||||||
"timeago.js": "^4.0.2",
|
"timeago.js": "^4.0.2",
|
||||||
"v-click-outside": "^3.1.2",
|
"v-click-outside": "^3.1.2",
|
||||||
"vue-fragment": "1.5.1",
|
"vue-fragment": "1.5.1",
|
||||||
|
@ -53906,7 +53936,7 @@
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"normalize-wheel": "^1.0.1",
|
"normalize-wheel": "^1.0.1",
|
||||||
"prismjs": "^1.17.1",
|
"prismjs": "^1.17.1",
|
||||||
"quill": "^2.0.0-dev.3",
|
"quill": "^2.0.0-dev.3",
|
||||||
|
@ -53932,7 +53962,7 @@
|
||||||
},
|
},
|
||||||
"packages/node-dev": {
|
"packages/node-dev": {
|
||||||
"name": "n8n-node-dev",
|
"name": "n8n-node-dev",
|
||||||
"version": "0.68.0",
|
"version": "0.70.0",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oclif/command": "^1.5.18",
|
"@oclif/command": "^1.5.18",
|
||||||
|
@ -53942,8 +53972,8 @@
|
||||||
"change-case": "^4.1.1",
|
"change-case": "^4.1.1",
|
||||||
"copyfiles": "^2.1.1",
|
"copyfiles": "^2.1.1",
|
||||||
"inquirer": "^7.0.1",
|
"inquirer": "^7.0.1",
|
||||||
"n8n-core": "~0.129.0",
|
"n8n-core": "~0.131.0",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"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",
|
||||||
|
@ -53963,7 +53993,7 @@
|
||||||
},
|
},
|
||||||
"packages/nodes-base": {
|
"packages/nodes-base": {
|
||||||
"name": "n8n-nodes-base",
|
"name": "n8n-nodes-base",
|
||||||
"version": "0.187.0",
|
"version": "0.189.0",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kafkajs/confluent-schema-registry": "1.0.6",
|
"@kafkajs/confluent-schema-registry": "1.0.6",
|
||||||
|
@ -54001,7 +54031,7 @@
|
||||||
"mqtt": "4.2.6",
|
"mqtt": "4.2.6",
|
||||||
"mssql": "^8.1.2",
|
"mssql": "^8.1.2",
|
||||||
"mysql2": "~2.3.0",
|
"mysql2": "~2.3.0",
|
||||||
"n8n-core": "~0.129.0",
|
"n8n-core": "~0.131.0",
|
||||||
"node-html-markdown": "^1.1.3",
|
"node-html-markdown": "^1.1.3",
|
||||||
"node-ssh": "^12.0.0",
|
"node-ssh": "^12.0.0",
|
||||||
"nodemailer": "^6.7.1",
|
"nodemailer": "^6.7.1",
|
||||||
|
@ -54055,7 +54085,7 @@
|
||||||
"eslint-plugin-n8n-nodes-base": "^1.5.4",
|
"eslint-plugin-n8n-nodes-base": "^1.5.4",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"jest": "^27.4.7",
|
"jest": "^27.4.7",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^27.1.3",
|
||||||
"tslint": "^6.1.2",
|
"tslint": "^6.1.2",
|
||||||
"typescript": "~4.6.0"
|
"typescript": "~4.6.0"
|
||||||
|
@ -54063,7 +54093,7 @@
|
||||||
},
|
},
|
||||||
"packages/workflow": {
|
"packages/workflow": {
|
||||||
"name": "n8n-workflow",
|
"name": "n8n-workflow",
|
||||||
"version": "0.111.0",
|
"version": "0.113.0",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@n8n_io/riot-tmpl": "^1.0.1",
|
"@n8n_io/riot-tmpl": "^1.0.1",
|
||||||
|
@ -84006,10 +84036,10 @@
|
||||||
"lodash.split": "^4.4.2",
|
"lodash.split": "^4.4.2",
|
||||||
"lodash.unset": "^4.5.2",
|
"lodash.unset": "^4.5.2",
|
||||||
"mysql2": "~2.3.0",
|
"mysql2": "~2.3.0",
|
||||||
"n8n-core": "~0.129.0",
|
"n8n-core": "~0.131.0",
|
||||||
"n8n-editor-ui": "~0.155.0",
|
"n8n-editor-ui": "~0.157.0",
|
||||||
"n8n-nodes-base": "~0.187.0",
|
"n8n-nodes-base": "~0.189.0",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"nodemailer": "^6.7.1",
|
"nodemailer": "^6.7.1",
|
||||||
"nodemon": "^2.0.2",
|
"nodemon": "^2.0.2",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
|
@ -84020,6 +84050,7 @@
|
||||||
"passport-cookie": "^1.0.9",
|
"passport-cookie": "^1.0.9",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"pg": "^8.3.0",
|
"pg": "^8.3.0",
|
||||||
|
"posthog-node": "^1.3.0",
|
||||||
"prom-client": "^13.1.0",
|
"prom-client": "^13.1.0",
|
||||||
"psl": "^1.8.0",
|
"psl": "^1.8.0",
|
||||||
"run-script-os": "^1.0.7",
|
"run-script-os": "^1.0.7",
|
||||||
|
@ -84061,7 +84092,7 @@
|
||||||
"jest": "^27.4.7",
|
"jest": "^27.4.7",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"p-cancelable": "^2.0.0",
|
"p-cancelable": "^2.0.0",
|
||||||
"qs": "^6.10.1",
|
"qs": "^6.10.1",
|
||||||
|
@ -84187,8 +84218,8 @@
|
||||||
"luxon": "^2.3.0",
|
"luxon": "^2.3.0",
|
||||||
"monaco-editor": "^0.30.1",
|
"monaco-editor": "^0.30.1",
|
||||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||||
"n8n-design-system": "~0.29.0",
|
"n8n-design-system": "~0.31.0",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"normalize-wheel": "^1.0.1",
|
"normalize-wheel": "^1.0.1",
|
||||||
"prismjs": "^1.17.1",
|
"prismjs": "^1.17.1",
|
||||||
"quill": "^2.0.0-dev.3",
|
"quill": "^2.0.0-dev.3",
|
||||||
|
@ -84234,8 +84265,8 @@
|
||||||
"change-case": "^4.1.1",
|
"change-case": "^4.1.1",
|
||||||
"copyfiles": "^2.1.1",
|
"copyfiles": "^2.1.1",
|
||||||
"inquirer": "^7.0.1",
|
"inquirer": "^7.0.1",
|
||||||
"n8n-core": "~0.129.0",
|
"n8n-core": "~0.131.0",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"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",
|
||||||
|
@ -84312,8 +84343,8 @@
|
||||||
"mqtt": "4.2.6",
|
"mqtt": "4.2.6",
|
||||||
"mssql": "^8.1.2",
|
"mssql": "^8.1.2",
|
||||||
"mysql2": "~2.3.0",
|
"mysql2": "~2.3.0",
|
||||||
"n8n-core": "~0.129.0",
|
"n8n-core": "~0.131.0",
|
||||||
"n8n-workflow": "~0.111.0",
|
"n8n-workflow": "~0.113.0",
|
||||||
"node-html-markdown": "^1.1.3",
|
"node-html-markdown": "^1.1.3",
|
||||||
"node-ssh": "^12.0.0",
|
"node-ssh": "^12.0.0",
|
||||||
"nodemailer": "^6.7.1",
|
"nodemailer": "^6.7.1",
|
||||||
|
@ -87238,6 +87269,31 @@
|
||||||
"xtend": "^4.0.0"
|
"xtend": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"posthog-node": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-2+VhqiY/rKIqKIXyvemBFHbeijHE25sP7eKltnqcFqAssUE6+sX6vusN9A4luzToOqHQkUZexiCKxvuGagh7JA==",
|
||||||
|
"requires": {
|
||||||
|
"axios": "0.24.0",
|
||||||
|
"axios-retry": "^3.1.9",
|
||||||
|
"component-type": "^1.2.1",
|
||||||
|
"join-component": "^1.1.0",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"remove-trailing-slash": "^0.1.1",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": {
|
||||||
|
"version": "0.24.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||||
|
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.14.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"prelude-ls": {
|
"prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
|
|
|
@ -681,6 +681,13 @@ export const schema = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
externalFrontendHooksUrls: {
|
||||||
|
doc: 'URLs to external frontend hooks files, ; separated',
|
||||||
|
format: String,
|
||||||
|
default: 'https://public-stage.n8n.cloud/posthog-hooks.js',
|
||||||
|
env: 'EXTERNAL_FRONTEND_HOOKS_URLS',
|
||||||
|
},
|
||||||
|
|
||||||
externalHookFiles: {
|
externalHookFiles: {
|
||||||
doc: 'Files containing external hooks. Multiple files can be separated by colon (":")',
|
doc: 'Files containing external hooks. Multiple files can be separated by colon (":")',
|
||||||
format: String,
|
format: String,
|
||||||
|
@ -888,6 +895,26 @@ export const schema = {
|
||||||
env: 'N8N_DIAGNOSTICS_ENABLED',
|
env: 'N8N_DIAGNOSTICS_ENABLED',
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
posthog: {
|
||||||
|
apiKey: {
|
||||||
|
doc: 'API key for PostHog',
|
||||||
|
format: String,
|
||||||
|
default: 'phc_4URIAm1uYfJO7j8kWSe0J8lc8IqnstRLS7Jx8NcakHo',
|
||||||
|
env: 'N8N_DIAGNOSTICS_POSTHOG_API_KEY',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
doc: 'API host for PostHog',
|
||||||
|
format: String,
|
||||||
|
default: 'https://app.posthog.com',
|
||||||
|
env: 'N8N_DIAGNOSTICS_POSTHOG_API_HOST',
|
||||||
|
},
|
||||||
|
disableSessionRecording: {
|
||||||
|
doc: 'Disable posthog session recording',
|
||||||
|
format: Boolean,
|
||||||
|
default: true,
|
||||||
|
env: 'N8N_DIAGNOSTICS_POSTHOG_DISABLE_RECORDING',
|
||||||
|
},
|
||||||
|
},
|
||||||
frontend: {
|
frontend: {
|
||||||
doc: 'Diagnostics config for frontend.',
|
doc: 'Diagnostics config for frontend.',
|
||||||
format: String,
|
format: String,
|
||||||
|
|
|
@ -157,6 +157,7 @@
|
||||||
"passport-cookie": "^1.0.9",
|
"passport-cookie": "^1.0.9",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"pg": "^8.3.0",
|
"pg": "^8.3.0",
|
||||||
|
"posthog-node": "^1.3.0",
|
||||||
"prom-client": "^13.1.0",
|
"prom-client": "^13.1.0",
|
||||||
"psl": "^1.8.0",
|
"psl": "^1.8.0",
|
||||||
"shelljs": "^0.8.5",
|
"shelljs": "^0.8.5",
|
||||||
|
|
|
@ -516,6 +516,9 @@ export interface IN8nUISettings {
|
||||||
missingPackages?: boolean;
|
missingPackages?: boolean;
|
||||||
executionMode: 'regular' | 'queue';
|
executionMode: 'regular' | 'queue';
|
||||||
communityNodesEnabled: boolean;
|
communityNodesEnabled: boolean;
|
||||||
|
deployment: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
isNpmAvailable: boolean;
|
isNpmAvailable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,16 +106,20 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
(note) => note.overlapping,
|
(note) => note.overlapping,
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
return this.telemetry.track('User saved workflow', {
|
return this.telemetry.track(
|
||||||
user_id: userId,
|
'User saved workflow',
|
||||||
workflow_id: workflow.id,
|
{
|
||||||
node_graph_string: JSON.stringify(nodeGraph),
|
user_id: userId,
|
||||||
notes_count_overlapping: overlappingCount,
|
workflow_id: workflow.id,
|
||||||
notes_count_non_overlapping: notesCount - overlappingCount,
|
node_graph_string: JSON.stringify(nodeGraph),
|
||||||
version_cli: this.versionCli,
|
notes_count_overlapping: overlappingCount,
|
||||||
num_tags: workflow.tags?.length ?? 0,
|
notes_count_non_overlapping: notesCount - overlappingCount,
|
||||||
public_api: publicApi,
|
version_cli: this.versionCli,
|
||||||
});
|
num_tags: workflow.tags?.length ?? 0,
|
||||||
|
public_api: publicApi,
|
||||||
|
},
|
||||||
|
{ withPostHog: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onWorkflowPostExecute(
|
async onWorkflowPostExecute(
|
||||||
|
@ -197,14 +201,18 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runData.data.startData?.destinationNode) {
|
if (runData.data.startData?.destinationNode) {
|
||||||
|
const telemetryPayload = {
|
||||||
|
...manualExecEventProperties,
|
||||||
|
node_type: TelemetryHelpers.getNodeTypeForName(
|
||||||
|
workflow,
|
||||||
|
runData.data.startData?.destinationNode,
|
||||||
|
)?.type,
|
||||||
|
node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode],
|
||||||
|
};
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.telemetry.track('Manual node exec finished', {
|
this.telemetry.track('Manual node exec finished', telemetryPayload, {
|
||||||
...manualExecEventProperties,
|
withPostHog: true,
|
||||||
node_type: TelemetryHelpers.getNodeTypeForName(
|
|
||||||
workflow,
|
|
||||||
runData.data.startData?.destinationNode,
|
|
||||||
)?.type,
|
|
||||||
node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode],
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -219,7 +227,9 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
});
|
});
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.telemetry.track('Manual workflow exec finished', manualExecEventProperties),
|
this.telemetry.track('Manual workflow exec finished', manualExecEventProperties, {
|
||||||
|
withPostHog: true,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,8 @@ import {
|
||||||
isUserManagementEnabled,
|
isUserManagementEnabled,
|
||||||
} from './UserManagement/UserManagementHelper';
|
} from './UserManagement/UserManagementHelper';
|
||||||
import { loadPublicApiVersions } from './PublicApi';
|
import { loadPublicApiVersions } from './PublicApi';
|
||||||
|
import { SharedWorkflow } from './databases/entities/SharedWorkflow';
|
||||||
|
import * as telemetryScripts from './telemetry/scripts';
|
||||||
|
|
||||||
require('body-parser-xml')(bodyParser);
|
require('body-parser-xml')(bodyParser);
|
||||||
|
|
||||||
|
@ -334,6 +336,9 @@ class App {
|
||||||
onboardingCallPromptEnabled: config.getEnv('onboardingCallPrompt.enabled'),
|
onboardingCallPromptEnabled: config.getEnv('onboardingCallPrompt.enabled'),
|
||||||
executionMode: config.getEnv('executions.mode'),
|
executionMode: config.getEnv('executions.mode'),
|
||||||
communityNodesEnabled: config.getEnv('nodes.communityPackages.enabled'),
|
communityNodesEnabled: config.getEnv('nodes.communityPackages.enabled'),
|
||||||
|
deployment: {
|
||||||
|
type: config.getEnv('deployment.type'),
|
||||||
|
},
|
||||||
isNpmAvailable: false,
|
isNpmAvailable: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2611,6 +2616,36 @@ class App {
|
||||||
readIndexFile = readIndexFile.replace(/\/%BASE_PATH%\//g, n8nPath);
|
readIndexFile = readIndexFile.replace(/\/%BASE_PATH%\//g, n8nPath);
|
||||||
readIndexFile = readIndexFile.replace(/\/favicon.ico/g, `${n8nPath}favicon.ico`);
|
readIndexFile = readIndexFile.replace(/\/favicon.ico/g, `${n8nPath}favicon.ico`);
|
||||||
|
|
||||||
|
const hooksUrls = config.getEnv('externalFrontendHooksUrls');
|
||||||
|
|
||||||
|
let scriptsString = '';
|
||||||
|
|
||||||
|
if (hooksUrls) {
|
||||||
|
scriptsString = hooksUrls.split(';').reduce((acc, curr) => {
|
||||||
|
return `${acc}<script src="${curr}"></script>`;
|
||||||
|
}, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.frontendSettings.telemetry.enabled) {
|
||||||
|
const phLoadingScript = telemetryScripts.createPostHogLoadingScript({
|
||||||
|
apiKey: config.getEnv('diagnostics.config.posthog.apiKey'),
|
||||||
|
apiHost: config.getEnv('diagnostics.config.posthog.apiHost'),
|
||||||
|
autocapture: false,
|
||||||
|
disableSessionRecording: config.getEnv(
|
||||||
|
'diagnostics.config.posthog.disableSessionRecording',
|
||||||
|
),
|
||||||
|
debug: config.getEnv('logs.level') === 'debug',
|
||||||
|
});
|
||||||
|
|
||||||
|
scriptsString += phLoadingScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstLinkedScriptSegment = '<link href="/js/';
|
||||||
|
readIndexFile = readIndexFile.replace(
|
||||||
|
firstLinkedScriptSegment,
|
||||||
|
scriptsString + firstLinkedScriptSegment,
|
||||||
|
);
|
||||||
|
|
||||||
// Serve the altered index.html file separately
|
// Serve the altered index.html file separately
|
||||||
this.app.get(`/index.html`, async (req: express.Request, res: express.Response) => {
|
this.app.get(`/index.html`, async (req: express.Request, res: express.Response) => {
|
||||||
res.send(readIndexFile);
|
res.send(readIndexFile);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* eslint-disable import/no-cycle */
|
/* eslint-disable import/no-cycle */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
import TelemetryClient from '@rudderstack/rudder-sdk-node';
|
import RudderStack from '@rudderstack/rudder-sdk-node';
|
||||||
|
import PostHog from 'posthog-node';
|
||||||
import { ITelemetryTrackProperties, LoggerProxy } from 'n8n-workflow';
|
import { ITelemetryTrackProperties, LoggerProxy } from 'n8n-workflow';
|
||||||
import * as config from '../../config';
|
import config from '../../config';
|
||||||
import { IExecutionTrackProperties } from '../Interfaces';
|
import { IExecutionTrackProperties } from '../Interfaces';
|
||||||
import { getLogger } from '../Logger';
|
import { getLogger } from '../Logger';
|
||||||
|
|
||||||
|
@ -24,7 +25,9 @@ interface IExecutionsBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Telemetry {
|
export class Telemetry {
|
||||||
private client?: TelemetryClient;
|
private rudderStack?: RudderStack;
|
||||||
|
|
||||||
|
private postHog?: PostHog;
|
||||||
|
|
||||||
private instanceId: string;
|
private instanceId: string;
|
||||||
|
|
||||||
|
@ -51,18 +54,19 @@ export class Telemetry {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = this.createTelemetryClient(key, url, logLevel);
|
this.rudderStack = this.initRudderStack(key, url, logLevel);
|
||||||
|
this.postHog = this.initPostHog();
|
||||||
|
|
||||||
this.startPulse();
|
this.startPulse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTelemetryClient(
|
private initRudderStack(key: string, url: string, logLevel: string): RudderStack {
|
||||||
key: string,
|
return new RudderStack(key, url, { logLevel });
|
||||||
url: string,
|
}
|
||||||
logLevel: string,
|
|
||||||
): TelemetryClient | undefined {
|
private initPostHog(): PostHog {
|
||||||
return new TelemetryClient(key, url, { logLevel });
|
return new PostHog(config.getEnv('diagnostics.config.posthog.apiKey'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private startPulse() {
|
private startPulse() {
|
||||||
|
@ -72,7 +76,7 @@ export class Telemetry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pulse(): Promise<unknown> {
|
private async pulse(): Promise<unknown> {
|
||||||
if (!this.client) {
|
if (!this.rudderStack) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +96,7 @@ export class Telemetry {
|
||||||
}
|
}
|
||||||
|
|
||||||
async trackWorkflowExecution(properties: IExecutionTrackProperties): Promise<void> {
|
async trackWorkflowExecution(properties: IExecutionTrackProperties): Promise<void> {
|
||||||
if (this.client) {
|
if (this.rudderStack) {
|
||||||
const execTime = new Date();
|
const execTime = new Date();
|
||||||
const workflowId = properties.workflow_id;
|
const workflowId = properties.workflow_id;
|
||||||
|
|
||||||
|
@ -122,8 +126,12 @@ export class Telemetry {
|
||||||
clearInterval(this.pulseIntervalReference);
|
clearInterval(this.pulseIntervalReference);
|
||||||
void this.track('User instance stopped');
|
void this.track('User instance stopped');
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
if (this.client) {
|
if (this.postHog) {
|
||||||
this.client.flush(resolve);
|
this.postHog.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rudderStack) {
|
||||||
|
this.rudderStack.flush(resolve);
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
@ -134,8 +142,18 @@ export class Telemetry {
|
||||||
[key: string]: string | number | boolean | object | undefined | null;
|
[key: string]: string | number | boolean | object | undefined | null;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
if (this.client) {
|
if (this.postHog) {
|
||||||
this.client.identify(
|
this.postHog.identify({
|
||||||
|
distinctId: this.instanceId,
|
||||||
|
properties: {
|
||||||
|
...traits,
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rudderStack) {
|
||||||
|
this.rudderStack.identify(
|
||||||
{
|
{
|
||||||
userId: this.instanceId,
|
userId: this.instanceId,
|
||||||
anonymousId: '000000000000',
|
anonymousId: '000000000000',
|
||||||
|
@ -152,9 +170,13 @@ export class Telemetry {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async track(eventName: string, properties: ITelemetryTrackProperties = {}): Promise<void> {
|
async track(
|
||||||
|
eventName: string,
|
||||||
|
properties: ITelemetryTrackProperties = {},
|
||||||
|
{ withPostHog } = { withPostHog: false }, // whether to additionally track with PostHog
|
||||||
|
): Promise<void> {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
if (this.client) {
|
if (this.rudderStack) {
|
||||||
const { user_id } = properties;
|
const { user_id } = properties;
|
||||||
const updatedProperties: ITelemetryTrackProperties = {
|
const updatedProperties: ITelemetryTrackProperties = {
|
||||||
...properties,
|
...properties,
|
||||||
|
@ -162,21 +184,35 @@ export class Telemetry {
|
||||||
version_cli: this.versionCli,
|
version_cli: this.versionCli,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client.track(
|
const payload = {
|
||||||
{
|
userId: `${this.instanceId}${user_id ? `#${user_id}` : ''}`,
|
||||||
userId: `${this.instanceId}${user_id ? `#${user_id}` : ''}`,
|
anonymousId: '000000000000',
|
||||||
anonymousId: '000000000000',
|
event: eventName,
|
||||||
event: eventName,
|
properties: updatedProperties,
|
||||||
properties: updatedProperties,
|
};
|
||||||
},
|
|
||||||
resolve,
|
if (withPostHog && this.postHog) {
|
||||||
);
|
this.postHog.capture({ ...payload, distinctId: payload.userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rudderStack.track(payload, resolve);
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isFeatureFlagEnabled(
|
||||||
|
featureFlagName: string,
|
||||||
|
{ user_id: userId }: ITelemetryTrackProperties = {},
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (!this.postHog) return Promise.resolve(false);
|
||||||
|
|
||||||
|
const fullId = [this.instanceId, userId].join('_'); // PostHog disallows # in ID
|
||||||
|
|
||||||
|
return this.postHog.isFeatureEnabled(featureFlagName, fullId);
|
||||||
|
}
|
||||||
|
|
||||||
// test helpers
|
// test helpers
|
||||||
|
|
||||||
getCountsBuffer(): IExecutionsBuffer {
|
getCountsBuffer(): IExecutionsBuffer {
|
||||||
|
|
17
packages/cli/src/telemetry/scripts.ts
Normal file
17
packages/cli/src/telemetry/scripts.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* Create a script to init PostHog, for embedding before the Vue bundle in `<head>` in `index.html`.
|
||||||
|
*/
|
||||||
|
export const createPostHogLoadingScript = ({
|
||||||
|
apiKey,
|
||||||
|
apiHost,
|
||||||
|
autocapture,
|
||||||
|
disableSessionRecording,
|
||||||
|
debug,
|
||||||
|
}: {
|
||||||
|
apiKey: string;
|
||||||
|
apiHost: string;
|
||||||
|
autocapture: boolean;
|
||||||
|
disableSessionRecording: boolean;
|
||||||
|
debug: boolean;
|
||||||
|
}) =>
|
||||||
|
`<script>!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);posthog.init('${apiKey}',{api_host:'${apiHost}', autocapture: ${autocapture.toString()}, disable_session_recording: ${disableSessionRecording.toString()}, debug:${debug.toString()}})</script>`;
|
|
@ -1,7 +1,7 @@
|
||||||
import { Telemetry } from '../../src/telemetry';
|
import { Telemetry } from '../../src/telemetry';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
|
|
||||||
jest.spyOn(Telemetry.prototype as any, 'createTelemetryClient').mockImplementation(() => {
|
jest.spyOn(Telemetry.prototype as any, 'initRudderStack').mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
flush: () => {},
|
flush: () => {},
|
||||||
identify: () => {},
|
identify: () => {},
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<n8n-text :bold="true">{{email}}</n8n-text>
|
<n8n-text :bold="true">{{email}}</n8n-text>
|
||||||
<span :class="$style.pendingBadge"><n8n-badge :bold="true">Pending</n8n-badge></span>
|
<span :class="$style.pendingBadge"><n8n-badge :bold="true">Pending</n8n-badge></span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="$style.infoContainer">
|
<div v-else :class="$style.infoContainer" ref="userInfo">
|
||||||
<div>
|
<div>
|
||||||
<n8n-text :bold="true">{{firstName}} {{lastName}} {{isCurrentUser ? this.t('nds.userInfo.you') : ''}}</n8n-text>
|
<n8n-text :bold="true">{{firstName}} {{lastName}} {{isCurrentUser ? this.t('nds.userInfo.you') : ''}}</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,9 +26,10 @@ import N8nText from '../N8nText';
|
||||||
import N8nAvatar from '../N8nAvatar';
|
import N8nAvatar from '../N8nAvatar';
|
||||||
import N8nBadge from '../N8nBadge';
|
import N8nBadge from '../N8nBadge';
|
||||||
import Locale from '../../mixins/locale';
|
import Locale from '../../mixins/locale';
|
||||||
|
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
export default mixins(Locale).extend({
|
export default mixins(Locale, externalHooks).extend({
|
||||||
name: 'n8n-users-info',
|
name: 'n8n-users-info',
|
||||||
components: {
|
components: {
|
||||||
N8nAvatar,
|
N8nAvatar,
|
||||||
|
@ -52,6 +53,9 @@ export default mixins(Locale).extend({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$externalHooks().run('userInfo.mounted', { userInfoRef: this.$refs.userInfo });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
1
packages/design-system/src/main.d.ts
vendored
1
packages/design-system/src/main.d.ts
vendored
|
@ -14,3 +14,4 @@ declare module 'n8n-design-system' {
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
export { locale } from './locale';
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div>
|
<div>
|
||||||
<n8n-input-label :label="label">
|
<n8n-input-label :label="label">
|
||||||
<div :class="{[$style.copyText]: true, [$style[size]]: true, [$style.collapsed]: collapse}" @click="copy">
|
<div :class="{[$style.copyText]: true, [$style[size]]: true, [$style.collapsed]: collapse}" @click="copy">
|
||||||
<span>{{ value }}</span>
|
<span ref="copyInputValue">{{ value }}</span>
|
||||||
<div :class="$style.copyButton"><span>{{ copyButtonText }}</span></div>
|
<div :class="$style.copyButton"><span>{{ copyButtonText }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</n8n-input-label>
|
</n8n-input-label>
|
||||||
|
@ -62,6 +62,9 @@ export default mixins(copyPaste, showMessage).extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$externalHooks().run('copyInput.mounted', { copyInputValueRef: this.$refs.copyInputValue });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -702,6 +702,7 @@ export default mixins(showMessage, nodeHelpers).extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$telemetry.track('User saved credentials', trackProperties);
|
this.$telemetry.track('User saved credentials', trackProperties);
|
||||||
|
this.$externalHooks().run('credentialEdit.saveCredential', trackProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
return credential;
|
return credential;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="credentialsToDisplay" v-loading="loading" :default-sort = "{prop: 'name', order: 'ascending'}" stripe max-height="450" @row-click="editCredential">
|
<el-table :data="credentialsToDisplay" v-loading="loading" :default-sort = "{prop: 'name', order: 'ascending'}" stripe max-height="450" @row-click="editCredential" ref="table">
|
||||||
<el-table-column property="name" :label="$locale.baseText('credentialsList.name')" class-name="clickable" sortable></el-table-column>
|
<el-table-column property="name" :label="$locale.baseText('credentialsList.name')" class-name="clickable" sortable></el-table-column>
|
||||||
<el-table-column property="type" :label="$locale.baseText('credentialsList.type')" class-name="clickable" sortable></el-table-column>
|
<el-table-column property="type" :label="$locale.baseText('credentialsList.type')" class-name="clickable" sortable></el-table-column>
|
||||||
<el-table-column property="createdAt" :label="$locale.baseText('credentialsList.created')" class-name="clickable" sortable></el-table-column>
|
<el-table-column property="createdAt" :label="$locale.baseText('credentialsList.created')" class-name="clickable" sortable></el-table-column>
|
||||||
|
@ -96,8 +96,9 @@ export default mixins(
|
||||||
this.$showError(e, this.$locale.baseText('credentialsList.errorLoadingCredentials'));
|
this.$showError(e, this.$locale.baseText('credentialsList.errorLoadingCredentials'));
|
||||||
}
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
this.$externalHooks().run('credentialsList.mounted', {
|
||||||
this.$externalHooks().run('credentialsList.mounted');
|
tableRef: this.$refs['table'],
|
||||||
|
});
|
||||||
this.$telemetry.track('User opened Credentials panel', { workflow_id: this.$store.getters.workflowId });
|
this.$telemetry.track('User opened Credentials panel', { workflow_id: this.$store.getters.workflowId });
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
|
|
|
@ -51,11 +51,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
import Modal from './Modal.vue';
|
import Modal from './Modal.vue';
|
||||||
import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants';
|
import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants';
|
||||||
|
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default mixins(externalHooks).extend({
|
||||||
name: 'CredentialsSelectModal',
|
name: 'CredentialsSelectModal',
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
|
@ -92,7 +94,16 @@ export default Vue.extend({
|
||||||
openCredentialType () {
|
openCredentialType () {
|
||||||
this.modalBus.$emit('close');
|
this.modalBus.$emit('close');
|
||||||
this.$store.dispatch('ui/openNewCredential', { type: this.selected });
|
this.$store.dispatch('ui/openNewCredential', { type: this.selected });
|
||||||
this.$telemetry.track('User opened Credential modal', { credential_type: this.selected, source: 'primary_menu', new_credential: true, workflow_id: this.$store.getters.workflowId });
|
|
||||||
|
const telemetryPayload = {
|
||||||
|
credential_type: this.selected,
|
||||||
|
source: 'primary_menu',
|
||||||
|
new_credential: true,
|
||||||
|
workflow_id: this.$store.getters.workflowId,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$telemetry.track('User opened Credential modal', telemetryPayload);
|
||||||
|
this.$externalHooks().run('credentialsSelectModal.openCredentialType', telemetryPayload);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
>
|
>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
|
|
||||||
<div class="filters">
|
<div class="filters" ref="filters">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="2" class="filter-headline">
|
<el-col :span="2" class="filter-headline">
|
||||||
{{ $locale.baseText('executionsList.filters') }}:
|
{{ $locale.baseText('executionsList.filters') }}:
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="combinedExecutions" stripe v-loading="isDataLoading" :row-class-name="getRowClass">
|
<el-table :data="combinedExecutions" stripe v-loading="isDataLoading" :row-class-name="getRowClass" ref="table">
|
||||||
<el-table-column label="" width="30">
|
<el-table-column label="" width="30">
|
||||||
<!-- eslint-disable-next-line vue/no-unused-vars -->
|
<!-- eslint-disable-next-line vue/no-unused-vars -->
|
||||||
<template slot="header" slot-scope="scope">
|
<template slot="header" slot-scope="scope">
|
||||||
|
@ -245,6 +245,11 @@ export default mixins(
|
||||||
|
|
||||||
this.$externalHooks().run('executionsList.openDialog');
|
this.$externalHooks().run('executionsList.openDialog');
|
||||||
this.$telemetry.track('User opened Executions log', { workflow_id: this.$store.getters.workflowId });
|
this.$telemetry.track('User opened Executions log', { workflow_id: this.$store.getters.workflowId });
|
||||||
|
|
||||||
|
this.$externalHooks().run('executionsList.created', {
|
||||||
|
tableRef: this.$refs['table'],
|
||||||
|
filtersRef: this.$refs['filters'],
|
||||||
|
});
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.autoRefreshInterval) {
|
if (this.autoRefreshInterval) {
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<div class="editor-description">
|
<div class="editor-description">
|
||||||
{{ $locale.baseText('expressionEdit.expression') }}
|
{{ $locale.baseText('expressionEdit.expression') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="expression-editor">
|
<div class="expression-editor" ref="expressionInput">
|
||||||
<expression-input :parameter="parameter" ref="inputFieldExpression" rows="8" :value="value" :path="path" @change="valueChanged" @keydown.stop="noOp"></expression-input>
|
<expression-input :parameter="parameter" ref="inputFieldExpression" rows="8" :value="value" :path="path" @change="valueChanged" @keydown.stop="noOp"></expression-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,9 @@
|
||||||
<div class="editor-description">
|
<div class="editor-description">
|
||||||
{{ $locale.baseText('expressionEdit.result') }}
|
{{ $locale.baseText('expressionEdit.result') }}
|
||||||
</div>
|
</div>
|
||||||
<expression-input :parameter="parameter" resolvedValue="true" ref="expressionResult" rows="8" :value="displayValue" :path="path"></expression-input>
|
<div ref="expressionOutput">
|
||||||
|
<expression-input :parameter="parameter" resolvedValue="true" ref="expressionResult" rows="8" :value="displayValue" :path="path"></expression-input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -74,6 +76,17 @@ export default mixins(
|
||||||
latestValue: '',
|
latestValue: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
updated() {
|
||||||
|
if (this.$refs.expressionInput && this.$refs.expressionOutput) {
|
||||||
|
this.$externalHooks().run(
|
||||||
|
'expressionEdit.mounted',
|
||||||
|
{
|
||||||
|
expressionInputRef: this.$refs.expressionInput,
|
||||||
|
expressionOutputRef: this.$refs.expressionOutput,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
valueChanged (value: string, forceUpdate = false) {
|
valueChanged (value: string, forceUpdate = false) {
|
||||||
this.latestValue = value;
|
this.latestValue = value;
|
||||||
|
@ -167,14 +180,16 @@ export default mixins(
|
||||||
this.$externalHooks().run('expressionEdit.dialogVisibleChanged', { dialogVisible: newValue, parameter: this.parameter, value: this.value, resolvedExpressionValue });
|
this.$externalHooks().run('expressionEdit.dialogVisibleChanged', { dialogVisible: newValue, parameter: this.parameter, value: this.value, resolvedExpressionValue });
|
||||||
|
|
||||||
if (!newValue) {
|
if (!newValue) {
|
||||||
this.$telemetry.track('User closed Expression Editor', {
|
const telemetryPayload = {
|
||||||
empty_expression: (this.value === '=') || (this.value === '={{}}') || !this.value,
|
empty_expression: (this.value === '=') || (this.value === '={{}}') || !this.value,
|
||||||
workflow_id: this.$store.getters.workflowId,
|
workflow_id: this.$store.getters.workflowId,
|
||||||
source: this.eventSource,
|
source: this.eventSource,
|
||||||
session_id: this.$store.getters['ui/ndvSessionId'],
|
session_id: this.$store.getters['ui/ndvSessionId'],
|
||||||
has_parameter: this.value.includes('$parameter'),
|
has_parameter: this.value.includes('$parameter'),
|
||||||
has_mapping: hasExpressionMapping(this.value),
|
has_mapping: hasExpressionMapping(this.value),
|
||||||
});
|
};
|
||||||
|
this.$telemetry.track('User closed Expression Editor', telemetryPayload);
|
||||||
|
this.$externalHooks().run('expressionEdit.closeDialog', telemetryPayload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -146,14 +146,16 @@
|
||||||
<span slot="title" class="item-title-root">{{nextVersions.length > 99 ? '99+' : nextVersions.length}} update{{nextVersions.length > 1 ? 's' : ''}} available</span>
|
<span slot="title" class="item-title-root">{{nextVersions.length > 99 ? '99+' : nextVersions.length}} update{{nextVersions.length > 1 ? 's' : ''}} available</span>
|
||||||
</n8n-menu-item>
|
</n8n-menu-item>
|
||||||
<el-dropdown placement="right-end" trigger="click" @command="onUserActionToggle" v-if="canUserAccessSidebarUserInfo && currentUser">
|
<el-dropdown placement="right-end" trigger="click" @command="onUserActionToggle" v-if="canUserAccessSidebarUserInfo && currentUser">
|
||||||
<n8n-menu-item class="user">
|
<div ref="user">
|
||||||
<div class="avatar">
|
<n8n-menu-item class="user">
|
||||||
<n8n-avatar :firstName="currentUser.firstName" :lastName="currentUser.lastName" size="small" />
|
<div class="avatar">
|
||||||
</div>
|
<n8n-avatar :firstName="currentUser.firstName" :lastName="currentUser.lastName" size="small" />
|
||||||
<span slot="title" class="item-title-root" v-if="!isCollapsed">
|
</div>
|
||||||
{{currentUser.fullName}}
|
<span slot="title" class="item-title-root" v-if="!isCollapsed">
|
||||||
</span>
|
{{currentUser.fullName}}
|
||||||
</n8n-menu-item>
|
</span>
|
||||||
|
</n8n-menu-item>
|
||||||
|
</div>
|
||||||
<el-dropdown-menu slot="dropdown">
|
<el-dropdown-menu slot="dropdown">
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
command="settings"
|
command="settings"
|
||||||
|
@ -359,6 +361,11 @@ export default mixins(
|
||||||
return this.$route.meta && this.$route.meta.nodeView;
|
return this.$route.meta && this.$route.meta.nodeView;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$refs.user) {
|
||||||
|
this.$externalHooks().run('mainSidebar.mounted', { userRef: this.$refs.user });
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
trackHelpItemClick (itemType: string) {
|
trackHelpItemClick (itemType: string) {
|
||||||
this.$telemetry.track('User clicked help resource', { type: itemType, workflow_id: this.$store.getters.workflowId });
|
this.$telemetry.track('User clicked help resource', { type: itemType, workflow_id: this.$store.getters.workflowId });
|
||||||
|
|
|
@ -179,7 +179,14 @@ export default mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.hasPinData || shouldUnpinAndExecute) {
|
if (!this.hasPinData || shouldUnpinAndExecute) {
|
||||||
this.$telemetry.track('User clicked execute node button', { node_type: this.nodeType ? this.nodeType.name : null, workflow_id: this.$store.getters.workflowId, source: this.telemetrySource });
|
const telemetryPayload = {
|
||||||
|
node_type: this.nodeType ? this.nodeType.name : null,
|
||||||
|
workflow_id: this.$store.getters.workflowId,
|
||||||
|
source: this.telemetrySource,
|
||||||
|
};
|
||||||
|
this.$telemetry.track('User clicked execute node button', telemetryPayload);
|
||||||
|
this.$externalHooks().run('nodeExecuteButton.onClick', telemetryPayload);
|
||||||
|
|
||||||
this.runWorkflow(this.nodeName, 'RunData.ExecuteNodeButton');
|
this.runWorkflow(this.nodeName, 'RunData.ExecuteNodeButton');
|
||||||
this.$emit('execute');
|
this.$emit('execute');
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,10 @@
|
||||||
:label="getOptionsOptionDisplayName(option)"
|
:label="getOptionsOptionDisplayName(option)"
|
||||||
>
|
>
|
||||||
<div class="list-option">
|
<div class="list-option">
|
||||||
<div class="option-headline">
|
<div
|
||||||
|
class="option-headline"
|
||||||
|
:class="{ 'remote-parameter-option': isRemoteParameterOption(option) }"
|
||||||
|
>
|
||||||
{{ getOptionsOptionDisplayName(option) }}
|
{{ getOptionsOptionDisplayName(option) }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -689,6 +692,9 @@ export default mixins(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isRemoteParameterOption(option: INodePropertyOptions) {
|
||||||
|
return this.remoteParameterOptions.map(o => o.name).includes(option.name);
|
||||||
|
},
|
||||||
credentialSelected (updateInformation: INodeUpdatePropertiesInformation) {
|
credentialSelected (updateInformation: INodeUpdatePropertiesInformation) {
|
||||||
// Update the values on the node
|
// Update the values on the node
|
||||||
this.$store.commit('updateNodeProperties', updateInformation);
|
this.$store.commit('updateNodeProperties', updateInformation);
|
||||||
|
@ -941,7 +947,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.node && (command === 'addExpression' || command === 'removeExpression')) {
|
if (this.node && (command === 'addExpression' || command === 'removeExpression')) {
|
||||||
this.$telemetry.track('User switched parameter mode', {
|
const telemetryPayload = {
|
||||||
node_type: this.node.type,
|
node_type: this.node.type,
|
||||||
parameter: this.path,
|
parameter: this.path,
|
||||||
old_mode: command === 'addExpression' ? 'fixed': 'expression',
|
old_mode: command === 'addExpression' ? 'fixed': 'expression',
|
||||||
|
@ -949,10 +955,21 @@ export default mixins(
|
||||||
was_parameter_empty: prevValue === '' || prevValue === undefined,
|
was_parameter_empty: prevValue === '' || prevValue === undefined,
|
||||||
had_mapping: hasExpressionMapping(prevValue),
|
had_mapping: hasExpressionMapping(prevValue),
|
||||||
had_parameter: typeof prevValue === 'string' && prevValue.includes('$parameter'),
|
had_parameter: typeof prevValue === 'string' && prevValue.includes('$parameter'),
|
||||||
});
|
};
|
||||||
|
this.$telemetry.track('User switched parameter mode', telemetryPayload);
|
||||||
|
this.$externalHooks().run('parameterInput.modeSwitch', telemetryPayload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
updated () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const remoteParameterOptions = this.$el.querySelectorAll('.remote-parameter-option');
|
||||||
|
|
||||||
|
if (remoteParameterOptions.length > 0) {
|
||||||
|
this.$externalHooks().run('parameterInput.updated', { remoteParameterOptions });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$on('optionSelected', this.optionSelected);
|
this.$on('optionSelected', this.optionSelected);
|
||||||
|
|
||||||
|
|
|
@ -465,7 +465,11 @@ export default mixins(showMessage, workflowHelpers).extend({
|
||||||
this.$data.isSaving = true;
|
this.$data.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('users/submitPersonalizationSurvey', {...values, version: SURVEY_VERSION});
|
const survey = { ...values, version: SURVEY_VERSION };
|
||||||
|
|
||||||
|
this.$externalHooks().run('personalizationModal.onSubmit', survey);
|
||||||
|
|
||||||
|
await this.$store.dispatch('users/submitPersonalizationSurvey', survey);
|
||||||
|
|
||||||
if (Object.keys(values).length === 0) {
|
if (Object.keys(values).length === 0) {
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
|
|
|
@ -488,6 +488,18 @@ export default mixins(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updated() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const jsonValues = this.$el.querySelectorAll('.vjs-value');
|
||||||
|
const tableRows = this.$el.querySelectorAll('tbody tr');
|
||||||
|
|
||||||
|
const elements = [...jsonValues, ...tableRows].reduce<Element[]>((acc, cur) => [...acc, cur], []);
|
||||||
|
|
||||||
|
if (elements.length > 0) {
|
||||||
|
this.$externalHooks().run('runData.updated', { elements });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.hidePinDataDiscoveryTooltip();
|
this.hidePinDataDiscoveryTooltip();
|
||||||
this.eventBus.$off('data-pinning-error', this.onDataPinningError);
|
this.eventBus.$off('data-pinning-error', this.onDataPinningError);
|
||||||
|
@ -802,14 +814,16 @@ export default mixins(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDataPinningSuccess({ source }: { source: 'pin-icon-click' | 'save-edit' }) {
|
onDataPinningSuccess({ source }: { source: 'pin-icon-click' | 'save-edit' }) {
|
||||||
this.$telemetry.track('Ndv data pinning success', {
|
const telemetryPayload = {
|
||||||
pinning_source: source,
|
pinning_source: source,
|
||||||
node_type: this.activeNode.type,
|
node_type: this.activeNode.type,
|
||||||
session_id: this.sessionId,
|
session_id: this.sessionId,
|
||||||
data_size: stringSizeInBytes(this.pinData),
|
data_size: stringSizeInBytes(this.pinData),
|
||||||
view: this.displayMode,
|
view: this.displayMode,
|
||||||
run_index: this.runIndex,
|
run_index: this.runIndex,
|
||||||
});
|
};
|
||||||
|
this.$externalHooks().run('runData.onDataPinningSuccess', telemetryPayload);
|
||||||
|
this.$telemetry.track('Ndv data pinning success', telemetryPayload);
|
||||||
},
|
},
|
||||||
onDataPinningError(
|
onDataPinningError(
|
||||||
{ errorType, source }: {
|
{ errorType, source }: {
|
||||||
|
@ -831,12 +845,15 @@ export default mixins(
|
||||||
{ source }: { source: 'banner-link' | 'pin-icon-click' | 'unpin-and-execute-modal' },
|
{ source }: { source: 'banner-link' | 'pin-icon-click' | 'unpin-and-execute-modal' },
|
||||||
) {
|
) {
|
||||||
if (source === 'pin-icon-click') {
|
if (source === 'pin-icon-click') {
|
||||||
this.$telemetry.track('User clicked pin data icon', {
|
const telemetryPayload = {
|
||||||
node_type: this.activeNode.type,
|
node_type: this.activeNode.type,
|
||||||
session_id: this.sessionId,
|
session_id: this.sessionId,
|
||||||
run_index: this.runIndex,
|
run_index: this.runIndex,
|
||||||
view: !this.hasNodeRun && !this.hasPinData ? 'none' : this.displayMode,
|
view: !this.hasNodeRun && !this.hasPinData ? 'none' : this.displayMode,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
this.$externalHooks().run('runData.onTogglePinData', telemetryPayload);
|
||||||
|
this.$telemetry.track('User clicked pin data icon', telemetryPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateNodeParameterIssues(this.node);
|
this.updateNodeParameterIssues(this.node);
|
||||||
|
|
|
@ -65,10 +65,12 @@
|
||||||
import { LOCAL_STORAGE_MAPPING_FLAG } from '@/constants';
|
import { LOCAL_STORAGE_MAPPING_FLAG } from '@/constants';
|
||||||
import { INodeUi, ITableData } from '@/Interface';
|
import { INodeUi, ITableData } from '@/Interface';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
import Draggable from './Draggable.vue';
|
import Draggable from './Draggable.vue';
|
||||||
import { shorten } from './helpers';
|
import { shorten } from './helpers';
|
||||||
|
import { externalHooks } from './mixins/externalHooks';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default mixins(externalHooks).extend({
|
||||||
name: 'RunDataTable',
|
name: 'RunDataTable',
|
||||||
components: { Draggable },
|
components: { Draggable },
|
||||||
props: {
|
props: {
|
||||||
|
@ -151,7 +153,7 @@ export default Vue.extend({
|
||||||
onDragEnd(column: string) {
|
onDragEnd(column: string) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const mappingTelemetry = this.$store.getters['ui/mappingTelemetry'];
|
const mappingTelemetry = this.$store.getters['ui/mappingTelemetry'];
|
||||||
this.$telemetry.track('User dragged data for mapping', {
|
const telemetryPayload = {
|
||||||
src_node_type: this.node.type,
|
src_node_type: this.node.type,
|
||||||
src_field_name: column,
|
src_field_name: column,
|
||||||
src_nodes_back: this.distanceFromActive,
|
src_nodes_back: this.distanceFromActive,
|
||||||
|
@ -161,7 +163,11 @@ export default Vue.extend({
|
||||||
src_element: 'column',
|
src_element: 'column',
|
||||||
success: false,
|
success: false,
|
||||||
...mappingTelemetry,
|
...mappingTelemetry,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
this.$externalHooks().run('runDataTable.onDragEnd', telemetryPayload);
|
||||||
|
|
||||||
|
this.$telemetry.track('User dragged data for mapping', telemetryPayload);
|
||||||
}, 1000); // ensure dest data gets set if drop
|
}, 1000); // ensure dest data gets set if drop
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="sticky-wrapper" :style="stickyPosition" :id="nodeId">
|
<div class="sticky-wrapper" :style="stickyPosition" :id="nodeId" ref="sticky">
|
||||||
<div
|
<div
|
||||||
:class="{'sticky-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}"
|
:class="{'sticky-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}"
|
||||||
:style="stickySize"
|
:style="stickySize"
|
||||||
|
@ -67,6 +67,9 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
||||||
type: Number,
|
type: Number,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$externalHooks().run('sticky.mounted', { stickyRef: this.$refs['sticky'] });
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
defaultText (): string {
|
defaultText (): string {
|
||||||
if (!this.nodeType) {
|
if (!this.nodeType) {
|
||||||
|
|
|
@ -4,20 +4,23 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { externalHooks } from './mixins/externalHooks';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default mixins(externalHooks).extend({
|
||||||
name: 'Telemetry',
|
name: 'Telemetry',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
initialised: false,
|
isTelemetryInitialized: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('settings', ['telemetry']),
|
...mapGetters('settings', ['telemetry']),
|
||||||
...mapGetters('users', ['currentUserId']),
|
...mapGetters('users', ['currentUserId']),
|
||||||
isTelemeteryEnabledOnRoute(): boolean {
|
...mapGetters(['instanceId']),
|
||||||
|
isTelemetryEnabledOnRoute(): boolean {
|
||||||
return this.$route.meta && this.$route.meta.telemetry ? !this.$route.meta.telemetry.disabled: true;
|
return this.$route.meta && this.$route.meta.telemetry ? !this.$route.meta.telemetry.disabled: true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -26,17 +29,24 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
if (this.initialised || !this.isTelemeteryEnabledOnRoute) {
|
if (this.isTelemetryInitialized || !this.isTelemetryEnabledOnRoute) return;
|
||||||
|
|
||||||
|
const telemetrySettings = this.telemetry;
|
||||||
|
|
||||||
|
if (!telemetrySettings || !telemetrySettings.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opts = this.telemetry;
|
|
||||||
if (opts && opts.enabled) {
|
this.$telemetry.init(
|
||||||
this.initialised = true;
|
telemetrySettings,
|
||||||
const instanceId = this.$store.getters.instanceId;
|
{
|
||||||
const userId = this.$store.getters['users/currentUserId'];
|
instanceId: this.instanceId,
|
||||||
const logLevel = this.$store.getters['settings/logLevel'];
|
userId: this.currentUserId,
|
||||||
this.$telemetry.init(opts, { instanceId, logLevel, userId, store: this.$store });
|
store: this.$store,
|
||||||
}
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.isTelemetryInitialized = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -44,10 +54,13 @@ export default Vue.extend({
|
||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
currentUserId(userId) {
|
currentUserId(userId) {
|
||||||
const instanceId = this.$store.getters.instanceId;
|
this.$telemetry.identify(this.instanceId, userId);
|
||||||
this.$telemetry.identify(instanceId, userId);
|
this.$externalHooks().run('telemetry.currentUserIdChanged', {
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
isTelemeteryEnabledOnRoute(enabled) {
|
isTelemetryEnabledOnRoute(enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="value clickable" @click="selectItem(item)">
|
<div v-else class="value clickable" @click="selectItem(item)">
|
||||||
<div class="item-title" :title="item.key">
|
<div class="item-title" :title="item.key" ref="variableSelectorItem">
|
||||||
{{item.name}}:
|
{{item.name}}:
|
||||||
<font-awesome-icon icon="dot-circle" title="Select Item" />
|
<font-awesome-icon icon="dot-circle" title="Select Item" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,8 +41,10 @@ import {
|
||||||
IVariableSelectorOption,
|
IVariableSelectorOption,
|
||||||
IVariableItemSelected,
|
IVariableItemSelected,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
import { externalHooks } from "@/components/mixins/externalHooks";
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default mixins(externalHooks).extend({
|
||||||
name: 'VariableSelectorItem',
|
name: 'VariableSelectorItem',
|
||||||
props: [
|
props: [
|
||||||
'allowParentSelect',
|
'allowParentSelect',
|
||||||
|
@ -85,6 +87,14 @@ export default Vue.extend({
|
||||||
extended: false,
|
extended: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$refs.variableSelectorItem) {
|
||||||
|
this.$externalHooks().run(
|
||||||
|
'variableSelectorItem.mounted',
|
||||||
|
{ variableSelectorItemRef: this.$refs.variableSelectorItem },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
optionSelected (command: string, item: IVariableSelectorOption) {
|
optionSelected (command: string, item: IVariableSelectorOption) {
|
||||||
// By default it is raw
|
// By default it is raw
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<el-table class="search-table" :data="filteredWorkflows" stripe @cell-click="openWorkflow" :default-sort = "{prop: 'updatedAt', order: 'descending'}" v-loading="isDataLoading">
|
<el-table class="search-table" :data="filteredWorkflows" stripe @cell-click="openWorkflow" :default-sort = "{prop: 'updatedAt', order: 'descending'}" v-loading="isDataLoading" ref="table">
|
||||||
<el-table-column property="name" :label="$locale.baseText('workflowOpen.name')" class-name="clickable" sortable>
|
<el-table-column property="name" :label="$locale.baseText('workflowOpen.name')" class-name="clickable" sortable>
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<div :key="scope.row.id">
|
<div :key="scope.row.id">
|
||||||
|
@ -127,6 +127,10 @@ export default mixins(
|
||||||
// Make sure that users can directly type in the filter
|
// Make sure that users can directly type in the filter
|
||||||
(this.$refs.inputFieldFilter as HTMLInputElement).focus();
|
(this.$refs.inputFieldFilter as HTMLInputElement).focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$externalHooks().run('workflowOpen.mounted', {
|
||||||
|
tableRef: this.$refs['table'],
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getIds(tags: ITag[] | undefined) {
|
getIds(tags: ITag[] | undefined) {
|
||||||
|
|
|
@ -41,7 +41,14 @@ export const workflowActivate = mixins(
|
||||||
const activeWorkflows = this.$store.getters.getActiveWorkflows;
|
const activeWorkflows = this.$store.getters.getActiveWorkflows;
|
||||||
const isWorkflowActive = activeWorkflows.includes(currWorkflowId);
|
const isWorkflowActive = activeWorkflows.includes(currWorkflowId);
|
||||||
|
|
||||||
this.$telemetry.track('User set workflow active status', { workflow_id: currWorkflowId, is_active: newActiveState, previous_status: isWorkflowActive, ndv_input: telemetrySource === 'ndv' });
|
const telemetryPayload = {
|
||||||
|
workflow_id: currWorkflowId,
|
||||||
|
is_active: newActiveState,
|
||||||
|
previous_status: isWorkflowActive,
|
||||||
|
ndv_input: telemetrySource === 'ndv',
|
||||||
|
};
|
||||||
|
this.$telemetry.track('User set workflow active status', telemetryPayload);
|
||||||
|
this.$externalHooks().run('workflowActivate.updateWorkflowActivation', telemetryPayload);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isWorkflowActive && newActiveState) {
|
if (isWorkflowActive && newActiveState) {
|
||||||
|
|
|
@ -95,6 +95,9 @@ const module: Module<IUsersState, IRootState> = {
|
||||||
getUserById(state: IUsersState): (userId: string) => IUser | null {
|
getUserById(state: IUsersState): (userId: string) => IUser | null {
|
||||||
return (userId: string): IUser | null => state.users[userId];
|
return (userId: string): IUser | null => state.users[userId];
|
||||||
},
|
},
|
||||||
|
globalRoleName(state: IUsersState, getters: any) { // tslint:disable-line:no-any
|
||||||
|
return getters.currentUser.globalRole.name;
|
||||||
|
},
|
||||||
canUserDeleteTags(state: IUsersState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any
|
canUserDeleteTags(state: IUsersState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any
|
||||||
const currentUser = getters.currentUser;
|
const currentUser = getters.currentUser;
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,11 @@ import {
|
||||||
ITelemetryTrackProperties,
|
ITelemetryTrackProperties,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { ILogLevel, INodeCreateElement, IRootState } from "@/Interface";
|
|
||||||
import { Route } from "vue-router";
|
import { Route } from "vue-router";
|
||||||
import { Store } from "vuex";
|
|
||||||
|
|
||||||
declare module 'vue/types/vue' {
|
import type { INodeCreateElement, IRootState } from "@/Interface";
|
||||||
interface Vue {
|
import type { Store } from "vuex";
|
||||||
$telemetry: Telemetry;
|
import type { IUserNodesPanelSession } from "./telemetry.types";
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TelemetryPlugin(vue: typeof _Vue): void {
|
export function TelemetryPlugin(vue: typeof _Vue): void {
|
||||||
const telemetry = new Telemetry();
|
const telemetry = new Telemetry();
|
||||||
|
@ -25,25 +21,13 @@ export function TelemetryPlugin(vue: typeof _Vue): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUserNodesPanelSessionData {
|
export class Telemetry {
|
||||||
nodeFilter: string;
|
|
||||||
resultsNodes: string[];
|
|
||||||
filterMode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IUserNodesPanelSession {
|
|
||||||
sessionId: string;
|
|
||||||
data: IUserNodesPanelSessionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Telemetry {
|
|
||||||
|
|
||||||
private pageEventQueue: Array<{route: Route}>;
|
private pageEventQueue: Array<{route: Route}>;
|
||||||
private previousPath: string;
|
private previousPath: string;
|
||||||
private store: Store<IRootState> | null;
|
private store: Store<IRootState> | null;
|
||||||
|
|
||||||
private get telemetry() {
|
private get rudderStack() {
|
||||||
// @ts-ignore
|
|
||||||
return window.rudderanalytics;
|
return window.rudderanalytics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,45 +46,64 @@ class Telemetry {
|
||||||
this.store = null;
|
this.store = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(options: ITelemetrySettings, { instanceId, logLevel, userId, store }: { instanceId: string, logLevel?: ILogLevel, userId?: string, store: Store<IRootState> }) {
|
init(
|
||||||
if (options.enabled && !this.telemetry) {
|
telemetrySettings: ITelemetrySettings,
|
||||||
if(!options.config) {
|
{ instanceId, userId, store }: {
|
||||||
return;
|
instanceId: string;
|
||||||
}
|
userId?: string;
|
||||||
|
store: Store<IRootState>;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (!telemetrySettings.enabled || !telemetrySettings.config || this.rudderStack) return;
|
||||||
|
|
||||||
this.store = store;
|
const { config: { key, url } } = telemetrySettings;
|
||||||
const logging = logLevel === 'debug' ? { logLevel: 'DEBUG'} : {};
|
|
||||||
this.loadTelemetryLibrary(options.config.key, options.config.url, { integrations: { All: false }, loadIntegration: false, ...logging});
|
this.store = store;
|
||||||
this.identify(instanceId, userId);
|
|
||||||
this.flushPageEvents();
|
const logLevel = store.getters['settings/logLevel'];
|
||||||
this.track('Session started', { session_id: store.getters.sessionId });
|
|
||||||
}
|
const logging = logLevel === 'debug' ? { logLevel: 'DEBUG' } : {};
|
||||||
|
|
||||||
|
this.initRudderStack(
|
||||||
|
key,
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
integrations: { All: false },
|
||||||
|
loadIntegration: false,
|
||||||
|
...logging,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.identify(instanceId, userId);
|
||||||
|
|
||||||
|
this.flushPageEvents();
|
||||||
|
this.track('Session started', { session_id: store.getters.sessionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
identify(instanceId: string, userId?: string) {
|
identify(instanceId: string, userId?: string) {
|
||||||
const traits = { instance_id: instanceId };
|
const traits = { instance_id: instanceId };
|
||||||
if (userId) {
|
if (userId) {
|
||||||
this.telemetry.identify(`${instanceId}#${userId}`, traits);
|
this.rudderStack.identify(`${instanceId}#${userId}`, traits);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.telemetry.reset();
|
this.rudderStack.reset();
|
||||||
this.telemetry.identify(undefined, traits);
|
this.rudderStack.identify(undefined, traits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
track(event: string, properties?: ITelemetryTrackProperties) {
|
track(event: string, properties?: ITelemetryTrackProperties) {
|
||||||
if (this.telemetry) {
|
if (!this.rudderStack) return;
|
||||||
const updatedProperties = {
|
|
||||||
...properties,
|
|
||||||
version_cli: this.store && this.store.getters.versionCli,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.telemetry.track(event, updatedProperties);
|
const updatedProperties = {
|
||||||
}
|
...properties,
|
||||||
|
version_cli: this.store && this.store.getters.versionCli,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.rudderStack.track(event, updatedProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
page(route: Route) {
|
page(route: Route) {
|
||||||
if (this.telemetry) {
|
if (this.rudderStack) {
|
||||||
if (route.path === this.previousPath) { // avoid duplicate requests query is changed for example on search page
|
if (route.path === this.previousPath) { // avoid duplicate requests query is changed for example on search page
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +116,7 @@ class Telemetry {
|
||||||
}
|
}
|
||||||
|
|
||||||
const category = (route.meta && route.meta.telemetry && route.meta.telemetry.pageCategory) || 'Editor';
|
const category = (route.meta && route.meta.telemetry && route.meta.telemetry.pageCategory) || 'Editor';
|
||||||
this.telemetry.page(category, pageName, properties);
|
this.rudderStack.page(category, pageName!, properties);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.pageEventQueue.push({
|
this.pageEventQueue.push({
|
||||||
|
@ -131,7 +134,7 @@ class Telemetry {
|
||||||
}
|
}
|
||||||
|
|
||||||
trackNodesPanel(event: string, properties: IDataObject = {}) {
|
trackNodesPanel(event: string, properties: IDataObject = {}) {
|
||||||
if (this.telemetry) {
|
if (this.rudderStack) {
|
||||||
properties.nodes_panel_session_id = this.userNodesPanelSession.sessionId;
|
properties.nodes_panel_session_id = this.userNodesPanelSession.sessionId;
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'nodeView.createNodeActiveChanged':
|
case 'nodeView.createNodeActiveChanged':
|
||||||
|
@ -203,36 +206,50 @@ class Telemetry {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadTelemetryLibrary(key: string, url: string, options: IDataObject) {
|
private initRudderStack(key: string, url: string, options: IDataObject) {
|
||||||
// @ts-ignore
|
|
||||||
window.rudderanalytics = window.rudderanalytics || [];
|
window.rudderanalytics = window.rudderanalytics || [];
|
||||||
|
|
||||||
this.telemetry.methods = ["load", "page", "track", "identify", "alias", "group", "ready", "reset", "getAnonymousId", "setAnonymousId"];
|
this.rudderStack.methods = [
|
||||||
this.telemetry.factory = (t: any) => { // tslint:disable-line:no-any
|
"load",
|
||||||
return (...args: any[]) => { // tslint:disable-line:no-any
|
"page",
|
||||||
const r = Array.prototype.slice.call(args);
|
"track",
|
||||||
r.unshift(t);
|
"identify",
|
||||||
this.telemetry.push(r);
|
"alias",
|
||||||
return this.telemetry;
|
"group",
|
||||||
|
"ready",
|
||||||
|
"reset",
|
||||||
|
"getAnonymousId",
|
||||||
|
"setAnonymousId",
|
||||||
|
];
|
||||||
|
|
||||||
|
this.rudderStack.factory = (method: string) => {
|
||||||
|
return (...args: unknown[]) => {
|
||||||
|
const argsCopy = [method, ...args];
|
||||||
|
this.rudderStack.push(argsCopy);
|
||||||
|
|
||||||
|
return this.rudderStack;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let t = 0; t < this.telemetry.methods.length; t++) {
|
for (const method of this.rudderStack.methods) {
|
||||||
const r = this.telemetry.methods[t];
|
this.rudderStack[method] = this.rudderStack.factory(method);
|
||||||
this.telemetry[r] = this.telemetry.factory(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.telemetry.loadJS = () => {
|
this.rudderStack.loadJS = () => {
|
||||||
const r = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
r.type = "text/javascript";
|
|
||||||
r.async = !0;
|
script.type = "text/javascript";
|
||||||
r.src = "https://cdn.rudderlabs.com/v1/rudder-analytics.min.js";
|
script.async = !0;
|
||||||
const a = document.getElementsByTagName("script")[0];
|
script.src = "https://cdn.rudderlabs.com/v1/rudder-analytics.min.js";
|
||||||
if(a && a.parentNode) {
|
|
||||||
a.parentNode.insertBefore(r, a);
|
const element: Element = document.getElementsByTagName("script")[0];
|
||||||
|
|
||||||
|
if (element && element.parentNode) {
|
||||||
|
element.parentNode.insertBefore(script, element);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.telemetry.loadJS();
|
|
||||||
this.telemetry.load(key, url, options);
|
this.rudderStack.loadJS();
|
||||||
|
this.rudderStack.load(key, url, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
declare module 'rudder-sdk-js';
|
|
95
packages/editor-ui/src/plugins/telemetry/telemetry.types.ts
Normal file
95
packages/editor-ui/src/plugins/telemetry/telemetry.types.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import type { Telemetry } from '.';
|
||||||
|
|
||||||
|
declare module 'vue/types/vue' {
|
||||||
|
interface Vue {
|
||||||
|
$telemetry: Telemetry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
rudderanalytics: RudderStack;
|
||||||
|
featureFlag: FeatureFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUserNodesPanelSession {
|
||||||
|
sessionId: string;
|
||||||
|
data: IUserNodesPanelSessionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IUserNodesPanelSessionData {
|
||||||
|
nodeFilter: string;
|
||||||
|
resultsNodes: string[];
|
||||||
|
filterMode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FeatureFlag {
|
||||||
|
getAll(): string[];
|
||||||
|
get(flagName: string): boolean | undefined;
|
||||||
|
isEnabled(flagName: string): boolean | undefined;
|
||||||
|
reload(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified version of:
|
||||||
|
* https://github.com/rudderlabs/rudder-sdk-js/blob/master/dist/rudder-sdk-js/index.d.ts
|
||||||
|
*/
|
||||||
|
interface RudderStack extends Array<unknown> {
|
||||||
|
[key: string]: unknown;
|
||||||
|
|
||||||
|
methods: string[];
|
||||||
|
|
||||||
|
factory: (method: string) => (...args: unknown[]) => RudderStack;
|
||||||
|
|
||||||
|
loadJS(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
load(
|
||||||
|
writeKey: string,
|
||||||
|
dataPlaneUrl: string,
|
||||||
|
options?: object,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
ready(): void;
|
||||||
|
|
||||||
|
page(
|
||||||
|
category?: string,
|
||||||
|
name?: string,
|
||||||
|
properties?: object,
|
||||||
|
options?: object,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
track(
|
||||||
|
event: string,
|
||||||
|
properties?: object,
|
||||||
|
options?: object,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
identify(
|
||||||
|
id?: string,
|
||||||
|
traits?: object,
|
||||||
|
options?: object,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
alias(
|
||||||
|
to: string,
|
||||||
|
from?: string,
|
||||||
|
options?: object,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
group(
|
||||||
|
group: string,
|
||||||
|
traits?: object,
|
||||||
|
options?: object,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
getAnonymousId(): void;
|
||||||
|
|
||||||
|
setAnonymousId(id?: string): void;
|
||||||
|
|
||||||
|
reset(): void;
|
||||||
|
}
|
|
@ -435,15 +435,23 @@ export default mixins(
|
||||||
methods: {
|
methods: {
|
||||||
onRunNode(nodeName: string, source: string) {
|
onRunNode(nodeName: string, source: string) {
|
||||||
const node = this.$store.getters.getNodeByName(nodeName);
|
const node = this.$store.getters.getNodeByName(nodeName);
|
||||||
this.$telemetry.track('User clicked execute node button', { node_type: node ? node.type : null, workflow_id: this.$store.getters.workflowId, source: 'canvas' });
|
const telemetryPayload = {
|
||||||
|
node_type: node ? node.type : null,
|
||||||
|
workflow_id: this.$store.getters.workflowId,
|
||||||
|
source: 'canvas',
|
||||||
|
};
|
||||||
|
this.$telemetry.track('User clicked execute node button', telemetryPayload);
|
||||||
|
this.$externalHooks().run('nodeView.onRunNode', telemetryPayload);
|
||||||
this.runWorkflow(nodeName, source);
|
this.runWorkflow(nodeName, source);
|
||||||
},
|
},
|
||||||
onRunWorkflow() {
|
onRunWorkflow() {
|
||||||
this.getWorkflowDataToSave().then((workflowData) => {
|
this.getWorkflowDataToSave().then((workflowData) => {
|
||||||
this.$telemetry.track('User clicked execute workflow button', {
|
const telemetryPayload = {
|
||||||
workflow_id: this.$store.getters.workflowId,
|
workflow_id: this.$store.getters.workflowId,
|
||||||
node_graph_string: JSON.stringify(TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes()).nodeGraph),
|
node_graph_string: JSON.stringify(TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes()).nodeGraph),
|
||||||
});
|
};
|
||||||
|
this.$telemetry.track('User clicked execute workflow button', telemetryPayload);
|
||||||
|
this.$externalHooks().run('nodeView.onRunWorkflow', telemetryPayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.runWorkflow();
|
this.runWorkflow();
|
||||||
|
@ -2042,6 +2050,9 @@ export default mixins(
|
||||||
this.$store.commit('setStateDirty', false);
|
this.$store.commit('setStateDirty', false);
|
||||||
|
|
||||||
this.setZoomLevel(1);
|
this.setZoomLevel(1);
|
||||||
|
|
||||||
|
if (window.featureFlag && !window.featureFlag.isEnabled('show-welcome-note')) return;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [0, 0]});
|
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [0, 0]});
|
||||||
// For novice users (onboardingFlowEnabled == true)
|
// For novice users (onboardingFlowEnabled == true)
|
||||||
|
|
|
@ -180,7 +180,9 @@ export default mixins(
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openInstallModal(event: MouseEvent) {
|
openInstallModal(event: MouseEvent) {
|
||||||
this.$telemetry.track('user clicked cnr install button', { is_empty_state: this.getInstalledPackages.length === 0 });
|
const telemetryPayload = { is_empty_state: this.getInstalledPackages.length === 0 };
|
||||||
|
this.$telemetry.track('user clicked cnr install button', telemetryPayload);
|
||||||
|
this.$externalHooks().run('settingsCommunityNodesView.openInstallModal', telemetryPayload);
|
||||||
this.$store.dispatch('ui/openModal', COMMUNITY_PACKAGE_INSTALL_MODAL_KEY);
|
this.$store.dispatch('ui/openModal', COMMUNITY_PACKAGE_INSTALL_MODAL_KEY);
|
||||||
},
|
},
|
||||||
onDescriptionTextClick(event: MouseEvent) {
|
onDescriptionTextClick(event: MouseEvent) {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.personal.personalSettings') }}</n8n-heading>
|
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.personal.personalSettings') }}</n8n-heading>
|
||||||
<div :class="$style.user">
|
<div :class="$style.user" ref="user">
|
||||||
<span :class="$style.username">
|
<span :class="$style.username">
|
||||||
<n8n-text color="text-light">{{currentUser.fullName}}</n8n-text>
|
<n8n-text color="text-light">{{currentUser.fullName}}</n8n-text>
|
||||||
</span>
|
</span>
|
||||||
|
@ -14,14 +14,16 @@
|
||||||
<div :class="$style.sectionHeader">
|
<div :class="$style.sectionHeader">
|
||||||
<n8n-heading size="large">{{ $locale.baseText('settings.personal.basicInformation') }}</n8n-heading>
|
<n8n-heading size="large">{{ $locale.baseText('settings.personal.basicInformation') }}</n8n-heading>
|
||||||
</div>
|
</div>
|
||||||
<n8n-form-inputs
|
<div>
|
||||||
v-if="formInputs"
|
<n8n-form-inputs
|
||||||
:inputs="formInputs"
|
v-if="formInputs"
|
||||||
:eventBus="formBus"
|
:inputs="formInputs"
|
||||||
@input="onInput"
|
:eventBus="formBus"
|
||||||
@ready="onReadyToSubmit"
|
@input="onInput"
|
||||||
@submit="onSubmit"
|
@ready="onReadyToSubmit"
|
||||||
/>
|
@submit="onSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div :class="$style.sectionHeader">
|
<div :class="$style.sectionHeader">
|
||||||
|
@ -101,6 +103,10 @@ export default mixins(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (this.$refs.user) {
|
||||||
|
this.$externalHooks().run('settingsPersonalView.mounted', { userRef: this.$refs.user });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentUser() {
|
currentUser() {
|
||||||
|
|
|
@ -106,11 +106,13 @@ export default mixins(workflowHelpers).extend({
|
||||||
this.navigateTo(event, VIEWS.TEMPLATE, id);
|
this.navigateTo(event, VIEWS.TEMPLATE, id);
|
||||||
},
|
},
|
||||||
onUseWorkflow({event, id}: {event: MouseEvent, id: string}) {
|
onUseWorkflow({event, id}: {event: MouseEvent, id: string}) {
|
||||||
this.$telemetry.track('User inserted workflow template', {
|
const telemetryPayload = {
|
||||||
template_id: id,
|
template_id: id,
|
||||||
wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
|
wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
|
||||||
source: 'collection',
|
source: 'collection',
|
||||||
});
|
};
|
||||||
|
this.$externalHooks().run('templatesCollectionView.onUseWorkflow', telemetryPayload);
|
||||||
|
this.$telemetry.track('User inserted workflow template', telemetryPayload);
|
||||||
|
|
||||||
this.navigateTo(event, VIEWS.TEMPLATE_IMPORT, id);
|
this.navigateTo(event, VIEWS.TEMPLATE_IMPORT, id);
|
||||||
},
|
},
|
||||||
|
|
|
@ -89,11 +89,14 @@ export default mixins(workflowHelpers).extend({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openWorkflow(id: string, e: PointerEvent) {
|
openWorkflow(id: string, e: PointerEvent) {
|
||||||
this.$telemetry.track('User inserted workflow template', {
|
const telemetryPayload = {
|
||||||
source: 'workflow',
|
source: 'workflow',
|
||||||
template_id: id,
|
template_id: id,
|
||||||
wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
|
wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
this.$externalHooks().run('templatesWorkflowView.openWorkflow', telemetryPayload);
|
||||||
|
this.$telemetry.track('User inserted workflow template', telemetryPayload);
|
||||||
|
|
||||||
if (e.metaKey || e.ctrlKey) {
|
if (e.metaKey || e.ctrlKey) {
|
||||||
const route = this.$router.resolve({ name: VIEWS.TEMPLATE_IMPORT, params: { id } });
|
const route = this.$router.resolve({ name: VIEWS.TEMPLATE_IMPORT, params: { id } });
|
||||||
|
|
Loading…
Reference in a new issue