🔀 Merge master

This commit is contained in:
Iván Ovejero 2021-12-06 09:41:15 +01:00
commit 00ac975164
32 changed files with 1345 additions and 286 deletions

236
package-lock.json generated
View file

@ -13524,6 +13524,15 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@ -13559,6 +13568,21 @@
}
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"optional": true
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@ -13621,6 +13645,58 @@
"worker-rpc": "^0.1.0"
}
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"optional": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@ -13655,6 +13731,12 @@
"slash": "^2.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"optional": true
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@ -13683,6 +13765,16 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"optional": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@ -13718,6 +13810,17 @@
}
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@ -13728,6 +13831,15 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"to-regex-range": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
@ -13821,6 +13933,12 @@
"requires": {
"tslib": "^1.8.1"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"optional": true
}
}
},
@ -23308,124 +23426,6 @@
}
}
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"optional": true
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"optional": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"optional": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"optional": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"optional": true
}
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "n8n",
"version": "0.151.0",
"version": "0.152.0",
"description": "n8n Workflow Automation Tool",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -111,10 +111,10 @@
"localtunnel": "^2.0.0",
"lodash.get": "^4.4.2",
"mysql2": "~2.3.0",
"n8n-core": "~0.95.0",
"n8n-editor-ui": "~0.118.0",
"n8n-nodes-base": "~0.148.0",
"n8n-workflow": "~0.78.0",
"n8n-core": "~0.96.0",
"n8n-editor-ui": "~0.119.0",
"n8n-nodes-base": "~0.149.0",
"n8n-workflow": "~0.79.0",
"oauth-1.0a": "^2.2.6",
"open": "^7.0.0",
"pg": "^8.3.0",

View file

@ -924,7 +924,7 @@ export async function executeWorkflow(
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function sendMessageToUI(source: string, message: any) {
export function sendMessageToUI(source: string, messages: any[]) {
if (this.sessionId === undefined) {
return;
}
@ -936,7 +936,7 @@ export function sendMessageToUI(source: string, message: any) {
'sendConsoleMessage',
{
source: `Node: "${source}"`,
message,
messages,
},
this.sessionId,
);

View file

@ -1,6 +1,6 @@
{
"name": "n8n-core",
"version": "0.95.0",
"version": "0.96.0",
"description": "Core functionality of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -50,7 +50,7 @@
"form-data": "^4.0.0",
"lodash.get": "^4.4.2",
"mime-types": "^2.1.27",
"n8n-workflow": "~0.78.0",
"n8n-workflow": "~0.79.0",
"oauth-1.0a": "^2.2.6",
"p-cancelable": "^2.0.0",
"qs": "^6.10.1",

View file

@ -1649,13 +1649,13 @@ export function getExecuteFunctions(
async putExecutionToWait(waitTill: Date): Promise<void> {
runExecutionData.waitTill = waitTill;
},
sendMessageToUI(message: any): void {
sendMessageToUI(...args: any[]): void {
if (mode !== 'manual') {
return;
}
try {
if (additionalData.sendMessageToUI) {
additionalData.sendMessageToUI(node.name, message);
additionalData.sendMessageToUI(node.name, args);
}
} catch (error) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions

View file

@ -1,6 +1,6 @@
{
"name": "n8n-editor-ui",
"version": "0.118.0",
"version": "0.119.0",
"description": "Workflow Editor UI for n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -74,7 +74,7 @@
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"n8n-workflow": "~0.78.0",
"n8n-workflow": "~0.79.0",
"normalize-wheel": "^1.0.1",
"prismjs": "^1.17.1",
"quill": "^2.0.0-dev.3",

View file

@ -66,11 +66,22 @@ declare module 'jsplumb' {
}
interface Endpoint {
endpoint: any; // tslint:disable-line:no-any
elementId: string;
__meta?: {
nodeName: string,
nodeId: string,
index: number,
totalEndpoints: number;
};
getUuid(): string;
getOverlay(name: string): any; // tslint:disable-line:no-any
repaint(params?: object): void;
}
interface N8nPlusEndpoint extends Endpoint {
setSuccessOutput(message: string): void;
clearSuccessOutput(): void;
}
interface Overlay {
@ -103,6 +114,7 @@ export interface IEndpointOptions {
parameters?: any; // tslint:disable-line:no-any
uuid?: string;
enabled?: boolean;
cssClass?: string;
}
export interface IUpdateInformation {
@ -459,7 +471,7 @@ export interface IPushDataTestWebhook {
export interface IPushDataConsoleMessage {
source: string;
message: string;
messages: string[];
}
export interface IVersionNotificationSettings {
@ -468,7 +480,7 @@ export interface IVersionNotificationSettings {
infoUrl: string;
}
export type IPersonalizationSurveyKeys = 'companySize' | 'codingSkill' | 'workArea' | 'otherWorkArea';
export type IPersonalizationSurveyKeys = 'codingSkill' | 'companyIndustry' | 'companySize' | 'otherCompanyIndustry' | 'otherWorkArea' | 'workArea';
export type IPersonalizationSurveyAnswers = {
[key in IPersonalizationSurveyKeys]: string | null
@ -713,4 +725,3 @@ export interface IBounds {
maxX: number;
maxY: number;
}

View file

@ -141,7 +141,7 @@
<n8n-icon-button icon="stop" size="small" :title="$baseText('executionsList.stopExecution')" @click.stop="stopExecution(scope.row.id)" :loading="stoppingExecutions.includes(scope.row.id)" />
</span>
<span v-if="scope.row.stoppedAt !== undefined && scope.row.id" >
<n8n-icon-button icon="folder-open" size="small" :title="$baseText('executionsList.openPastExecution')" @click.stop="displayExecution(scope.row)" />
<n8n-icon-button icon="folder-open" size="small" :title="$baseText('executionsList.openPastExecution')" @click.stop="(e) => displayExecution(scope.row, e)" />
</span>
</div>
</template>
@ -329,7 +329,14 @@ export default mixins(
return false;
},
convertToDisplayDate,
displayExecution (execution: IExecutionShortResponse) {
displayExecution (execution: IExecutionShortResponse, e: PointerEvent) {
if (e.metaKey || e.ctrlKey) {
const route = this.$router.resolve({name: 'ExecutionById', params: {id: execution.id}});
window.open(route.href, '_blank');
return;
}
this.$router.push({
name: 'ExecutionById',
params: { id: execution.id },

View file

@ -1,5 +1,5 @@
<template>
<div :class="{'node-wrapper': true, selected: isSelected}" :style="nodePosition">
<div class="node-wrapper" :style="nodePosition">
<div class="select-background" v-show="isSelected"></div>
<div :class="{'node-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}" :data-name="data.name" :ref="data.name">
<div :class="nodeClass" :style="nodeStyle" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd">
@ -544,8 +544,7 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, renderText, workflow
</style>
<style lang="scss">
/** node */
.node-wrapper.selected {
.jtk-endpoint {
z-index: 2;
}
@ -576,32 +575,31 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, renderText, workflow
z-index: 4;
}
/** node endpoints */
.jtk-endpoint {
z-index:5;
}
.jtk-connector.jtk-hover {
z-index: 6;
}
.disabled-linethrough {
.jtk-endpoint.plus-endpoint {
z-index: 6;
}
.jtk-endpoint.jtk-hover {
.jtk-endpoint.dot-output-endpoint {
z-index: 7;
}
.jtk-overlay {
z-index:7;
z-index: 7;
}
.disabled-linethrough {
z-index: 8;
}
.jtk-connector.jtk-dragging {
z-index: 8;
}
.jtk-endpoint.jtk-drag-active {
.jtk-drag-active.dot-output-endpoint, .jtk-drag-active.rect-input-endpoint {
z-index: 9;
}
@ -618,3 +616,87 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, renderText, workflow
}
</style>
<style lang="scss">
$--stalklength: 40px;
$--box-size-medium: 24px;
$--box-size-small: 18px;
.plus-endpoint {
cursor: pointer;
.plus-stalk {
border-top: 2px solid var(--color-foreground-dark);
position: absolute;
width: $--stalklength;
height: 0;
right: 100%;
top: calc(50% - 1px);
pointer-events: none;
.connection-run-items-label {
position: relative;
width: 100%;
span {
display: none;
left: calc(50% + 4px);
}
}
}
.plus-container {
color: var(--color-foreground-xdark);
border: 2px solid var(--color-foreground-xdark);
background-color: var(--color-background-xlight);
border-radius: var(--border-radius-base);
height: $--box-size-medium;
width: $--box-size-medium;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-2xs);
position: absolute;
top: 0;
right: 0;
pointer-events: none;
&.small {
height: $--box-size-small;
width: $--box-size-small;
font-size: 8px;
}
.fa-plus {
width: 1em;
}
}
.drop-hover-message {
font-weight: var(--font-weight-bold);
font-size: var(--font-size-2xs);
line-height: var(--font-line-height-regular);
color: var(--color-text-light);
position: absolute;
top: -6px;
left: calc(100% + 8px);
width: 200px;
display: none;
}
&.hidden > * {
display: none;
}
&.success .plus-stalk {
border-color: var(--color-success-light);
span {
display: inline;
}
}
}
</style>

View file

@ -49,28 +49,74 @@
value="0"
/>
<n8n-option
label="1"
label="1. I get stuck too quickly to achieve much"
value="1"
/>
<n8n-option
label="2"
label="2. I can code some useful things, but I spend a lot of time stuck"
value="2"
/>
<n8n-option
label="3"
label="3. I know enough to be dangerous, but I'm no expert"
value="3"
/>
<n8n-option
label="4"
label="4. I can figure most things out"
value="4"
/>
<n8n-option
:label="`5 (${$baseText('personalizationModal.proCoder')})`"
label="5. I can do almost anything I want, easily (pro coder)"
value="5"
/>
</n8n-select>
</n8n-input-label>
<n8n-input-label label="Which areas do you mainly work in?">
<n8n-select :value="values[WORK_AREA_KEY]" multiple placeholder="Select..." @change="(value) => onInput(WORK_AREA_KEY, value)">
<n8n-option :value="FINANCE_WORK_AREA" label="Finance" />
<n8n-option :value="HR_WORK_AREA" label="HR" />
<n8n-option :value="IT_ENGINEERING_WORK_AREA" label="IT / Engineering" />
<n8n-option :value="LEGAL_WORK_AREA" label="Legal" />
<n8n-option :value="MARKETING_WORK_AREA" label="Marketing" />
<n8n-option :value="OPS_WORK_AREA" label="Operations" />
<n8n-option :value="PRODUCT_WORK_AREA" label="Product" />
<n8n-option :value="SALES_BUSINESSDEV_WORK_AREA" label="Sales / Bizdev" />
<n8n-option :value="SECURITY_WORK_AREA" label="Security" />
<n8n-option :value="SUPPORT_WORK_AREA" label="Support" />
<n8n-option :value="EXECUTIVE_WORK_AREA" label="Executive team" />
<n8n-option :value="OTHER_WORK_AREA_OPTION" label="Other (please specify)" />
<n8n-option :value="NOT_APPLICABLE_WORK_AREA" label="I'm not using n8n for work" />
</n8n-select>
</n8n-input-label>
<n8n-input
v-if="otherWorkAreaFieldVisible"
:value="values[OTHER_WORK_AREA_KEY]"
placeholder="Specify your work area"
@input="(value) => onInput(OTHER_WORK_AREA_KEY, value)"
/>
<section v-if="showAllIndustryQuestions">
<n8n-input-label label="Which industries is your company in?">
<n8n-select :value="values[COMPANY_INDUSTRY_KEY]" multiple placeholder="Select..." @change="(value) => onInput(COMPANY_INDUSTRY_KEY, value)">
<n8n-option :value="E_COMMERCE_INDUSTRY" label="eCommerce" />
<n8n-option :value="AUTOMATION_CONSULTING_INDUSTRY" label="Automation consulting" />
<n8n-option :value="SYSTEM_INTEGRATION_INDUSTRY" label="Systems integration" />
<n8n-option :value="GOVERNMENT_INDUSTRY" label="Government" />
<n8n-option :value="LEGAL_INDUSTRY" label="Legal" />
<n8n-option :value="HEALTHCARE_INDUSTRY" label="Healthcare" />
<n8n-option :value="FINANCE_INDUSTRY" label="Finance" />
<n8n-option :value="SECURITY_INDUSTRY" label="Security" />
<n8n-option :value="SAAS_INDUSTRY" label="SaaS" />
<n8n-option :value="OTHER_INDUSTRY_OPTION" label="Other (please specify)" />
</n8n-select>
</n8n-input-label>
<n8n-input
v-if="otherCompanyIndustryFieldVisible"
:value="values[OTHER_COMPANY_INDUSTRY_KEY]"
placeholder="Specify your company's industry"
@input="(value) => onInput(OTHER_COMPANY_INDUSTRY_KEY, value)"
/>
<n8n-input-label :label="$baseText('personalizationModal.howBigIsYourCompany')">
<n8n-select :value="values[COMPANY_SIZE_KEY]" :placeholder="$baseText('personalizationModal.select')" @change="(value) => onInput(COMPANY_SIZE_KEY, value)">
<n8n-option
@ -99,6 +145,8 @@
/>
</n8n-select>
</n8n-input-label>
</section>
</div>
</template>
<template v-slot:footer>
@ -114,29 +162,42 @@
import mixins from "vue-typed-mixins";
import {
PERSONALIZATION_MODAL_KEY,
AUTOMATION_CONSULTING_WORK_AREA,
FINANCE_WORK_AREA,
HR_WORK_AREA,
IT_ENGINEERING_WORK_AREA,
LEGAL_WORK_AREA,
MARKETING_WORK_AREA,
PRODUCT_WORK_AREA,
SALES_BUSINESSDEV_WORK_AREA,
SECURITY_WORK_AREA,
SUPPORT_WORK_AREA,
OPS_WORK_AREA,
OTHER_WORK_AREA_OPTION,
AUTOMATION_CONSULTING_INDUSTRY,
CODING_SKILL_KEY,
COMPANY_INDUSTRY_KEY,
COMPANY_SIZE_100_499,
COMPANY_SIZE_1000_OR_MORE,
COMPANY_SIZE_20_OR_LESS,
COMPANY_SIZE_20_99,
COMPANY_SIZE_100_499,
COMPANY_SIZE_500_999,
COMPANY_SIZE_1000_OR_MORE,
COMPANY_SIZE_PERSONAL_USE,
WORK_AREA_KEY,
COMPANY_SIZE_KEY,
CODING_SKILL_KEY,
COMPANY_SIZE_PERSONAL_USE,
E_COMMERCE_INDUSTRY,
EXECUTIVE_WORK_AREA,
FINANCE_INDUSTRY,
FINANCE_WORK_AREA,
GOVERNMENT_INDUSTRY,
HEALTHCARE_INDUSTRY,
HR_WORK_AREA,
IT_ENGINEERING_WORK_AREA,
LEGAL_INDUSTRY,
LEGAL_WORK_AREA,
MARKETING_WORK_AREA,
NOT_APPLICABLE_WORK_AREA,
OPS_WORK_AREA,
OTHER_COMPANY_INDUSTRY_KEY,
OTHER_INDUSTRY_OPTION,
OTHER_WORK_AREA_KEY,
OTHER_WORK_AREA_OPTION,
PERSONALIZATION_MODAL_KEY,
PRODUCT_WORK_AREA,
SAAS_INDUSTRY,
SALES_BUSINESSDEV_WORK_AREA,
SECURITY_INDUSTRY,
SECURITY_WORK_AREA,
SUPPORT_WORK_AREA,
SYSTEM_INTEGRATION_INDUSTRY,
WORK_AREA_KEY,
} from "../constants";
import { workflowHelpers } from "@/components/mixins/workflowHelpers";
import { showMessage } from "@/components/mixins/showMessage";
@ -155,14 +216,17 @@ export default mixins(showMessage, renderText, workflowHelpers).extend({
isSaving: false,
PERSONALIZATION_MODAL_KEY,
otherWorkAreaFieldVisible: false,
otherCompanyIndustryFieldVisible: false,
showAllIndustryQuestions: true,
modalBus: new Vue(),
values: {
[WORK_AREA_KEY]: null,
[COMPANY_SIZE_KEY]: null,
[CODING_SKILL_KEY]: null,
[OTHER_WORK_AREA_KEY]: null,
[COMPANY_INDUSTRY_KEY]: null,
[OTHER_COMPANY_INDUSTRY_KEY]: null,
} as IPersonalizationSurveyAnswers,
AUTOMATION_CONSULTING_WORK_AREA,
FINANCE_WORK_AREA,
HR_WORK_AREA,
IT_ENGINEERING_WORK_AREA,
@ -171,19 +235,33 @@ export default mixins(showMessage, renderText, workflowHelpers).extend({
PRODUCT_WORK_AREA,
SALES_BUSINESSDEV_WORK_AREA,
SECURITY_WORK_AREA,
EXECUTIVE_WORK_AREA,
SUPPORT_WORK_AREA,
OPS_WORK_AREA,
OTHER_WORK_AREA_OPTION,
NOT_APPLICABLE_WORK_AREA,
COMPANY_SIZE_20_OR_LESS,
COMPANY_SIZE_20_99,
COMPANY_SIZE_100_499,
COMPANY_SIZE_500_999,
COMPANY_SIZE_1000_OR_MORE,
COMPANY_SIZE_PERSONAL_USE,
E_COMMERCE_INDUSTRY,
AUTOMATION_CONSULTING_INDUSTRY,
SYSTEM_INTEGRATION_INDUSTRY,
GOVERNMENT_INDUSTRY,
LEGAL_INDUSTRY,
HEALTHCARE_INDUSTRY,
FINANCE_INDUSTRY,
SECURITY_INDUSTRY,
SAAS_INDUSTRY,
OTHER_INDUSTRY_OPTION,
WORK_AREA_KEY,
COMPANY_SIZE_KEY,
CODING_SKILL_KEY,
COMPANY_INDUSTRY_KEY,
OTHER_WORK_AREA_KEY,
OTHER_COMPANY_INDUSTRY_KEY,
};
},
computed: {
@ -196,14 +274,26 @@ export default mixins(showMessage, renderText, workflowHelpers).extend({
this.modalBus.$emit('close');
},
onInput(name: IPersonalizationSurveyKeys, value: string) {
if (name === WORK_AREA_KEY && value === OTHER_WORK_AREA_OPTION) {
if (name === WORK_AREA_KEY && value.includes(OTHER_WORK_AREA_OPTION)) {
this.otherWorkAreaFieldVisible = true;
}
else if (name === WORK_AREA_KEY && value.includes(NOT_APPLICABLE_WORK_AREA)) {
this.showAllIndustryQuestions = false;
}
else if (name === WORK_AREA_KEY) {
this.otherWorkAreaFieldVisible = false;
this.showAllIndustryQuestions = true;
this.values[OTHER_WORK_AREA_KEY] = null;
}
if (name === COMPANY_INDUSTRY_KEY && value.includes(OTHER_INDUSTRY_OPTION)) {
this.otherCompanyIndustryFieldVisible = true;
}
else if (name === COMPANY_INDUSTRY_KEY) {
this.otherCompanyIndustryFieldVisible = false;
this.values[OTHER_COMPANY_INDUSTRY_KEY] = null;
}
this.values[name] = value;
},
async save(): Promise<void> {
@ -229,7 +319,7 @@ export default mixins(showMessage, renderText, workflowHelpers).extend({
<style lang="scss" module>
.container {
> div:not(:last-child) {
> div, section > div:not(:last-child) {
margin-bottom: var(--spacing-m);
}
}
@ -247,4 +337,5 @@ export default mixins(showMessage, renderText, workflowHelpers).extend({
height: 140px;
}
</style>

View file

@ -136,11 +136,18 @@ export default mixins(
this.filterTagIds.push(tagId);
}
},
async openWorkflow (data: IWorkflowShortResponse, column: any) { // tslint:disable-line:no-any
async openWorkflow (data: IWorkflowShortResponse, column: any, cell: any, e: PointerEvent) { // tslint:disable-line:no-any
if (column.label !== 'Active') {
const currentWorkflowId = this.$store.getters.workflowId;
if (e.metaKey || e.ctrlKey) {
const route = this.$router.resolve({name: 'NodeViewExisting', params: {name: data.id}});
window.open(route.href, '_blank');
return;
}
if (data.id === currentWorkflowId) {
this.$showMessage({
title: this.$baseText('workflowOpen.showMessage.title'),

View file

@ -11,6 +11,7 @@ import { Endpoint } from 'jsplumb';
import {
INodeTypeDescription,
} from 'n8n-workflow';
import { getStyleTokenValue } from '../helpers';
export const nodeBase = mixins(
deviceSupportHelpers,
@ -68,13 +69,14 @@ export const nodeBase = mixins(
endpointStyle: CanvasHelpers.getInputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
endpointHoverStyle: CanvasHelpers.getInputEndpointStyle(nodeTypeData, '--color-primary'),
isSource: false,
isTarget: !this.isReadOnly,
isTarget: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
parameters: {
nodeIndex: this.nodeIndex,
type: inputName,
index,
},
enabled: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView
enabled: !this.isReadOnly, // enabled in default case to allow dragging
cssClass: 'rect-input-endpoint',
dragAllowedWhenFull: true,
dropOptions: {
tolerance: 'touch',
@ -92,7 +94,9 @@ export const nodeBase = mixins(
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
endpoint.__meta = {
nodeName: node.name,
nodeId: this.nodeId,
index: i,
totalEndpoints: nodeTypeData.inputs.length,
};
// TODO: Activate again if it makes sense. Currently makes problems when removing
@ -140,6 +144,7 @@ export const nodeBase = mixins(
type: inputName,
index,
},
cssClass: 'dot-output-endpoint',
dragAllowedWhenFull: false,
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
};
@ -151,11 +156,53 @@ export const nodeBase = mixins(
];
}
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, {...newEndpointData});
endpoint.__meta = {
nodeName: node.name,
nodeId: this.nodeId,
index: i,
totalEndpoints: nodeTypeData.outputs.length,
};
if (!this.isReadOnly) {
const plusEndpointData: IEndpointOptions = {
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeIndex, index),
anchor: anchorPosition,
maxConnections: -1,
endpoint: 'N8nPlus',
isSource: true,
isTarget: false,
enabled: !this.isReadOnly,
endpointStyle: {
fill: getStyleTokenValue('--color-xdark'),
outlineStroke: 'none',
hover: false,
showOutputLabel: nodeTypeData.outputs.length === 1,
size: nodeTypeData.outputs.length >= 3 ? 'small' : 'medium',
},
endpointHoverStyle: {
fill: getStyleTokenValue('--color-primary'),
outlineStroke: 'none',
hover: true, // hack to distinguish hover state
},
parameters: {
nodeIndex: this.nodeIndex,
type: inputName,
index,
},
cssClass: 'plus-draggable-endpoint',
dragAllowedWhenFull: false,
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
};
const plusEndpoint: Endpoint = this.instance.addEndpoint(this.nodeId, plusEndpointData);
plusEndpoint.__meta = {
nodeName: node.name,
nodeId: this.nodeId,
index: i,
totalEndpoints: nodeTypeData.outputs.length,
};
}
});
},
__makeInstanceDraggable(node: INodeUi) {

View file

@ -165,7 +165,7 @@ export const pushConnection = mixins(
if (receivedData.type === 'sendConsoleMessage') {
const pushData = receivedData.data;
console.log(pushData.source, pushData.message); // eslint-disable-line no-console
console.log(pushData.source, ...pushData.messages); // eslint-disable-line no-console
return true;
}

View file

@ -11,6 +11,7 @@ export const DEFAULT_NEW_WORKFLOW_NAME = 'My workflow';
export const MIN_WORKFLOW_NAME_LENGTH = 1;
export const MAX_WORKFLOW_NAME_LENGTH = 128;
export const DUPLICATE_POSTFFIX = ' copy';
export const NODE_OUTPUT_DEFAULT_KEY = '_NODE_OUTPUT_DEFAULT_KEY_';
// tags
export const MAX_TAG_NAME_LENGTH = 24;
@ -93,7 +94,6 @@ export const INSTANCE_ID_HEADER = 'n8n-instance-id';
export const WAIT_TIME_UNLIMITED = '3000-01-01T00:00:00.000Z';
export const WORK_AREA_KEY = 'workArea';
export const AUTOMATION_CONSULTING_WORK_AREA = 'automationConsulting';
export const FINANCE_WORK_AREA = 'finance';
export const HR_WORK_AREA = 'HR';
export const IT_ENGINEERING_WORK_AREA = 'IT-Engineering';
@ -104,7 +104,21 @@ export const SALES_BUSINESSDEV_WORK_AREA = 'sales-businessDevelopment';
export const SECURITY_WORK_AREA = 'security';
export const SUPPORT_WORK_AREA = 'support';
export const OPS_WORK_AREA = 'ops';
export const EXECUTIVE_WORK_AREA = 'executive';
export const OTHER_WORK_AREA_OPTION = 'other';
export const NOT_APPLICABLE_WORK_AREA = 'n/a';
export const COMPANY_INDUSTRY_KEY = 'companyIndustry';
export const E_COMMERCE_INDUSTRY = 'e-commerce';
export const AUTOMATION_CONSULTING_INDUSTRY = 'automation-consulting';
export const SYSTEM_INTEGRATION_INDUSTRY = 'systems-integration';
export const GOVERNMENT_INDUSTRY = 'government';
export const LEGAL_INDUSTRY = 'legal-industry';
export const HEALTHCARE_INDUSTRY= 'healthcare';
export const FINANCE_INDUSTRY = 'finance-industry';
export const SECURITY_INDUSTRY = 'security-industry';
export const SAAS_INDUSTRY = 'saas';
export const OTHER_INDUSTRY_OPTION= 'other';
export const COMPANY_SIZE_KEY = 'companySize';
export const COMPANY_SIZE_20_OR_LESS = '<20';
@ -116,4 +130,5 @@ export const COMPANY_SIZE_PERSONAL_USE = 'personalUser';
export const CODING_SKILL_KEY = 'codingSkill';
export const OTHER_WORK_AREA_KEY = 'otherWorkArea';
export const OTHER_COMPANY_INDUSTRY_KEY = 'otherCompanyIndustry';

View file

@ -1,5 +1,5 @@
import { AUTOMATION_CONSULTING_WORK_AREA, CALENDLY_TRIGGER_NODE_TYPE, CLEARBIT_NODE_TYPE, COMPANY_SIZE_1000_OR_MORE, COMPANY_SIZE_500_999, CRON_NODE_TYPE, ELASTIC_SECURITY_NODE_TYPE, EMAIL_SEND_NODE_TYPE, EXECUTE_COMMAND_NODE_TYPE, FINANCE_WORK_AREA, FUNCTION_NODE_TYPE, GITHUB_TRIGGER_NODE_TYPE, HTTP_REQUEST_NODE_TYPE, IF_NODE_TYPE, ITEM_LISTS_NODE_TYPE, IT_ENGINEERING_WORK_AREA, JIRA_TRIGGER_NODE_TYPE, MICROSOFT_EXCEL_NODE_TYPE, MICROSOFT_TEAMS_NODE_TYPE, PERSONALIZATION_MODAL_KEY, PAGERDUTY_NODE_TYPE, PRODUCT_WORK_AREA, QUICKBOOKS_NODE_TYPE, SALESFORCE_NODE_TYPE, SALES_BUSINESSDEV_WORK_AREA, SECURITY_WORK_AREA, SEGMENT_NODE_TYPE, SET_NODE_TYPE, SLACK_NODE_TYPE, SPREADSHEET_FILE_NODE_TYPE, SWITCH_NODE_TYPE, WEBHOOK_NODE_TYPE, XERO_NODE_TYPE, COMPANY_SIZE_KEY, WORK_AREA_KEY, CODING_SKILL_KEY } from '@/constants';
import { CALENDLY_TRIGGER_NODE_TYPE, CLEARBIT_NODE_TYPE, COMPANY_SIZE_1000_OR_MORE, COMPANY_SIZE_500_999, CRON_NODE_TYPE, ELASTIC_SECURITY_NODE_TYPE, EMAIL_SEND_NODE_TYPE, EXECUTE_COMMAND_NODE_TYPE, FINANCE_WORK_AREA, FUNCTION_NODE_TYPE, GITHUB_TRIGGER_NODE_TYPE, HTTP_REQUEST_NODE_TYPE, IF_NODE_TYPE, ITEM_LISTS_NODE_TYPE, IT_ENGINEERING_WORK_AREA, JIRA_TRIGGER_NODE_TYPE, MICROSOFT_EXCEL_NODE_TYPE, MICROSOFT_TEAMS_NODE_TYPE, PERSONALIZATION_MODAL_KEY, PAGERDUTY_NODE_TYPE, PRODUCT_WORK_AREA, QUICKBOOKS_NODE_TYPE, SALESFORCE_NODE_TYPE, SALES_BUSINESSDEV_WORK_AREA, SECURITY_WORK_AREA, SEGMENT_NODE_TYPE, SET_NODE_TYPE, SLACK_NODE_TYPE, SPREADSHEET_FILE_NODE_TYPE, SWITCH_NODE_TYPE, WEBHOOK_NODE_TYPE, XERO_NODE_TYPE, COMPANY_SIZE_KEY, WORK_AREA_KEY, CODING_SKILL_KEY } from '@/constants';
import { IPersonalizationSurveyAnswers } from '@/Interface';
export function getPersonalizedNodeTypes(answers: IPersonalizationSurveyAnswers) {
@ -17,7 +17,7 @@ export function getPersonalizedNodeTypes(answers: IPersonalizationSurveyAnswers)
}
let nodeTypes = [] as string[];
if (workArea === IT_ENGINEERING_WORK_AREA || workArea === AUTOMATION_CONSULTING_WORK_AREA) {
if (workArea === IT_ENGINEERING_WORK_AREA) {
nodeTypes = nodeTypes.concat(WEBHOOK_NODE_TYPE);
}
else {

View file

@ -60,6 +60,7 @@ const module: Module<ISettingsState, IRootState> = {
context.commit('setTelemetry', settings.telemetry, {root: true});
const showPersonalizationsModal = settings.personalizationSurvey && settings.personalizationSurvey.shouldShow && !settings.personalizationSurvey.answers;
if (showPersonalizationsModal) {
context.commit('ui/openModal', PERSONALIZATION_MODAL_KEY, {root: true});
}

View file

@ -117,28 +117,6 @@ body {
}
}
// Transfer list (nodes)
.el-transfer {
.el-transfer-panel {
background-color: $--custom-input-background;
// border: 1px solid #404040;
border: none;
border-radius: 10px;
padding: 1em;
.el-transfer-panel__header {
background: none;
}
}
.el-transfer__buttons button {
border: 2px solid $--color-primary;
}
.el-transfer__buttons button.is-disabled {
border: 2px solid #aaa;
background-color: #fff;
}
}
// Tabs
.type-selector:focus,
.el-tabs__header:focus,
@ -212,4 +190,3 @@ body {
}
}
}

View file

@ -0,0 +1,495 @@
/**
* Custom Plus Endpoint
* Based on jsplumb Blank Endpoint type
*
* Source GitHub repository:
* https://github.com/jsplumb/jsplumb
*
* Source files:
* https://github.com/jsplumb/jsplumb/blob/fb5fce52794fa52306825bdaa62bf3855cdfd7e0/src/defaults.js#L1230
*
* All 1.x.x and 2.x.x versions of jsPlumb Community edition, and so also the
* content of this file, are dual-licensed under both MIT and GPLv2.
*
* MIT LICENSE
*
* Copyright (c) 2010 - 2014 jsPlumb, http://jsplumbtoolkit.com/
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* ===============================================================================
* GNU GENERAL PUBLIC LICENSE
* Version 2, June 1991
*
* Copyright (C) 1989, 1991 Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*
* Preamble
*
* The licenses for most software are designed to take away your
* freedom to share and change it. By contrast, the GNU General Public
* License is intended to guarantee your freedom to share and change free
* software--to make sure the software is free for all its users. This
* General Public License applies to most of the Free Software
* Foundation's software and to any other program whose authors commit to
* using it. (Some other Free Software Foundation software is covered by
* the GNU Lesser General Public License instead.) You can apply it to
* your programs, too.
*
* When we speak of free software, we are referring to freedom, not
* price. Our General Public Licenses are designed to make sure that you
* have the freedom to distribute copies of free software (and charge for
* this service if you wish), that you receive source code or can get it
* if you want it, that you can change the software or use pieces of it
* in new free programs; and that you know you can do these things.
*
* To protect your rights, we need to make restrictions that forbid
* anyone to deny you these rights or to ask you to surrender the rights.
* These restrictions translate to certain responsibilities for you if you
* distribute copies of the software, or if you modify it.
*
* For example, if you distribute copies of such a program, whether
* gratis or for a fee, you must give the recipients all the rights that
* you have. You must make sure that they, too, receive or can get the
* source code. And you must show them these terms so they know their
* rights.
*
* We protect your rights with two steps: (1) copyright the software, and
* (2) offer you this license which gives you legal permission to copy,
* distribute and/or modify the software.
*
* Also, for each author's protection and ours, we want to make certain
* that everyone understands that there is no warranty for this free
* software. If the software is modified by someone else and passed on, we
* want its recipients to know that what they have is not the original, so
* that any problems introduced by others will not reflect on the original
* authors' reputations.
*
* Finally, any free program is threatened constantly by software
* patents. We wish to avoid the danger that redistributors of a free
* program will individually obtain patent licenses, in effect making the
* program proprietary. To prevent this, we have made it clear that any
* patent must be licensed for everyone's free use or not licensed at all.
*
* The precise terms and conditions for copying, distribution and
* modification follow.
*
* GNU GENERAL PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. This License applies to any program or other work which contains
* a notice placed by the copyright holder saying it may be distributed
* under the terms of this General Public License. The "Program", below,
* refers to any such program or work, and a "work based on the Program"
* means either the Program or any derivative work under copyright law:
* that is to say, a work containing the Program or a portion of it,
* either verbatim or with modifications and/or translated into another
* language. (Hereinafter, translation is included without limitation in
* the term "modification".) Each licensee is addressed as "you".
*
* Activities other than copying, distribution and modification are not
* covered by this License; they are outside its scope. The act of
* running the Program is not restricted, and the output from the Program
* is covered only if its contents constitute a work based on the
* Program (independent of having been made by running the Program).
* Whether that is true depends on what the Program does.
*
* 1. You may copy and distribute verbatim copies of the Program's
* source code as you receive it, in any medium, provided that you
* conspicuously and appropriately publish on each copy an appropriate
* copyright notice and disclaimer of warranty; keep intact all the
* notices that refer to this License and to the absence of any warranty;
* and give any other recipients of the Program a copy of this License
* along with the Program.
*
* You may charge a fee for the physical act of transferring a copy, and
* you may at your option offer warranty protection in exchange for a fee.
*
* 2. You may modify your copy or copies of the Program or any portion
* of it, thus forming a work based on the Program, and copy and
* distribute such modifications or work under the terms of Section 1
* above, provided that you also meet all of these conditions:
*
* a) You must cause the modified files to carry prominent notices
* stating that you changed the files and the date of any change.
*
* b) You must cause any work that you distribute or publish, that in
* whole or in part contains or is derived from the Program or any
* part thereof, to be licensed as a whole at no charge to all third
* parties under the terms of this License.
*
* c) If the modified program normally reads commands interactively
* when run, you must cause it, when started running for such
* interactive use in the most ordinary way, to print or display an
* announcement including an appropriate copyright notice and a
* notice that there is no warranty (or else, saying that you provide
* a warranty) and that users may redistribute the program under
* these conditions, and telling the user how to view a copy of this
* License. (Exception: if the Program itself is interactive but
* does not normally print such an announcement, your work based on
* the Program is not required to print an announcement.)
*
* These requirements apply to the modified work as a whole. If
* identifiable sections of that work are not derived from the Program,
* and can be reasonably considered independent and separate works in
* themselves, then this License, and its terms, do not apply to those
* sections when you distribute them as separate works. But when you
* distribute the same sections as part of a whole which is a work based
* on the Program, the distribution of the whole must be on the terms of
* this License, whose permissions for other licensees extend to the
* entire whole, and thus to each and every part regardless of who wrote it.
*
* Thus, it is not the intent of this section to claim rights or contest
* your rights to work written entirely by you; rather, the intent is to
* exercise the right to control the distribution of derivative or
* collective works based on the Program.
*
* In addition, mere aggregation of another work not based on the Program
* with the Program (or with a work based on the Program) on a volume of
* a storage or distribution medium does not bring the other work under
* the scope of this License.
*
* 3. You may copy and distribute the Program (or a work based on it,
* under Section 2) in object code or executable form under the terms of
* Sections 1 and 2 above provided that you also do one of the following:
*
* a) Accompany it with the complete corresponding machine-readable
* source code, which must be distributed under the terms of Sections
* 1 and 2 above on a medium customarily used for software interchange; or,
*
* b) Accompany it with a written offer, valid for at least three
* years, to give any third party, for a charge no more than your
* cost of physically performing source distribution, a complete
* machine-readable copy of the corresponding source code, to be
* distributed under the terms of Sections 1 and 2 above on a medium
* customarily used for software interchange; or,
*
* c) Accompany it with the information you received as to the offer
* to distribute corresponding source code. (This alternative is
* allowed only for noncommercial distribution and only if you
* received the program in object code or executable form with such
* an offer, in accord with Subsection b above.)
*
* The source code for a work means the preferred form of the work for
* making modifications to it. For an executable work, complete source
* code means all the source code for all modules it contains, plus any
* associated interface definition files, plus the scripts used to
* control compilation and installation of the executable. However, as a
* special exception, the source code distributed need not include
* anything that is normally distributed (in either source or binary
* form) with the major components (compiler, kernel, and so on) of the
* operating system on which the executable runs, unless that component
* itself accompanies the executable.
*
* If distribution of executable or object code is made by offering
* access to copy from a designated place, then offering equivalent
* access to copy the source code from the same place counts as
* distribution of the source code, even though third parties are not
* compelled to copy the source along with the object code.
*
* 4. You may not copy, modify, sublicense, or distribute the Program
* except as expressly provided under this License. Any attempt
* otherwise to copy, modify, sublicense or distribute the Program is
* void, and will automatically terminate your rights under this License.
* However, parties who have received copies, or rights, from you under
* this License will not have their licenses terminated so long as such
* parties remain in full compliance.
*
* 5. You are not required to accept this License, since you have not
* signed it. However, nothing else grants you permission to modify or
* distribute the Program or its derivative works. These actions are
* prohibited by law if you do not accept this License. Therefore, by
* modifying or distributing the Program (or any work based on the
* Program), you indicate your acceptance of this License to do so, and
* all its terms and conditions for copying, distributing or modifying
* the Program or works based on it.
*
* 6. Each time you redistribute the Program (or any work based on the
* Program), the recipient automatically receives a license from the
* original licensor to copy, distribute or modify the Program subject to
* these terms and conditions. You may not impose any further
* restrictions on the recipients' exercise of the rights granted herein.
* You are not responsible for enforcing compliance by third parties to
* this License.
*
* 7. If, as a consequence of a court judgment or allegation of patent
* infringement or for any other reason (not limited to patent issues),
* conditions are imposed on you (whether by court order, agreement or
* otherwise) that contradict the conditions of this License, they do not
* excuse you from the conditions of this License. If you cannot
* distribute so as to satisfy simultaneously your obligations under this
* License and any other pertinent obligations, then as a consequence you
* may not distribute the Program at all. For example, if a patent
* license would not permit royalty-free redistribution of the Program by
* all those who receive copies directly or indirectly through you, then
* the only way you could satisfy both it and this License would be to
* refrain entirely from distribution of the Program.
*
* If any portion of this section is held invalid or unenforceable under
* any particular circumstance, the balance of the section is intended to
* apply and the section as a whole is intended to apply in other
* circumstances.
*
* It is not the purpose of this section to induce you to infringe any
* patents or other property right claims or to contest validity of any
* such claims; this section has the sole purpose of protecting the
* integrity of the free software distribution system, which is
* implemented by public license practices. Many people have made
* generous contributions to the wide range of software distributed
* through that system in reliance on consistent application of that
* system; it is up to the author/donor to decide if he or she is willing
* to distribute software through any other system and a licensee cannot
* impose that choice.
*
* This section is intended to make thoroughly clear what is believed to
* be a consequence of the rest of this License.
*
* 8. If the distribution and/or use of the Program is restricted in
* certain countries either by patents or by copyrighted interfaces, the
* original copyright holder who places the Program under this License
* may add an explicit geographical distribution limitation excluding
* those countries, so that distribution is permitted only in or among
* countries not thus excluded. In such case, this License incorporates
* the limitation as if written in the body of this License.
*
* 9. The Free Software Foundation may publish revised and/or new versions
* of the General Public License from time to time. Such new versions will
* be similar in spirit to the present version, but may differ in detail to
* address new problems or concerns.
*
* Each version is given a distinguishing version number. If the Program
* specifies a version number of this License which applies to it and "any
* later version", you have the option of following the terms and conditions
* either of that version or of any later version published by the Free
* Software Foundation. If the Program does not specify a version number of
* this License, you may choose any version ever published by the Free Software
* Foundation.
*
* 10. If you wish to incorporate parts of the Program into other free
* programs whose distribution conditions are different, write to the author
* to ask for permission. For software which is copyrighted by the Free
* Software Foundation, write to the Free Software Foundation; we sometimes
* make exceptions for this. Our decision will be guided by the two goals
* of preserving the free status of all derivatives of our free software and
* of promoting the sharing and reuse of software generally.
*
* NO WARRANTY
*
* 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
* FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
* OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
* PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
* OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
* TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
* PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
* REPAIR OR CORRECTION.
*
* 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
* WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
* REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
* INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
* OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
* TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
* YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
* PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
*/
(function () {
var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
var DOMElementEndpoint = function (params) {
_jp.jsPlumbUIComponent.apply(this, arguments);
this._jsPlumb.displayElements = [];
};
_ju.extend(DOMElementEndpoint, _jp.jsPlumbUIComponent, {
getDisplayElements: function () {
return this._jsPlumb.displayElements;
},
appendDisplayElement: function (el) {
this._jsPlumb.displayElements.push(el);
},
});
/*
* Class: Endpoints.N8nPlus
*/
_jp.Endpoints.N8nPlus = function (params) {
const _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments);
this.type = "N8nPlus";
this.label = '';
this.labelOffset = 0;
this.size = 'medium';
this.showOutputLabel = true;
const boxSize = {
medium: 24,
small: 18,
};
const stalkLength = 40;
DOMElementEndpoint.apply(this, arguments);
var clazz = params.cssClass ? " " + params.cssClass : "";
this.canvas = _jp.createElement("div", {
display: "block",
background: "transparent",
position: "absolute",
}, this._jsPlumb.instance.endpointClass + clazz + ' plus-endpoint');
this.canvas.innerHTML = `
<div class="plus-stalk">
<div class="connection-run-items-label">
<span class="floating"></span>
</div>
</div>
<div class="plus-container">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="plus" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-plus">
<path fill="currentColor" d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z" class=""></path>
</svg>
<div class="drop-hover-message">
Click to add node</br>
or drag to connect
</div>
</div>
`;
this.canvas.addEventListener('click', (e) => {
this._jsPlumb.instance.fire('plusEndpointClick', params.endpoint, e);
});
this._jsPlumb.instance.appendElement(this.canvas);
const container = this.canvas.querySelector('.plus-container');
const message = container.querySelector('.drop-hover-message');
const plusStalk = this.canvas.querySelector('.plus-stalk');
const successOutput = this.canvas.querySelector('.plus-stalk span');
this.setSuccessOutput = (label) => {
this.canvas.classList.add('success');
if (this.showOutputLabel) {
successOutput.textContent = label;
this.label = label;
this.labelOffset = successOutput.offsetWidth;
plusStalk.style.width = `${stalkLength + this.labelOffset}px`;
if (this._jsPlumb && this._jsPlumb.instance && !this._jsPlumb.instance.isSuspendDrawing()) {
params.endpoint.repaint(); // force rerender to move plus hoverable/draggable space
}
}
};
this.clearSuccessOutput = () => {
this.canvas.classList.remove('success');
successOutput.textContent = '';
this.label = '';
this.labelOffset = 0;
plusStalk.style.width = `${stalkLength}px`;
params.endpoint.repaint();
};
const isDragging = () => {
const endpoint = params.endpoint;
const plusConnections = endpoint.connections;
if (plusConnections.length) {
return !!plusConnections.find((conn) => conn && conn.targetId && conn.targetId.startsWith('jsPlumb'));
}
return false;
};
const hasEndpointConnections = () => {
const endpoint = params.endpoint;
const plusConnections = endpoint.connections;
if (plusConnections.length >= 1) {
return true;
}
const allConnections = this._jsPlumb.instance.getConnections({
source: endpoint.elementId,
}); // includes connections from other output endpoints like dot
return !!allConnections.find((connection) => {
if (!connection || !connection.endpoints || !connection.endpoints.length || !connection.endpoints[0]) {
return false;
}
const sourceEndpoint = connection.endpoints[0];
return sourceEndpoint === endpoint || sourceEndpoint.getUuid() === endpoint.getUuid();
});
};
this.paint = function (style, anchor) {
if (hasEndpointConnections()) {
this.canvas.classList.add('hidden');
}
else {
this.canvas.classList.remove('hidden');
container.style.color = style.fill;
container.style['border-color'] = style.fill;
message.style.display = style.hover ? 'inline' : 'none';
}
_ju.sizeElement(this.canvas, this.x, this.y, this.w, this.h);
};
this._compute = (anchorPoint, orientation, endpointStyle, connectorPaintStyle) => {
this.size = endpointStyle.size || this.size;
this.showOutputLabel = !!endpointStyle.showOutputLabel;
if (this.size !== 'medium') {
container.classList.add(this.size);
}
setTimeout(() => {
if (this.label && !this.labelOffset) { // if label is hidden, offset is 0 so recalculate
this.setSuccessOutput(this.label);
}
}, 0);
const defaultPosition = [anchorPoint[0] + stalkLength + this.labelOffset, anchorPoint[1] - boxSize[this.size] / 2, boxSize[this.size], boxSize[this.size]];
if (isDragging()) {
return defaultPosition;
}
if (hasEndpointConnections()) {
return [0, 0, 0, 0]; // remove hoverable box from view
}
return defaultPosition;
};
};
_ju.extend(_jp.Endpoints.N8nPlus, [_jp.Endpoints.AbstractEndpoint, DOMElementEndpoint], {
cleanup: function () {
if (this.canvas && this.canvas.parentNode) {
this.canvas.parentNode.removeChild(this.canvas);
}
},
});
_jp.Endpoints.svg.N8nPlus = _jp.Endpoints.N8nPlus;
})();

View file

@ -31,7 +31,6 @@ import Col from 'element-ui/lib/col';
import Badge from 'element-ui/lib/badge';
import Card from 'element-ui/lib/card';
import ColorPicker from 'element-ui/lib/color-picker';
import Transfer from 'element-ui/lib/transfer';
import Container from 'element-ui/lib/container';
import Loading from 'element-ui/lib/loading';
import MessageBox from 'element-ui/lib/message-box';
@ -109,7 +108,6 @@ Vue.use(Col);
Vue.use(Badge);
Vue.use(Card);
Vue.use(ColorPicker);
Vue.use(Transfer);
Vue.use(Container);
Vue.component(CollapseTransition.name, CollapseTransition);

View file

@ -892,7 +892,7 @@ export const store = new Vuex.Store({
return state.workflowExecutionData;
},
getWorkflowRunData: (state): IRunData | null => {
if (state.workflowExecutionData === null) {
if (!state.workflowExecutionData || !state.workflowExecutionData.data || !state.workflowExecutionData.data.resultData) {
return null;
}

View file

@ -111,11 +111,11 @@
<script lang="ts">
import Vue from 'vue';
import {
Connection, Endpoint,
Connection, Endpoint, N8nPlusEndpoint,
} from 'jsplumb';
import { MessageBoxInputData } from 'element-ui/types/message-box';
import { jsPlumb, OnConnectionBindInfo } from 'jsplumb';
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
import { NODE_NAME_PREFIX, NODE_OUTPUT_DEFAULT_KEY, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
import { copyPaste } from '@/components/mixins/copyPaste';
import { externalHooks } from '@/components/mixins/externalHooks';
import { genericHelpers } from '@/components/mixins/genericHelpers';
@ -179,6 +179,7 @@ import {
} from '@/i18n';
import '../plugins/N8nCustomConnectorType';
import '../plugins/PlusEndpointType';
export default mixins(
copyPaste,
@ -463,7 +464,7 @@ export default mixins(
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
this.$telemetry.track('User opened read-only execution', { workflow_id: data.workflowData.id, execution_mode: data.mode, execution_finished: data.finished });
if (data.finished !== true && data.data.resultData.error) {
if (data.finished !== true && data && data.data && data.data.resultData && data.data.resultData.error) {
// Check if any node contains an error
let nodeErrorFound = false;
if (data.data.resultData.runData) {
@ -1680,21 +1681,7 @@ export default mixins(
this.pullConnActive = true;
this.newNodeInsertPosition = null;
CanvasHelpers.resetConnection(connection);
CanvasHelpers.addOverlays(
connection,
[
[
'Label',
{
id: CanvasHelpers.OVERLAY_DROP_NODE_ID,
label: this.$baseText('nodeView.dropConnectionToAddNode'),
cssClass: 'drop-add-node-label',
location: 0.5,
visible: true,
},
],
],
);
const nodes = [...document.querySelectorAll('.node-default')];
const onMouseMove = (e: MouseEvent | TouchEvent) => {
@ -1754,6 +1741,17 @@ export default mixins(
console.error(e); // eslint-disable-line no-console
}
});
// @ts-ignore
this.instance.bind(('plusEndpointClick'), (endpoint: Endpoint) => {
if (endpoint && endpoint.__meta) {
insertNodeAfterSelected({
sourceId: endpoint.__meta.nodeId,
index: endpoint.__meta.index,
eventSource: 'plus_endpoint',
});
}
});
},
async newWorkflow (): Promise<void> {
await this.resetWorkspace();
@ -1814,11 +1812,19 @@ export default mixins(
if (workflowId !== null) {
const workflow = await this.restApi().getWorkflow(workflowId);
if (!workflow) {
throw new Error('Could not find workflow');
this.$router.push({
name: "NodeViewNew",
});
this.$showMessage({
title: 'Error',
message: 'Could not find workflow',
type: 'error',
});
} else {
this.$titleSet(workflow.name, 'IDLE');
// Open existing workflow
await this.openWorkflow(workflowId);
}
this.$titleSet(workflow.name, 'IDLE');
// Open existing workflow
await this.openWorkflow(workflowId);
} else {
// Create new workflow
await this.newWorkflow();
@ -1887,8 +1893,13 @@ export default mixins(
// it visibly stays behind free floating without a connection.
connection.removeOverlays();
const sourceEndpoint = connection.endpoints && connection.endpoints[0];
this.pullConnActiveNodeName = null; // prevent new connections when connectionDetached is triggered
this.instance.deleteConnection(connection); // on delete, triggers connectionDetached event which applies mutation to store
if (sourceEndpoint) {
const endpoints = this.instance.getEndpoints(sourceEndpoint.elementId);
endpoints.forEach((endpoint: Endpoint) => endpoint.repaint()); // repaint both circle and plus endpoint
}
},
__removeConnectionByConnectionInfo (info: OnConnectionBindInfo, removeVisualConnection = false) {
// @ts-ignore
@ -1983,6 +1994,16 @@ export default mixins(
return uuids[0] === sourceEndpoint && uuids[1] === targetEndpoint;
});
},
getJSPlumbEndpoints (nodeName: string): Endpoint[] {
const nodeIndex = this.getNodeIndex(nodeName);
const nodeId = `${NODE_NAME_PREFIX}${nodeIndex}`;
return this.instance.getEndpoints(nodeId);
},
getPlusEndpoint (nodeName: string, outputIndex: number): Endpoint | undefined {
const endpoints = this.getJSPlumbEndpoints(nodeName);
// @ts-ignore
return endpoints.find((endpoint: Endpoint) => endpoint.type === 'N8nPlus' && endpoint.__meta && endpoint.__meta.index === outputIndex);
},
getIncomingOutgoingConnections(nodeName: string): {incoming: Connection[], outgoing: Connection[]} {
const name = `${NODE_NAME_PREFIX}${this.$store.getters.getNodeIndex(nodeName)}`;
// @ts-ignore
@ -2022,33 +2043,47 @@ export default mixins(
outgoing.forEach((connection: Connection) => {
CanvasHelpers.resetConnection(connection);
});
const endpoints = this.getJSPlumbEndpoints(sourceNodeName);
endpoints.forEach((endpoint: Endpoint) => {
// @ts-ignore
if (endpoint.type === 'N8nPlus') {
(endpoint.endpoint as N8nPlusEndpoint).clearSuccessOutput();
}
});
return;
}
const nodeConnections = (this.$store.getters.outgoingConnectionsByNodeName(sourceNodeName) as INodeConnections).main;
if (!nodeConnections) {
return;
}
const outputMap = CanvasHelpers.getOutputSummary(data, nodeConnections);
const outputMap = CanvasHelpers.getOutputSummary(data, nodeConnections || []);
Object.keys(outputMap).forEach((sourceOutputIndex: string) => {
Object.keys(outputMap[sourceOutputIndex]).forEach((targetNodeName: string) => {
Object.keys(outputMap[sourceOutputIndex][targetNodeName]).forEach((targetInputIndex: string) => {
const connection = this.getJSPlumbConnection(sourceNodeName, parseInt(sourceOutputIndex, 10), targetNodeName, parseInt(targetInputIndex, 10));
if (targetNodeName) {
const connection = this.getJSPlumbConnection(sourceNodeName, parseInt(sourceOutputIndex, 10), targetNodeName, parseInt(targetInputIndex, 10));
if (!connection) {
return;
if (connection) {
const output = outputMap[sourceOutputIndex][targetNodeName][targetInputIndex];
if (!output || !output.total) {
CanvasHelpers.resetConnection(connection);
}
else {
CanvasHelpers.addConnectionOutputSuccess(connection, output);
}
}
}
const output = outputMap[sourceOutputIndex][targetNodeName][targetInputIndex];
if (!output || !output.total) {
CanvasHelpers.resetConnection(connection);
return;
const endpoint = this.getPlusEndpoint(sourceNodeName, parseInt(sourceOutputIndex, 10));
if (endpoint && endpoint.endpoint) {
const output = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
if (output && output.total > 0) {
(endpoint.endpoint as N8nPlusEndpoint).setSuccessOutput(CanvasHelpers.getRunItemsLabel(output));
}
else {
(endpoint.endpoint as N8nPlusEndpoint).clearSuccessOutput();
}
}
CanvasHelpers.addConnectionOutputSuccess(connection, output);
});
});
});
@ -2086,6 +2121,7 @@ export default mixins(
}
}
let waitForNewConnection = false;
// connect nodes before/after deleted node
const nodeType: INodeTypeDescription | null = this.$store.getters.nodeType(node.type, node.typeVersion);
if (nodeType && nodeType.outputs.length === 1
@ -2095,6 +2131,7 @@ export default mixins(
const conn1 = incoming[0];
const conn2 = outgoing[0];
if (conn1.__meta && conn2.__meta) {
waitForNewConnection = true;
const sourceNodeName = conn1.__meta.sourceNodeName;
const sourceNodeOutputIndex = conn1.__meta.sourceOutputIndex;
const targetNodeName = conn2.__meta.targetNodeName;
@ -2102,7 +2139,12 @@ export default mixins(
setTimeout(() => {
this.connectTwoNodes(sourceNodeName, sourceNodeOutputIndex, targetNodeName, targetNodeOuputIndex);
}, 100);
if (waitForNewConnection) {
this.instance.setSuspendDrawing(false, true);
waitForNewConnection = false;
}
}, 100); // just to make it clear to users that this is a new connection
}
}
}
@ -2127,8 +2169,10 @@ export default mixins(
this.$store.commit('removeNode', node);
this.$store.commit('clearNodeExecutionData', node.name);
// Now it can draw again
this.instance.setSuspendDrawing(false, true);
if (!waitForNewConnection) {
// Now it can draw again
this.instance.setSuspendDrawing(false, true);
}
// Remove node from selected index if found in it
this.$store.commit('removeNodeFromSelection', node);
@ -2811,7 +2855,7 @@ export default mixins(
color: var(--color-success);
}
> span.floating {
.floating {
position: absolute;
top: -22px;
transform: translateX(-50%);

View file

@ -1,5 +1,5 @@
import { getStyleTokenValue } from "@/components/helpers";
import { START_NODE_TYPE } from "@/constants";
import { NODE_OUTPUT_DEFAULT_KEY, START_NODE_TYPE } from "@/constants";
import { IBounds, INodeUi, IZoomConfig, XYPosition } from "@/Interface";
import { Connection, Endpoint, Overlay, OverlaySpec, PaintStyle } from "jsplumb";
import {
@ -57,11 +57,13 @@ export const CONNECTOR_FLOWCHART_TYPE = ['N8nCustom', {
getEndpointOffset(endpoint: Endpoint) {
const indexOffset = 10; // stub offset between different endpoints of same node
const index = endpoint && endpoint.__meta ? endpoint.__meta.index : 0;
const totalEndpoints = endpoint && endpoint.__meta ? endpoint.__meta.totalEndpoints : 0;
const outputOverlay = getOverlay(endpoint, OVERLAY_OUTPUT_NAME_LABEL);
const labelOffset = outputOverlay && outputOverlay.label && outputOverlay.label.length > 1 ? 10 : 0;
const outputsOffset = totalEndpoints > 3 ? 24 : 0; // avoid intersecting plus
return index * indexOffset + labelOffset;
return index * indexOffset + labelOffset + outputsOffset;
},
}];
@ -112,11 +114,9 @@ export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [
],
];
export const ANCHOR_POSITIONS: {
[key: string]: {
[key: number]: string[] | number[][];
[key: number]: number[][];
}
} = {
input: {
@ -499,20 +499,33 @@ export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputCo
}
run.data.main.forEach((output: INodeExecutionData[] | null, i: number) => {
if (!nodeConnections[i]) {
const sourceOutputIndex = i;
if (!outputMap[sourceOutputIndex]) {
outputMap[sourceOutputIndex] = {};
}
if (!outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY]) {
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY] = {};
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0] = {
total: 0,
iterations: 0,
};
}
const defaultOutput = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
defaultOutput.total += output ? output.length : 0;
defaultOutput.iterations += output ? 1 : 0;
if (!nodeConnections[sourceOutputIndex]) {
return;
}
nodeConnections[i]
nodeConnections[sourceOutputIndex]
.map((connection: IConnection) => {
const sourceOutputIndex = i;
const targetNodeName = connection.node;
const targetInputIndex = connection.index;
if (!outputMap[sourceOutputIndex]) {
outputMap[sourceOutputIndex] = {};
}
if (!outputMap[sourceOutputIndex][targetNodeName]) {
outputMap[sourceOutputIndex][targetNodeName] = {};
}
@ -542,6 +555,13 @@ export const resetConnection = (connection: Connection) => {
}
};
export const getRunItemsLabel = (output: {total: number, iterations: number}): string => {
let label = `${output.total}`;
label = output.total > 1 ? `${label} items` : `${label} item`;
label = output.iterations > 1 ? `${label} total` : label;
return label;
};
export const addConnectionOutputSuccess = (connection: Connection, output: {total: number, iterations: number}) => {
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_SUCCESS);
if (connection.canvas) {
@ -552,15 +572,11 @@ export const addConnectionOutputSuccess = (connection: Connection, output: {tota
connection.removeOverlay(OVERLAY_RUN_ITEMS_ID);
}
let label = `${output.total}`;
label = output.total > 1 ? `${label} items` : `${label} item`;
label = output.iterations > 1 ? `${label} total` : label;
connection.addOverlay([
'Label',
{
id: OVERLAY_RUN_ITEMS_ID,
label: `<span>${label}</span>`,
label: `<span>${getRunItemsLabel(output)}</span>`,
cssClass: 'connection-run-items-label',
location: .5,
},

View file

@ -1,6 +1,6 @@
{
"name": "n8n-node-dev",
"version": "0.35.0",
"version": "0.36.0",
"description": "CLI to simplify n8n credentials/node development",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -60,8 +60,8 @@
"change-case": "^4.1.1",
"copyfiles": "^2.1.1",
"inquirer": "^7.0.1",
"n8n-core": "~0.95.0",
"n8n-workflow": "~0.78.0",
"n8n-core": "~0.96.0",
"n8n-workflow": "~0.79.0",
"oauth-1.0a": "^2.2.6",
"replace-in-file": "^6.0.0",
"request": "^2.88.2",

View file

@ -0,0 +1,27 @@
import {
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class HttpQueryAuth implements ICredentialType {
name = 'httpQueryAuth';
displayName = 'Query Auth';
documentationUrl = 'httpRequest';
icon = 'node:n8n-nodes-base.httpRequest';
properties: INodeProperties[] = [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
];
}

View file

@ -700,14 +700,14 @@ export const eventFields: INodeProperties[] = [
description: `Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves.`,
},
{
displayName: 'End Time',
displayName: 'Start Time',
name: 'timeMax',
type: 'dateTime',
default: '',
description: `Upper bound (exclusive) for an event's start time to filter by`,
},
{
displayName: 'Start Time',
displayName: 'End Time',
name: 'timeMin',
type: 'dateTime',
default: '',

View file

@ -9,10 +9,12 @@ import {
} from 'n8n-core';
import {
IDataObject, NodeApiError,
IDataObject,
IPollFunctions,
NodeApiError,
} from 'n8n-workflow';
export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const options: OptionsWithUri = {
headers: {
'Content-Type': 'application/json',
@ -37,7 +39,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
}
}
export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];

View file

@ -0,0 +1,215 @@
import {
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
IPollFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
import {
googleApiRequest,
googleApiRequestAllItems,
} from './GenericFunctions';
import * as moment from 'moment';
export class GoogleCalendarTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Google Calendar Trigger',
name: 'googleCalendarTrigger',
icon: 'file:googleCalendar.svg',
group: ['trigger'],
version: 1,
subtitle: '={{$parameter["triggerOn"]}}',
description: 'Starts the workflow when Google Calendar events occur',
defaults: {
name: 'Google Calendar Trigger',
color: '#3E87E4',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'googleCalendarOAuth2Api',
required: true,
},
],
polling: true,
properties: [
{
displayName: 'Calendar Name/ID',
name: 'calendarId',
type: 'options',
required: true,
typeOptions: {
loadOptionsMethod: 'getCalendars',
},
default: '',
},
{
displayName: 'Trigger On',
name: 'triggerOn',
type: 'options',
required: true,
default: '',
options: [
{
name: 'Event Created',
value: 'eventCreated',
},
{
name: 'Event Ended',
value: 'eventEnded',
},
{
name: 'Event Started',
value: 'eventStarted',
},
{
name: 'Event Updated',
value: 'eventUpdated',
},
],
description: '',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Match Term',
name: 'matchTerm',
type: 'string',
default: '',
description: 'Free text search terms to filter events that match these terms in any field, except for extended properties',
},
],
},
],
};
methods = {
loadOptions: {
// Get all the calendars to display them to user so that he can
// select them easily
async getCalendars(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const calendars = await googleApiRequestAllItems.call(
this,
'items',
'GET',
'/calendar/v3/users/me/calendarList',
);
for (const calendar of calendars) {
returnData.push({
name: calendar.summary,
value: calendar.id,
});
}
return returnData;
},
},
};
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const poolTimes = this.getNodeParameter('pollTimes.item', []) as IDataObject[];
const triggerOn = this.getNodeParameter('triggerOn', '') as string;
const calendarId = this.getNodeParameter('calendarId') as string;
const webhookData = this.getWorkflowStaticData('node');
const matchTerm = this.getNodeParameter('options.matchTerm', '') as string;
if (poolTimes.length === 0) {
throw new NodeOperationError(
this.getNode(),
'Please set a poll time',
);
}
if (triggerOn === '') {
throw new NodeOperationError(
this.getNode(),
'Please select an event',
);
}
if (calendarId === '') {
throw new NodeOperationError(
this.getNode(),
'Please select a calendar',
);
}
const now = moment().utc().format();
const startDate = webhookData.lastTimeChecked as string || now;
const endDate = now;
const qs: IDataObject = {
showDeleted: false,
};
if (matchTerm !== '') {
qs.q = matchTerm;
}
let events;
if (triggerOn === 'eventCreated' || triggerOn === 'eventUpdated') {
Object.assign(qs, {
updatedMin: startDate,
orderBy: 'updated',
});
} else if (triggerOn === 'eventStarted' || triggerOn === 'eventEnded') {
Object.assign(qs, {
singleEvents: true,
timeMin: moment(startDate).startOf('second').utc().format(),
timeMax: moment(endDate).endOf('second').utc().format(),
orderBy: 'startTime',
});
}
if (this.getMode() === 'manual') {
delete qs.updatedMin;
delete qs.timeMin;
delete qs.timeMax;
qs.maxResults = 1;
events = await googleApiRequest.call(this, 'GET', `/calendar/v3/calendars/${calendarId}/events`, {}, qs);
events = events.items;
} else {
events = await googleApiRequestAllItems.call(this, 'items', 'GET', `/calendar/v3/calendars/${calendarId}/events`, {}, qs);
if (triggerOn === 'eventCreated') {
events = events.filter((event: { created: string }) => moment(event.created).isBetween(startDate, endDate));
} else if (triggerOn === 'eventUpdated') {
events = events.filter((event: { created: string, updated: string }) => !moment(moment(event.created).format('YYYY-MM-DDTHH:mm:ss')).isSame(moment(event.updated).format('YYYY-MM-DDTHH:mm:ss')));
} else if (triggerOn === 'eventStarted') {
events = events.filter((event: { start: { dateTime: string } }) => moment(event.start.dateTime).isBetween(startDate, endDate, null, '[]'));
} else if (triggerOn === 'eventEnded') {
events = events.filter((event: { end: { dateTime: string } }) => moment(event.end.dateTime).isBetween(startDate, endDate, null, '[]'));
}
}
webhookData.lastTimeChecked = endDate;
if (Array.isArray(events) && events.length) {
return [this.helpers.returnJsonArray(events)];
}
if (this.getMode() === 'manual') {
throw new NodeApiError(this.getNode(), { message: 'No data with the current filter could be found' });
}
return null;
}
}

View file

@ -73,6 +73,17 @@ export class HttpRequest implements INodeType {
},
},
},
{
name: 'httpQueryAuth',
required: true,
displayOptions: {
show: {
authentication: [
'queryAuth',
],
},
},
},
{
name: 'oAuth1Api',
required: true,
@ -114,6 +125,10 @@ export class HttpRequest implements INodeType {
name: 'Header Auth',
value: 'headerAuth',
},
{
name: 'Query Auth',
value: 'queryAuth',
},
{
name: 'OAuth1',
value: 'oAuth1',
@ -641,6 +656,7 @@ export class HttpRequest implements INodeType {
const httpBasicAuth = await this.getCredentials('httpBasicAuth');
const httpDigestAuth = await this.getCredentials('httpDigestAuth');
const httpHeaderAuth = await this.getCredentials('httpHeaderAuth');
const httpQueryAuth = await this.getCredentials('httpQueryAuth');
const oAuth1Api = await this.getCredentials('oAuth1Api');
const oAuth2Api = await this.getCredentials('oAuth2Api');
@ -890,6 +906,12 @@ export class HttpRequest implements INodeType {
if (httpHeaderAuth !== undefined) {
requestOptions.headers![httpHeaderAuth.name as string] = httpHeaderAuth.value;
}
if (httpQueryAuth !== undefined) {
if (!requestOptions.qs) {
requestOptions.qs = {};
}
requestOptions.qs![httpQueryAuth.name as string] = httpQueryAuth.value;
}
if (httpDigestAuth !== undefined) {
requestOptions.auth = {
user: httpDigestAuth.user as string,

View file

@ -1,6 +1,6 @@
{
"name": "n8n-nodes-base",
"version": "0.148.0",
"version": "0.149.0",
"description": "Base nodes of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -138,6 +138,7 @@
"dist/credentials/HttpBasicAuth.credentials.js",
"dist/credentials/HttpDigestAuth.credentials.js",
"dist/credentials/HttpHeaderAuth.credentials.js",
"dist/credentials/HttpQueryAuth.credentials.js",
"dist/credentials/HubspotApi.credentials.js",
"dist/credentials/HubspotDeveloperApi.credentials.js",
"dist/credentials/HubspotOAuth2Api.credentials.js",
@ -424,6 +425,7 @@
"dist/nodes/Google/BigQuery/GoogleBigQuery.node.js",
"dist/nodes/Google/Books/GoogleBooks.node.js",
"dist/nodes/Google/Calendar/GoogleCalendar.node.js",
"dist/nodes/Google/Calendar/GoogleCalendarTrigger.node.js",
"dist/nodes/Google/CloudNaturalLanguage/GoogleCloudNaturalLanguage.node.js",
"dist/nodes/Google/Contacts/GoogleContacts.node.js",
"dist/nodes/Google/Docs/GoogleDocs.node.js",
@ -677,7 +679,7 @@
"@types/xml2js": "^0.4.3",
"gulp": "^4.0.0",
"jest": "^26.4.2",
"n8n-workflow": "~0.78.0",
"n8n-workflow": "~0.79.0",
"nodelinter": "^0.1.9",
"ts-jest": "^26.3.0",
"tslint": "^6.1.2",
@ -717,7 +719,7 @@
"mqtt": "4.2.6",
"mssql": "^6.2.0",
"mysql2": "~2.3.0",
"n8n-core": "~0.95.0",
"n8n-core": "~0.96.0",
"node-ssh": "^12.0.0",
"nodemailer": "^6.5.0",
"pdf-parse": "^1.1.1",

View file

@ -1,6 +1,6 @@
{
"name": "n8n-workflow",
"version": "0.78.0",
"version": "0.79.0",
"description": "Workflow base code of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",

View file

@ -114,11 +114,11 @@ export class Expression {
// @ts-ignore
data.document = {};
// @ts-ignore
data.constructor = {};
// Execute the expression
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
// tmpl.tmpl('{{this.Promise=global.Promise;global=this;}}', data);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const returnValue = tmpl.tmpl(parameterValue, data);
if (typeof returnValue === 'function') {

View file

@ -57,7 +57,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[]
multipleValueButtonText: 'Add Poll Time',
},
default: {},
description: 'Time at which polling should occur.',
description: 'Time at which polling should occur',
placeholder: 'Add Poll Time',
options: [
{
@ -115,7 +115,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[]
},
},
default: 14,
description: 'The hour of the day to trigger (24h format).',
description: 'The hour of the day to trigger (24h format)',
},
{
displayName: 'Minute',
@ -131,7 +131,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[]
},
},
default: 0,
description: 'The minute of the day to trigger.',
description: 'The minute of the day to trigger',
},
{
displayName: 'Day of Month',
@ -147,7 +147,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[]
maxValue: 31,
},
default: 1,
description: 'The day of the month to trigger.',
description: 'The day of the month to trigger',
},
{
displayName: 'Weekday',
@ -189,7 +189,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[]
},
],
default: '1',
description: 'The weekday to trigger.',
description: 'The weekday to trigger',
},
{
displayName: 'Cron Expression',
@ -218,7 +218,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[]
},
},
default: 2,
description: 'All how many X minutes/hours it should trigger.',
description: 'All how many X minutes/hours it should trigger',
},
{
displayName: 'Unit',
@ -240,7 +240,7 @@ export function getSpecialNodeParameters(nodeType: INodeType): INodeProperties[]
},
],
default: 'hours',
description: 'If it should trigger all X minutes or hours.',
description: 'If it should trigger all X minutes or hours',
},
],
},