mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
feat(editor): update expressions display (#4171)
* N8n 4673 expressions res1 (#4149) * hide hints if necessary * refactor out parameter input * refactor param input in creds * remove any * add expression result before * update case * add types * fix spacing * update types * update expr * update parameter input * update param input * update param input * remove import * fix typo * update value * fix drop for rl * add state to track hovering item * add hover behavior to resolve values * update index * fix run selector bug * add run item to eval expr * add paired item mappings * fix rec bug * Fix for loops * handle pinned data * add missing pinned * fix bug * support parent * add input * map back from output * clean up * fix output bug * fix branching bug * update preview * only if expr * fix output * fix expr eval for outputs * add default hover state * fix hover state * fix branching * hide hint if expr * remove duplicate logic * update style * allow opening expr in demo * update expr * update row hover * update param name * clean up * update hovering state * update default output * fix duplicate import * update hover behavior * update package lock * fix pinned data case * address case when no input
This commit is contained in:
parent
fe7c8a85ce
commit
6b538494ce
106
package-lock.json
generated
106
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.196.0",
|
||||
"version": "0.197.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "n8n",
|
||||
"version": "0.196.0",
|
||||
"version": "0.197.1",
|
||||
"hasInstallScript": true,
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
@ -43257,7 +43257,7 @@
|
|||
},
|
||||
"packages/cli": {
|
||||
"name": "n8n",
|
||||
"version": "0.196.0",
|
||||
"version": "0.197.1",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
|
@ -43303,10 +43303,10 @@
|
|||
"lodash.split": "^4.4.2",
|
||||
"lodash.unset": "^4.5.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.136.0",
|
||||
"n8n-editor-ui": "~0.162.0",
|
||||
"n8n-nodes-base": "~0.194.0",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-core": "~0.137.0",
|
||||
"n8n-editor-ui": "~0.163.1",
|
||||
"n8n-nodes-base": "~0.195.1",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
@ -44453,7 +44453,7 @@
|
|||
},
|
||||
"packages/core": {
|
||||
"name": "n8n-core",
|
||||
"version": "0.136.0",
|
||||
"version": "0.137.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
|
@ -44465,7 +44465,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"qs": "^6.10.1",
|
||||
|
@ -45521,7 +45521,7 @@
|
|||
},
|
||||
"packages/design-system": {
|
||||
"name": "n8n-design-system",
|
||||
"version": "0.36.0",
|
||||
"version": "0.37.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"element-ui": "~2.15.7",
|
||||
|
@ -45704,7 +45704,7 @@
|
|||
},
|
||||
"packages/editor-ui": {
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.162.0",
|
||||
"version": "0.163.1",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@fontsource/open-sans": "^4.5.0",
|
||||
|
@ -45728,8 +45728,8 @@
|
|||
"lodash.set": "^4.3.2",
|
||||
"luxon": "^2.3.0",
|
||||
"monaco-editor": "^0.30.1",
|
||||
"n8n-design-system": "~0.36.0",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-design-system": "~0.37.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "2.0.0-dev.4",
|
||||
|
@ -46156,7 +46156,7 @@
|
|||
},
|
||||
"packages/node-dev": {
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.75.0",
|
||||
"version": "0.76.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
|
@ -46164,8 +46164,8 @@
|
|||
"change-case": "^4.1.1",
|
||||
"fast-glob": "^3.2.5",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "~0.136.0",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-core": "~0.137.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
@ -46185,21 +46185,9 @@
|
|||
"@types/vorpal": "^1.11.0"
|
||||
}
|
||||
},
|
||||
"packages/node-dev/node_modules/typescript": {
|
||||
"version": "4.6.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
|
||||
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"packages/nodes-base": {
|
||||
"name": "n8n-nodes-base",
|
||||
"version": "0.194.0",
|
||||
"version": "0.195.1",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@kafkajs/confluent-schema-registry": "1.0.6",
|
||||
|
@ -46237,7 +46225,7 @@
|
|||
"mqtt": "4.2.6",
|
||||
"mssql": "^8.1.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.136.0",
|
||||
"n8n-core": "~0.137.0",
|
||||
"node-html-markdown": "^1.1.3",
|
||||
"node-ssh": "^12.0.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
|
@ -46292,7 +46280,7 @@
|
|||
"eslint-plugin-n8n-nodes-base": "^1.9.3",
|
||||
"gulp": "^4.0.0",
|
||||
"jest": "^27.4.7",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"ts-jest": "^27.1.3",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~4.8.0"
|
||||
|
@ -47329,7 +47317,7 @@
|
|||
},
|
||||
"packages/workflow": {
|
||||
"name": "n8n-workflow",
|
||||
"version": "0.118.0",
|
||||
"version": "0.119.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@n8n_io/riot-tmpl": "^1.0.1",
|
||||
|
@ -52127,7 +52115,7 @@
|
|||
"@oclif/errors": "^1.3.5",
|
||||
"@oclif/parser": "^3.8.0",
|
||||
"debug": "^4.1.1",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^11.0.1",
|
||||
"is-wsl": "^2.1.1",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
|
@ -52153,10 +52141,10 @@
|
|||
"clean-stack": "^3.0.1",
|
||||
"cli-progress": "^3.10.0",
|
||||
"debug": "^4.3.4",
|
||||
"ejs": "^3.1.8",
|
||||
"ejs": "^3.1.6",
|
||||
"fs-extra": "^9.1.0",
|
||||
"get-package-type": "^0.1.0",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^11.1.0",
|
||||
"hyperlinker": "^1.0.0",
|
||||
"indent-string": "^4.0.0",
|
||||
"is-wsl": "^2.2.0",
|
||||
|
@ -52368,7 +52356,7 @@
|
|||
"@oclif/errors": "^1.3.3",
|
||||
"@oclif/parser": "^3.8.0",
|
||||
"debug": "^4.1.1",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^11.0.1",
|
||||
"is-wsl": "^2.1.1",
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
|
@ -52561,7 +52549,7 @@
|
|||
"@oclif/errors": "^1.3.3",
|
||||
"@oclif/parser": "^3.8.0",
|
||||
"debug": "^4.1.1",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^11.0.1",
|
||||
"is-wsl": "^2.1.1",
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
|
@ -53067,7 +53055,7 @@
|
|||
"css-loader": "^3.6.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"find-up": "^5.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^6.0.4",
|
||||
"fork-ts-checker-webpack-plugin": "^4.1.6",
|
||||
"glob": "^7.1.6",
|
||||
"glob-promise": "^3.4.0",
|
||||
"global": "^4.4.0",
|
||||
|
@ -55589,7 +55577,7 @@
|
|||
"@typescript-eslint/types": "5.38.1",
|
||||
"@typescript-eslint/visitor-keys": "5.38.1",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"semver": "^7.3.7",
|
||||
"tsutils": "^3.21.0"
|
||||
|
@ -56959,7 +56947,7 @@
|
|||
"integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browserslist": "^4.21.3",
|
||||
"browserslist": "^4.12.0",
|
||||
"caniuse-lite": "^1.0.30001109",
|
||||
"normalize-range": "^0.1.2",
|
||||
"num2fraction": "^1.2.2",
|
||||
|
@ -59784,7 +59772,7 @@
|
|||
"integrity": "sha512-xVtYpJQ5grszDHEUU9O7XbjjcZ0ccX3LgQsyqSvTnjX97ZqEgn9F5srmrwwwMtbKzDllyFPL+O+2OFMl1lU4TQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browserslist": "^4.21.3"
|
||||
"browserslist": "^4.21.4"
|
||||
}
|
||||
},
|
||||
"core-js-pure": {
|
||||
|
@ -59858,7 +59846,7 @@
|
|||
"requires": {
|
||||
"arrify": "^2.0.1",
|
||||
"cp-file": "^7.0.0",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^9.2.0",
|
||||
"has-glob": "^1.0.0",
|
||||
"junk": "^3.1.0",
|
||||
"nested-error-stacks": "^2.1.0",
|
||||
|
@ -61462,7 +61450,7 @@
|
|||
"functional-red-black-tree": "^1.0.1",
|
||||
"glob-parent": "^6.0.1",
|
||||
"globals": "^13.15.0",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^11.1.0",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.0.0",
|
||||
|
@ -71914,10 +71902,10 @@
|
|||
"lodash.split": "^4.4.2",
|
||||
"lodash.unset": "^4.5.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.136.0",
|
||||
"n8n-editor-ui": "~0.162.0",
|
||||
"n8n-nodes-base": "~0.194.0",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-core": "~0.137.0",
|
||||
"n8n-editor-ui": "~0.163.1",
|
||||
"n8n-nodes-base": "~0.195.1",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
"nodemon": "^2.0.2",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
|
@ -72807,7 +72795,7 @@
|
|||
"jest": "^27.4.7",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"qs": "^6.10.1",
|
||||
|
@ -73791,8 +73779,8 @@
|
|||
"lodash.set": "^4.3.2",
|
||||
"luxon": "^2.3.0",
|
||||
"monaco-editor": "^0.30.1",
|
||||
"n8n-design-system": "~0.36.0",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-design-system": "~0.37.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "2.0.0-dev.4",
|
||||
|
@ -74109,19 +74097,13 @@
|
|||
"change-case": "^4.1.1",
|
||||
"fast-glob": "^3.2.5",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "~0.136.0",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-core": "~0.137.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
"tmp-promise": "^3.0.2",
|
||||
"typescript": "~4.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": {
|
||||
"version": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
|
||||
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"n8n-nodes-base": {
|
||||
|
@ -74194,8 +74176,8 @@
|
|||
"mqtt": "4.2.6",
|
||||
"mssql": "^8.1.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.136.0",
|
||||
"n8n-workflow": "~0.118.0",
|
||||
"n8n-core": "~0.137.0",
|
||||
"n8n-workflow": "~0.119.0",
|
||||
"node-html-markdown": "^1.1.3",
|
||||
"node-ssh": "^12.0.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
|
@ -78476,7 +78458,7 @@
|
|||
"fs-extra": "^6.0.1",
|
||||
"get-stream": "^5.1.0",
|
||||
"glob": "^7.1.2",
|
||||
"globby": "^11.0.2",
|
||||
"globby": "^10.0.1",
|
||||
"http-call": "^5.1.2",
|
||||
"load-json-file": "^6.2.0",
|
||||
"pkg-dir": "^4.2.0",
|
||||
|
@ -84173,7 +84155,7 @@
|
|||
"consola": "^2.15.3",
|
||||
"dotenv": "^16.0.0",
|
||||
"dotenv-expand": "^8.0.2",
|
||||
"ejs": "^3.1.8",
|
||||
"ejs": "^3.1.6",
|
||||
"fast-glob": "^3.2.11",
|
||||
"fs-extra": "^10.0.1",
|
||||
"html-minifier-terser": "^6.1.0",
|
||||
|
|
|
@ -72,33 +72,34 @@
|
|||
var(--color-secondary-l)
|
||||
);
|
||||
|
||||
--color-secondary-tint-1-h: 247;
|
||||
--color-secondary-tint-1-s: 49%;
|
||||
--color-secondary-tint-1-l: 85%;
|
||||
--color-secondary-tint-1: hsl(
|
||||
var(--color-secondary-tint-1-h),
|
||||
var(--color-secondary-tint-1-s),
|
||||
var(--color-secondary-h),
|
||||
var(--color-secondary-s),
|
||||
var(--color-secondary-tint-1-l)
|
||||
);
|
||||
|
||||
--color-secondary-tint-2-h: 247;
|
||||
--color-secondary-tint-2-s: 49%;
|
||||
--color-secondary-tint-2-l: 92%;
|
||||
--color-secondary-tint-2: hsl(
|
||||
var(--color-secondary-tint-2-h),
|
||||
var(--color-secondary-tint-2-s),
|
||||
var(--color-secondary-h),
|
||||
var(--color-secondary-s),
|
||||
var(--color-secondary-tint-2-l)
|
||||
);
|
||||
|
||||
--color-secondary-tint-3-h: 247;
|
||||
--color-secondary-tint-3-s: 49%;
|
||||
--color-secondary-tint-3-l: 95%;
|
||||
--color-secondary-tint-3: hsl(
|
||||
var(--color-secondary-tint-3-h),
|
||||
var(--color-secondary-tint-3-s),
|
||||
var(--color-secondary-h),
|
||||
var(--color-secondary-s),
|
||||
var(--color-secondary-tint-3-l)
|
||||
);
|
||||
|
||||
--color-secondary-tint-4-l: 98%;
|
||||
--color-secondary-tint-4: hsl(
|
||||
var(--color-secondary-h),
|
||||
var(--color-secondary-s),
|
||||
var(--color-secondary-tint-4-l)
|
||||
);
|
||||
|
||||
--color-success-h: 150.4;
|
||||
--color-success-s: 60%;
|
||||
--color-success-l: 40.4%;
|
||||
|
|
|
@ -31,7 +31,7 @@ import ColorCircles from './ColorCircles.vue';
|
|||
<Canvas>
|
||||
<Story name="secondary">
|
||||
{{
|
||||
template: `<color-circles :colors="['--color-secondary', '--color-secondary-tint-1', '--color-secondary-tint-2']" />`,
|
||||
template: `<color-circles :colors="['--color-secondary', '--color-secondary-tint-1', '--color-secondary-tint-2', '--color-secondary-tint-3', '--color-secondary-tint-4']" />`,
|
||||
components: {
|
||||
ColorCircles,
|
||||
},
|
||||
|
|
|
@ -212,11 +212,6 @@ export interface IStartRunData {
|
|||
pinData?: IPinData;
|
||||
}
|
||||
|
||||
export interface IRunDataUi {
|
||||
node?: string;
|
||||
workflowData: IWorkflowData;
|
||||
}
|
||||
|
||||
export interface ITableData {
|
||||
columns: string[];
|
||||
data: GenericValue[][];
|
||||
|
@ -863,6 +858,7 @@ export interface IRootState {
|
|||
oauthCallbackUrls: object;
|
||||
n8nMetadata: object;
|
||||
workflowExecutionData: IExecutionResponse | null;
|
||||
workflowExecutionPairedItemMappings: {[itemId: string]: Set<string>};
|
||||
lastSelectedNode: string | null;
|
||||
lastSelectedNodeOutputIndex: number | null;
|
||||
nodeViewOffsetPosition: XYPosition;
|
||||
|
@ -912,6 +908,13 @@ export interface IModalState {
|
|||
|
||||
export type IRunDataDisplayMode = 'table' | 'json' | 'binary';
|
||||
|
||||
export interface TargetItem {
|
||||
nodeName: string;
|
||||
itemIndex: number;
|
||||
runIndex: number;
|
||||
outputIndex: number;
|
||||
}
|
||||
|
||||
export interface IUiState {
|
||||
sidebarMenuCollapsed: boolean;
|
||||
modalStack: string[];
|
||||
|
@ -925,11 +928,15 @@ export interface IUiState {
|
|||
sessionId: string;
|
||||
input: {
|
||||
displayMode: IRunDataDisplayMode;
|
||||
nodeName?: string;
|
||||
run?: number;
|
||||
branch?: number;
|
||||
data: {
|
||||
isEmpty: boolean;
|
||||
}
|
||||
};
|
||||
output: {
|
||||
branch?: number;
|
||||
displayMode: IRunDataDisplayMode;
|
||||
data: {
|
||||
isEmpty: boolean;
|
||||
|
@ -941,6 +948,7 @@ export interface IUiState {
|
|||
};
|
||||
focusedMappableInput: string;
|
||||
mappingTelemetry: {[key: string]: string | number | boolean};
|
||||
hoveringItem: null | TargetItem;
|
||||
};
|
||||
mainPanelPosition: number;
|
||||
draggable: {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div @keydown.stop :class="$style.container" v-if="credentialProperties.length">
|
||||
<form v-for="parameter in credentialProperties" :key="parameter.name" autocomplete="off">
|
||||
<!-- Why form? to break up inputs, to prevent Chrome autofill -->
|
||||
<ParameterInputExpanded
|
||||
<parameter-input-expanded
|
||||
:parameter="parameter"
|
||||
:value="credentialData[parameter.name]"
|
||||
:documentationUrl="documentationUrl"
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
:label="$locale.nodeText().inputLabelDisplayName(property, path)"
|
||||
:underline="true"
|
||||
size="small"
|
||||
color="text-dark"
|
||||
/>
|
||||
<div v-if="multipleValues === true">
|
||||
<div
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
>
|
||||
<template slot="content">
|
||||
<div :class="$style.container">
|
||||
<n8n-input-label :label="$locale.baseText('importCurlModal.input.label')">
|
||||
<n8n-input-label :label="$locale.baseText('importCurlModal.input.label')" color="text-dark">
|
||||
<n8n-input
|
||||
:value="curlCommand"
|
||||
type="textarea"
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
:showMappingHint="draggableHintShown"
|
||||
:distanceFromActive="currentNodeDepth"
|
||||
paneType="input"
|
||||
@itemHover="$emit('itemHover', $event)"
|
||||
@linkRun="onLinkRun"
|
||||
@unlinkRun="onUnlinkRun"
|
||||
@runChange="onRunIndexChange"
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
:tooltipText="$locale.nodeText().inputLabelDescription(parameter, path)"
|
||||
:underline="true"
|
||||
size="small"
|
||||
color="text-dark"
|
||||
/>
|
||||
|
||||
<div v-for="(value, index) in values" :key="index" class="duplicate-parameter-item" :class="parameter.type">
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
:bold="false"
|
||||
:set="issues = getIssues(credentialTypeDescription.name)"
|
||||
size="small"
|
||||
color="text-dark"
|
||||
>
|
||||
<div v-if="isReadOnly">
|
||||
<n8n-input
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
@select="onInputSelect"
|
||||
@execute="onNodeExecute"
|
||||
@tableMounted="onInputTableMounted"
|
||||
@itemHover="onInputItemHover"
|
||||
/>
|
||||
</template>
|
||||
<template #output>
|
||||
|
@ -76,6 +77,7 @@
|
|||
@runChange="onRunOutputIndexChange"
|
||||
@openSettings="openSettings"
|
||||
@tableMounted="onOutputTableMounted"
|
||||
@itemHover="onOutputItemHover"
|
||||
/>
|
||||
</template>
|
||||
<template #main>
|
||||
|
@ -111,7 +113,7 @@ import {
|
|||
IRunExecutionData,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import { IExecutionResponse, INodeUi, IUpdateInformation } from '../Interface';
|
||||
import { IExecutionResponse, INodeUi, IUpdateInformation, TargetItem } from '../Interface';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
|
@ -200,7 +202,7 @@ export default mixins(
|
|||
this.executionWaitingForWebhook
|
||||
);
|
||||
},
|
||||
activeNode(): INodeUi {
|
||||
activeNode(): INodeUi | null {
|
||||
return this.$store.getters.activeNode;
|
||||
},
|
||||
inputNodeName(): string | undefined {
|
||||
|
@ -394,8 +396,45 @@ export default mixins(
|
|||
maxInputRun() {
|
||||
this.runInputIndex = -1;
|
||||
},
|
||||
inputNodeName(nodeName: string | undefined) {
|
||||
this.$store.commit('ui/setInputNodeName', nodeName);
|
||||
},
|
||||
inputRun() {
|
||||
this.$store.commit('ui/setInputRunIndex', this.inputRun);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onInputItemHover(e: {itemIndex: number, outputIndex: number} | null) {
|
||||
if (!this.inputNodeName) {
|
||||
return;
|
||||
}
|
||||
if (e === null) {
|
||||
this.$store.commit('ui/setHoveringItem', null);
|
||||
return;
|
||||
}
|
||||
|
||||
const item: TargetItem = {
|
||||
nodeName: this.inputNodeName,
|
||||
runIndex: this.inputRun,
|
||||
outputIndex: e.outputIndex,
|
||||
itemIndex: e.itemIndex,
|
||||
};
|
||||
this.$store.commit('ui/setHoveringItem', item);
|
||||
},
|
||||
onOutputItemHover(e: {itemIndex: number, outputIndex: number} | null) {
|
||||
if (e === null || !this.activeNode) {
|
||||
this.$store.commit('ui/setHoveringItem', null);
|
||||
return;
|
||||
}
|
||||
|
||||
const item: TargetItem = {
|
||||
nodeName: this.activeNode.name,
|
||||
runIndex: this.outputRun,
|
||||
outputIndex: e.outputIndex,
|
||||
itemIndex: e.itemIndex,
|
||||
};
|
||||
this.$store.commit('ui/setHoveringItem', item);
|
||||
},
|
||||
onInputTableMounted(e: { avgRowHeight: number }) {
|
||||
this.avgInputRowHeight = e.avgRowHeight;
|
||||
},
|
||||
|
@ -410,13 +449,15 @@ export default mixins(
|
|||
},
|
||||
onFeatureRequestClick() {
|
||||
window.open(this.featureRequestUrl, '_blank');
|
||||
this.$telemetry.track('User clicked ndv link', {
|
||||
node_type: this.activeNode.type,
|
||||
workflow_id: this.$store.getters.workflowId,
|
||||
session_id: this.sessionId,
|
||||
pane: 'main',
|
||||
type: 'i-wish-this-node-would',
|
||||
});
|
||||
if (this.activeNode) {
|
||||
this.$telemetry.track('User clicked ndv link', {
|
||||
node_type: this.activeNode.type,
|
||||
workflow_id: this.$store.getters.workflowId,
|
||||
session_id: this.sessionId,
|
||||
pane: 'main',
|
||||
type: 'i-wish-this-node-would',
|
||||
});
|
||||
}
|
||||
},
|
||||
onPanelsInit(e: { position: number }) {
|
||||
this.mainPanelPosition = e.position;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
@linkRun="onLinkRun"
|
||||
@unlinkRun="onUnlinkRun"
|
||||
@tableMounted="$emit('tableMounted', $event)"
|
||||
@itemHover="$emit('itemHover', $event)"
|
||||
ref="runData"
|
||||
>
|
||||
<template v-slot:header>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
:value="value"
|
||||
:displayTitle="displayTitle"
|
||||
:expressionDisplayValue="expressionDisplayValue"
|
||||
:expressionComputedValue="expressionEvaluated"
|
||||
:isValueExpression="isValueExpression"
|
||||
:isReadOnly="isReadOnly"
|
||||
:parameterIssues="getIssues"
|
||||
|
@ -37,7 +38,7 @@
|
|||
:size="inputSize"
|
||||
:type="getStringInputType"
|
||||
:rows="getArgument('rows')"
|
||||
:value="activeDrop || forceShowExpression? '': expressionDisplayValue"
|
||||
:value="expressionDisplayValue"
|
||||
:title="displayTitle"
|
||||
@keydown.stop
|
||||
/>
|
||||
|
@ -297,6 +298,8 @@ import {
|
|||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
Workflow,
|
||||
INodeProperties,
|
||||
INodePropertyCollection,
|
||||
NodeParameterValueType,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -316,12 +319,13 @@ import { externalHooks } from '@/components/mixins/externalHooks';
|
|||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import { hasExpressionMapping, isValueExpression } from './helpers';
|
||||
import { isResourceLocatorValue } from '@/typeGuards';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { CUSTOM_API_CALL_KEY } from '@/constants';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { hasExpressionMapping, isValueExpression } from './helpers';
|
||||
import { isResourceLocatorValue } from '@/typeGuards';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
export default mixins(
|
||||
externalHooks,
|
||||
|
@ -344,21 +348,56 @@ export default mixins(
|
|||
TextEdit,
|
||||
ImportParameter,
|
||||
},
|
||||
props: [
|
||||
'inputSize',
|
||||
'isReadOnly',
|
||||
'documentationUrl',
|
||||
'parameter', // NodeProperties
|
||||
'path', // string
|
||||
'value',
|
||||
'hideIssues', // boolean
|
||||
'errorHighlight',
|
||||
'isForCredential', // boolean
|
||||
'eventSource', // string
|
||||
'activeDrop',
|
||||
'droppable',
|
||||
'forceShowExpression',
|
||||
],
|
||||
props: {
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
},
|
||||
parameter: {
|
||||
type: Object as PropType<INodeProperties>,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
||||
},
|
||||
hideLabel: {
|
||||
type: Boolean,
|
||||
},
|
||||
droppable: {
|
||||
type: Boolean,
|
||||
},
|
||||
activeDrop: {
|
||||
type: Boolean,
|
||||
},
|
||||
forceShowExpression: {
|
||||
type: Boolean,
|
||||
},
|
||||
hint: {
|
||||
type: String as PropType<string | undefined>,
|
||||
},
|
||||
inputSize: {
|
||||
type: String,
|
||||
},
|
||||
hideIssues: {
|
||||
type: Boolean,
|
||||
},
|
||||
documentationUrl: {
|
||||
type: String as PropType<string | undefined>,
|
||||
},
|
||||
errorHighlight: {
|
||||
type: Boolean,
|
||||
},
|
||||
isForCredential: {
|
||||
type: Boolean,
|
||||
},
|
||||
eventSource: {
|
||||
type: String,
|
||||
},
|
||||
expressionEvaluated: {
|
||||
type: String as PropType<string | undefined>,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
codeEditDialogVisible: false,
|
||||
|
@ -419,12 +458,21 @@ export default mixins(
|
|||
},
|
||||
computed: {
|
||||
...mapGetters('credentials', ['allCredentialTypes']),
|
||||
expressionDisplayValue(): string {
|
||||
if (this.activeDrop || this.forceShowExpression) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const value = isResourceLocatorValue(this.value) ? this.value.value : this.value;
|
||||
if (typeof value === 'string' && value.startsWith('=')) {
|
||||
return value.slice(1);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
isValueExpression(): boolean {
|
||||
return isValueExpression(this.parameter, this.value);
|
||||
},
|
||||
areExpressionsDisabled(): boolean {
|
||||
return this.$store.getters['ui/areExpressionsDisabled'];
|
||||
},
|
||||
codeAutocomplete (): string | undefined {
|
||||
return this.getArgument('codeAutocomplete') as string | undefined;
|
||||
},
|
||||
|
@ -486,9 +534,9 @@ export default mixins(
|
|||
|
||||
let returnValue;
|
||||
if (this.isValueExpression === false) {
|
||||
returnValue = this.isResourceLocatorParameter ? (this.value ? this.value.value: '') : this.value;
|
||||
returnValue = this.isResourceLocatorParameter ? (isResourceLocatorValue(this.value) ? this.value.value: '') : this.value;
|
||||
} else {
|
||||
returnValue = this.expressionValueComputed;
|
||||
returnValue = this.expressionEvaluated;
|
||||
}
|
||||
|
||||
if (this.parameter.type === 'credentialsSelect') {
|
||||
|
@ -519,39 +567,6 @@ export default mixins(
|
|||
|
||||
return returnValue;
|
||||
},
|
||||
expressionDisplayValue (): string {
|
||||
const value = this.displayValue;
|
||||
|
||||
// address type errors for text input
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
expressionValueComputed (): NodeParameterValue | string[] | null {
|
||||
if (this.areExpressionsDisabled) {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
if (this.node === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let computedValue: NodeParameterValue;
|
||||
|
||||
try {
|
||||
computedValue = this.resolveExpression(this.value.value || this.value) as NodeParameterValue;
|
||||
} catch (error) {
|
||||
computedValue = `[${this.$locale.baseText('parameterInput.error')}}: ${error.message}]`;
|
||||
}
|
||||
|
||||
return computedValue;
|
||||
},
|
||||
getStringInputType () {
|
||||
if (this.getArgument('password') === true) {
|
||||
return 'password';
|
||||
|
@ -562,7 +577,7 @@ export default mixins(
|
|||
return 'textarea';
|
||||
}
|
||||
|
||||
if (this.parameter.type === 'code') {
|
||||
if (this.parameter.typeOptions && this.parameter.typeOptions.editor === 'code') {
|
||||
return 'textarea';
|
||||
}
|
||||
|
||||
|
@ -587,13 +602,14 @@ export default mixins(
|
|||
} else if (
|
||||
['options', 'multiOptions'].includes(this.parameter.type) &&
|
||||
this.remoteParameterOptionsLoading === false &&
|
||||
this.remoteParameterOptionsLoadingIssues === null
|
||||
this.remoteParameterOptionsLoadingIssues === null &&
|
||||
this.parameterOptions
|
||||
) {
|
||||
// Check if the value resolves to a valid option
|
||||
// Currently it only displays an error in the node itself in
|
||||
// case the value is not valid. The workflow can still be executed
|
||||
// and the error is not displayed on the node in the workflow
|
||||
const validOptions = this.parameterOptions!.map((options: INodePropertyOptions) => options.value);
|
||||
const validOptions = this.parameterOptions.map((options) => (options as INodePropertyOptions).value);
|
||||
|
||||
const checkValues: string[] = [];
|
||||
|
||||
|
@ -640,7 +656,7 @@ export default mixins(
|
|||
editorType (): string {
|
||||
return this.getArgument('editor') as string;
|
||||
},
|
||||
parameterOptions (): INodePropertyOptions[] {
|
||||
parameterOptions (): Array<INodePropertyOptions | INodeProperties | INodePropertyCollection> | undefined {
|
||||
if (this.hasRemoteMethod === false) {
|
||||
// Options are already given
|
||||
return this.parameter.options;
|
||||
|
@ -828,10 +844,6 @@ export default mixins(
|
|||
this.valueChanged(val);
|
||||
},
|
||||
openExpressionEdit() {
|
||||
if (this.areExpressionsDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isValueExpression) {
|
||||
this.expressionEditDialogVisible = true;
|
||||
this.trackExpressionEditOpen();
|
||||
|
@ -896,7 +908,7 @@ export default mixins(
|
|||
|
||||
this.$emit('textInput', parameterData);
|
||||
},
|
||||
valueChanged (value: string[] | string | number | boolean | Date | {} | null) {
|
||||
valueChanged (value: NodeParameterValueType | {} | Date) {
|
||||
if (this.parameter.name === 'nodeCredentialType') {
|
||||
this.activeCredentialType = value as string;
|
||||
}
|
||||
|
@ -905,7 +917,7 @@ export default mixins(
|
|||
value = value.toISOString();
|
||||
}
|
||||
|
||||
if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && value !== null && value.toString().charAt(0) !== '#') {
|
||||
if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && value !== null && value !== undefined && value.toString().charAt(0) !== '#') {
|
||||
const newValue = this.rgbaToHex(value as string);
|
||||
if (newValue !== null) {
|
||||
this.tempValue = newValue;
|
||||
|
@ -959,14 +971,14 @@ export default mixins(
|
|||
this.trackExpressionEditOpen();
|
||||
}, 375);
|
||||
} else if (command === 'removeExpression') {
|
||||
let value = this.expressionValueComputed;
|
||||
let value: NodeParameterValueType = this.expressionEvaluated;
|
||||
|
||||
if (this.parameter.type === 'multiOptions' && typeof value === 'string') {
|
||||
value = (value || '').split(',')
|
||||
.filter((value) => (this.parameterOptions || []).find((option) => option.value === value));
|
||||
.filter((value) => (this.parameterOptions || []).find((option) => (option as INodePropertyOptions).value === value));
|
||||
}
|
||||
|
||||
if (this.isResourceLocatorParameter) {
|
||||
if (this.isResourceLocatorParameter && isResourceLocatorValue(this.value)) {
|
||||
this.valueChanged({ __rl: true, value, mode: this.value.mode });
|
||||
} else {
|
||||
this.valueChanged(typeof value !== 'undefined' ? value : null);
|
||||
|
|
|
@ -18,19 +18,18 @@
|
|||
/>
|
||||
</template>
|
||||
<template>
|
||||
<parameter-input
|
||||
<parameter-input-wrapper
|
||||
ref="param"
|
||||
inputSize="large"
|
||||
:parameter="parameter"
|
||||
:value="value"
|
||||
:path="parameter.name"
|
||||
:hideIssues="true"
|
||||
:displayOptions="true"
|
||||
:documentationUrl="documentationUrl"
|
||||
:errorHighlight="showRequiredErrors"
|
||||
:isForCredential="true"
|
||||
:eventSource="eventSource"
|
||||
:isValueExpression="isValueExpression"
|
||||
:hint="!showRequiredErrors? hint: ''"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@textInput="valueChanged"
|
||||
|
@ -44,30 +43,27 @@
|
|||
</n8n-link>
|
||||
</n8n-text>
|
||||
</div>
|
||||
<input-hint :class="$style.hint" :hint="$locale.credText().hint(parameter)" />
|
||||
</template>
|
||||
</n8n-input-label>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { IUpdateInformation } from '@/Interface';
|
||||
import ParameterInput from './ParameterInput.vue';
|
||||
import ParameterOptions from './ParameterOptions.vue';
|
||||
import InputHint from './ParameterInputHint.vue';
|
||||
import Vue from 'vue';
|
||||
import Vue, { PropType } from 'vue';
|
||||
import ParameterInputWrapper from './ParameterInputWrapper.vue';
|
||||
import { isValueExpression } from './helpers';
|
||||
import { INodeParameterResourceLocator, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ParameterInputExpanded',
|
||||
name: 'parameter-input-expanded',
|
||||
components: {
|
||||
ParameterInput,
|
||||
InputHint,
|
||||
ParameterOptions,
|
||||
ParameterInputWrapper,
|
||||
},
|
||||
props: {
|
||||
parameter: {
|
||||
type: Object as () => INodeProperties,
|
||||
type: Object as PropType<INodeProperties>,
|
||||
},
|
||||
value: {
|
||||
},
|
||||
|
@ -106,6 +102,13 @@ export default Vue.extend({
|
|||
|
||||
return false;
|
||||
},
|
||||
hint(): string | null {
|
||||
if (this.isValueExpression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.$locale.credText().hint(this.parameter);
|
||||
},
|
||||
isValueExpression (): boolean {
|
||||
return isValueExpression(this.parameter, this.value as string | INodeParameterResourceLocator);
|
||||
},
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
:showOptions="menuExpanded || focused || forceShowExpression"
|
||||
:bold="false"
|
||||
size="small"
|
||||
color="text-dark"
|
||||
>
|
||||
<template #options>
|
||||
<parameter-options
|
||||
|
@ -34,16 +35,16 @@
|
|||
:buttons="dataMappingTooltipButtons"
|
||||
>
|
||||
<span slot="content" v-html="$locale.baseText(`dataMapping.${displayMode}Hint`, { interpolate: { name: parameter.displayName } })" />
|
||||
<parameter-input
|
||||
<parameter-input-wrapper
|
||||
ref="param"
|
||||
:parameter="parameter"
|
||||
:value="value"
|
||||
:displayOptions="displayOptions"
|
||||
:path="path"
|
||||
:isReadOnly="isReadOnly"
|
||||
:droppable="droppable"
|
||||
:activeDrop="activeDrop"
|
||||
:forceShowExpression="forceShowExpression"
|
||||
:hint="hint"
|
||||
@valueChanged="valueChanged"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
|
@ -53,7 +54,6 @@
|
|||
</n8n-tooltip>
|
||||
</template>
|
||||
</draggable-target>
|
||||
<input-hint :class="$style.hint" :hint="$locale.nodeText().hint(parameter, path)" />
|
||||
</template>
|
||||
</n8n-input-label>
|
||||
</template>
|
||||
|
@ -68,7 +68,6 @@ import {
|
|||
IUpdateInformation,
|
||||
} from '@/Interface';
|
||||
|
||||
import ParameterInput from '@/components/ParameterInput.vue';
|
||||
import InputHint from './ParameterInputHint.vue';
|
||||
import ParameterOptions from './ParameterOptions.vue';
|
||||
import DraggableTarget from '@/components/DraggableTarget.vue';
|
||||
|
@ -76,6 +75,7 @@ import mixins from 'vue-typed-mixins';
|
|||
import { showMessage } from './mixins/showMessage';
|
||||
import { LOCAL_STORAGE_MAPPING_FLAG } from '@/constants';
|
||||
import { hasExpressionMapping } from './helpers';
|
||||
import ParameterInputWrapper from './ParameterInputWrapper.vue';
|
||||
import { hasOnlyListMode } from './ResourceLocator/helpers';
|
||||
import { INodePropertyMode } from 'n8n-workflow';
|
||||
import { isResourceLocatorValue } from '@/typeGuards';
|
||||
|
@ -87,10 +87,10 @@ export default mixins(
|
|||
.extend({
|
||||
name: 'parameter-input-full',
|
||||
components: {
|
||||
ParameterInput,
|
||||
InputHint,
|
||||
ParameterOptions,
|
||||
DraggableTarget,
|
||||
ParameterInputWrapper,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -125,6 +125,9 @@ export default mixins(
|
|||
node (): INodeUi | null {
|
||||
return this.$store.getters.activeNode;
|
||||
},
|
||||
hint (): string | null {
|
||||
return this.$locale.nodeText().hint(this.parameter, this.path);
|
||||
},
|
||||
isResourceLocator (): boolean {
|
||||
return this.parameter.type === 'resourceLocator';
|
||||
},
|
||||
|
@ -256,9 +259,3 @@ export default mixins(
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.hint {
|
||||
margin-top: var(--spacing-4xs);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<n8n-text size="xsmall" color="text-base" v-if="hint">
|
||||
<div ref="hint" v-html="hint"></div>
|
||||
</n8n-text>
|
||||
</div>
|
||||
<n8n-text size="small" color="text-base" tag="div" v-if="hint">
|
||||
<div ref="hint" :class="{[$style.hint]: true, [$style.highlight]: highlight}" v-html="hint"></div>
|
||||
</n8n-text>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -11,7 +9,14 @@ import Vue from "vue";
|
|||
|
||||
export default Vue.extend({
|
||||
name: 'InputHint',
|
||||
props: ['hint'],
|
||||
props: {
|
||||
hint: {
|
||||
type: String,
|
||||
},
|
||||
highlight: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
if(this.$refs.hint){
|
||||
(this.$refs.hint as Element).querySelectorAll('a').forEach(a => a.target = "_blank");
|
||||
|
@ -20,3 +25,17 @@ export default Vue.extend({
|
|||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.hint {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
:tooltipText="$locale.nodeText().inputLabelDescription(parameter, path)"
|
||||
size="small"
|
||||
:underline="true"
|
||||
color="text-dark"
|
||||
/>
|
||||
<collection-parameter
|
||||
v-if="parameter.type === 'collection'"
|
||||
|
|
210
packages/editor-ui/src/components/ParameterInputWrapper.vue
Normal file
210
packages/editor-ui/src/components/ParameterInputWrapper.vue
Normal file
|
@ -0,0 +1,210 @@
|
|||
<template>
|
||||
<div>
|
||||
<parameter-input
|
||||
ref="param"
|
||||
:inputSize="inputSize"
|
||||
:parameter="parameter"
|
||||
:value="value"
|
||||
:path="path"
|
||||
:isReadOnly="isReadOnly"
|
||||
:droppable="droppable"
|
||||
:activeDrop="activeDrop"
|
||||
:forceShowExpression="forceShowExpression"
|
||||
:hideIssues="hideIssues"
|
||||
:documentationUrl="documentationUrl"
|
||||
:errorHighlight="errorHighlight"
|
||||
:isForCredential="isForCredential"
|
||||
:eventSource="eventSource"
|
||||
:expressionEvaluated="expressionValueComputed"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@drop="onDrop"
|
||||
@textInput="onTextInput"
|
||||
@valueChanged="onValueChanged" />
|
||||
<input-hint v-if="expressionOutput || parameterHint" :class="$style.hint" :highlight="!!(expressionOutput && targetItem)" :hint="expressionOutput || parameterHint" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue';
|
||||
|
||||
import ParameterInput from '@/components/ParameterInput.vue';
|
||||
import InputHint from './ParameterInputHint.vue';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { showMessage } from './mixins/showMessage';
|
||||
import { INodeProperties, INodePropertyMode, IRunData, isResourceLocatorValue, NodeParameterValue, NodeParameterValueType } from 'n8n-workflow';
|
||||
import { INodeUi, IUiState, IUpdateInformation, TargetItem } from '@/Interface';
|
||||
import { workflowHelpers } from './mixins/workflowHelpers';
|
||||
import { isValueExpression } from './helpers';
|
||||
|
||||
export default mixins(
|
||||
showMessage,
|
||||
workflowHelpers,
|
||||
)
|
||||
.extend({
|
||||
name: 'parameter-input-wrapper',
|
||||
components: {
|
||||
ParameterInput,
|
||||
InputHint,
|
||||
},
|
||||
mounted() {
|
||||
this.$on('optionSelected', this.optionSelected);
|
||||
},
|
||||
props: {
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
},
|
||||
parameter: {
|
||||
type: Object as PropType<INodeProperties>,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
||||
},
|
||||
hideLabel: {
|
||||
type: Boolean,
|
||||
},
|
||||
droppable: {
|
||||
type: Boolean,
|
||||
},
|
||||
activeDrop: {
|
||||
type: Boolean,
|
||||
},
|
||||
forceShowExpression: {
|
||||
type: Boolean,
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
inputSize: {
|
||||
type: String,
|
||||
},
|
||||
hideIssues: {
|
||||
type: Boolean,
|
||||
},
|
||||
documentationUrl: {
|
||||
type: String as PropType<string | undefined>,
|
||||
},
|
||||
errorHighlight: {
|
||||
type: Boolean,
|
||||
},
|
||||
isForCredential: {
|
||||
type: Boolean,
|
||||
},
|
||||
eventSource: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isValueExpression () {
|
||||
return isValueExpression(this.parameter, this.value);
|
||||
},
|
||||
activeNode(): INodeUi | null {
|
||||
return this.$store.getters.activeNode;
|
||||
},
|
||||
selectedRLMode(): INodePropertyMode | undefined {
|
||||
if (typeof this.value !== 'object' ||this.parameter.type !== 'resourceLocator' || !isResourceLocatorValue(this.value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mode = this.value.mode;
|
||||
if (mode) {
|
||||
return this.parameter.modes?.find((m: INodePropertyMode) => m.name === mode);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
parameterHint(): string | undefined {
|
||||
if (this.isValueExpression) {
|
||||
return undefined;
|
||||
}
|
||||
if (this.selectedRLMode && this.selectedRLMode.hint) {
|
||||
return this.selectedRLMode.hint;
|
||||
}
|
||||
|
||||
return this.hint;
|
||||
},
|
||||
targetItem(): TargetItem | null {
|
||||
return this.$store.getters['ui/hoveringItem'];
|
||||
},
|
||||
expressionValueComputed (): string | null {
|
||||
const inputNodeName: string | undefined = this.$store.getters['ui/ndvInputNodeName'];
|
||||
const value = isResourceLocatorValue(this.value)? this.value.value: this.value;
|
||||
if (this.activeNode === null || !this.isValueExpression || typeof value !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const inputRunIndex: number | undefined = this.$store.getters['ui/ndvInputRunIndex'];
|
||||
const inputBranchIndex: number | undefined = this.$store.getters['ui/ndvInputBranchIndex'];
|
||||
|
||||
let computedValue: NodeParameterValue;
|
||||
try {
|
||||
const targetItem = this.targetItem ?? undefined;
|
||||
computedValue = this.resolveExpression(value, undefined, {targetItem, inputNodeName, inputRunIndex, inputBranchIndex});
|
||||
if (computedValue === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof computedValue === 'string' && computedValue.trim().length === 0) {
|
||||
computedValue = this.$locale.baseText('parameterInput.emptyString');
|
||||
}
|
||||
} catch (error) {
|
||||
computedValue = `[${this.$locale.baseText('parameterInput.error')}}: ${error.message}]`;
|
||||
}
|
||||
|
||||
return typeof computedValue === 'string' ? computedValue : JSON.stringify(computedValue);
|
||||
},
|
||||
expressionOutput(): string | null {
|
||||
if (this.isValueExpression && this.expressionValueComputed) {
|
||||
const inputData = this.$store.getters['ui/ndvInputData'];
|
||||
if (!inputData || (inputData && inputData.length <= 1)) {
|
||||
return this.expressionValueComputed;
|
||||
}
|
||||
|
||||
return this.$locale.baseText(`parameterInput.expressionResult`, {
|
||||
interpolate: {
|
||||
result: this.expressionValueComputed,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onFocus() {
|
||||
this.$emit('focus');
|
||||
},
|
||||
onBlur() {
|
||||
this.$emit('blur');
|
||||
},
|
||||
onDrop(data: string) {
|
||||
this.$emit('drop', data);
|
||||
},
|
||||
optionSelected(command: string) {
|
||||
if (this.$refs.param) {
|
||||
(this.$refs.param as Vue).$emit('optionSelected', command);
|
||||
}
|
||||
},
|
||||
onValueChanged(parameterData: IUpdateInformation) {
|
||||
this.$emit('valueChanged', parameterData);
|
||||
},
|
||||
onTextInput(parameterData: IUpdateInformation) {
|
||||
this.$emit('textInput', parameterData);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.hint {
|
||||
margin-top: var(--spacing-4xs);
|
||||
}
|
||||
|
||||
.hovering {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
</style>
|
|
@ -82,7 +82,7 @@
|
|||
v-if="isValueExpression || droppable || forceShowExpression"
|
||||
type="text"
|
||||
:size="inputSize"
|
||||
:value="activeDrop || forceShowExpression ? '' : expressionDisplayValue"
|
||||
:value="expressionDisplayValue"
|
||||
:title="displayTitle"
|
||||
@keydown.stop
|
||||
ref="input"
|
||||
|
@ -137,7 +137,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</resource-locator-dropdown>
|
||||
<parameter-input-hint v-if="infoText" class="mt-4xs" :hint="infoText" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -163,7 +162,6 @@ import {
|
|||
import DraggableTarget from '@/components/DraggableTarget.vue';
|
||||
import ExpressionEdit from '@/components/ExpressionEdit.vue';
|
||||
import ParameterIssues from '@/components/ParameterIssues.vue';
|
||||
import ParameterInputHint from '@/components/ParameterInputHint.vue';
|
||||
import ResourceLocatorDropdown from './ResourceLocatorDropdown.vue';
|
||||
import Vue, { PropType } from 'vue';
|
||||
import { INodeUi, IResourceLocatorReqParams, IResourceLocatorResultExpanded } from '@/Interface';
|
||||
|
@ -172,7 +170,6 @@ import stringify from 'fast-json-stable-stringify';
|
|||
import { workflowHelpers } from '../mixins/workflowHelpers';
|
||||
import { nodeHelpers } from '../mixins/nodeHelpers';
|
||||
import { getAppNameFromNodeName } from '../helpers';
|
||||
import { type } from 'os';
|
||||
import { isResourceLocatorValue } from '@/typeGuards';
|
||||
|
||||
interface IResourceLocatorQuery {
|
||||
|
@ -188,7 +185,6 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
DraggableTarget,
|
||||
ExpressionEdit,
|
||||
ParameterIssues,
|
||||
ParameterInputHint,
|
||||
ResourceLocatorDropdown,
|
||||
},
|
||||
props: {
|
||||
|
@ -216,7 +212,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
expressionDisplayValue: {
|
||||
expressionComputedValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
@ -224,6 +220,9 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
expressionDisplayValue: {
|
||||
type: String,
|
||||
},
|
||||
forceShowExpression: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -298,9 +297,6 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
|
||||
return defaults[this.selectedMode] || '';
|
||||
},
|
||||
infoText(): string {
|
||||
return this.currentMode.hint ? this.currentMode.hint : '';
|
||||
},
|
||||
currentMode(): INodePropertyMode {
|
||||
return this.findModeByName(this.selectedMode) || ({} as INodePropertyMode);
|
||||
},
|
||||
|
@ -327,8 +323,8 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
}
|
||||
|
||||
if (this.selectedMode === 'url') {
|
||||
if (this.isValueExpression && typeof this.expressionDisplayValue === 'string' && this.expressionDisplayValue.startsWith('http')) {
|
||||
return this.expressionDisplayValue;
|
||||
if (this.isValueExpression && typeof this.expressionComputedValue === 'string' && this.expressionComputedValue.startsWith('http')) {
|
||||
return this.expressionComputedValue;
|
||||
}
|
||||
|
||||
if (typeof this.valueToDisplay === 'string' && this.valueToDisplay.startsWith('http')) {
|
||||
|
@ -337,7 +333,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
}
|
||||
|
||||
if (this.currentMode.url) {
|
||||
const value = this.isValueExpression? this.expressionDisplayValue : this.valueToDisplay;
|
||||
const value = this.isValueExpression? this.expressionComputedValue : this.valueToDisplay;
|
||||
if (typeof value === 'string') {
|
||||
const expression = this.currentMode.url.replace(/\{\{\$value\}\}/g, value);
|
||||
const resolved = this.resolveExpression(expression);
|
||||
|
|
|
@ -220,8 +220,11 @@
|
|||
:distanceFromActive="distanceFromActive"
|
||||
:showMappingHint="showMappingHint"
|
||||
:runIndex="runIndex"
|
||||
:pageOffset="currentPageOffset"
|
||||
:totalRuns="maxRunIndex"
|
||||
:hasDefaultHoverState="paneType === 'input'"
|
||||
@mounted="$emit('tableMounted', $event)"
|
||||
@activeRowChanged="onItemHover"
|
||||
/>
|
||||
|
||||
<run-data-json
|
||||
|
@ -419,7 +422,7 @@ export default mixins(
|
|||
type: String,
|
||||
},
|
||||
overrideOutputs: {
|
||||
type: Array,
|
||||
type: Array as PropType<number[]>,
|
||||
},
|
||||
mappingEnabled: {
|
||||
type: Boolean,
|
||||
|
@ -463,6 +466,10 @@ export default mixins(
|
|||
this.showPinDataDiscoveryTooltip(this.jsonData);
|
||||
}
|
||||
}
|
||||
this.$store.commit('ui/setNDVBranchIndex', {
|
||||
pane: this.paneType,
|
||||
branchIndex: this.currentOutputIndex,
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
this.hidePinDataDiscoveryTooltip();
|
||||
|
@ -561,6 +568,9 @@ export default mixins(
|
|||
|
||||
return 0;
|
||||
},
|
||||
currentPageOffset(): number {
|
||||
return this.pageSize * (this.currentPage - 1);
|
||||
},
|
||||
maxRunIndex (): number {
|
||||
if (this.node === null) {
|
||||
return 0;
|
||||
|
@ -662,6 +672,17 @@ export default mixins(
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
onItemHover(itemIndex: number | null) {
|
||||
if (itemIndex === null) {
|
||||
this.$emit('itemHover', null);
|
||||
|
||||
return;
|
||||
}
|
||||
this.$emit('itemHover', {
|
||||
outputIndex: this.currentOutputIndex,
|
||||
itemIndex,
|
||||
});
|
||||
},
|
||||
onClickDataPinningDocsLink() {
|
||||
this.$telemetry.track('User clicked ndv link', {
|
||||
workflow_id: this.$store.getters.workflowId,
|
||||
|
@ -1094,6 +1115,12 @@ export default mixins(
|
|||
this.onDisplayModeChange('table');
|
||||
}
|
||||
},
|
||||
currentOutputIndex(branchIndex: number) {
|
||||
this.$store.commit('ui/setNDVBranchIndex', {
|
||||
pane: this.paneType,
|
||||
branchIndex,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -5,8 +5,13 @@
|
|||
<th :class="$style.emptyCell"></th>
|
||||
<th :class="$style.tableRightMargin"></th>
|
||||
</tr>
|
||||
<tr v-for="(row, index1) in tableData.data" :key="index1">
|
||||
<td>
|
||||
<tr v-for="(row, index1) in tableData.data" :key="index1" :class="{[$style.hoveringRow]: isHoveringRow(index1)}">
|
||||
<td
|
||||
:data-row="index1"
|
||||
:data-col="0"
|
||||
@mouseenter="onMouseEnterCell"
|
||||
@mouseleave="onMouseLeaveCell"
|
||||
>
|
||||
<n8n-text>{{ $locale.baseText('runData.emptyItemHint') }}</n8n-text>
|
||||
</td>
|
||||
<td :class="$style.tableRightMargin"></td>
|
||||
|
@ -88,10 +93,11 @@
|
|||
</div>
|
||||
</template>
|
||||
<template>
|
||||
<tr v-for="(row, index1) in tableData.data" :key="index1">
|
||||
<tr v-for="(row, index1) in tableData.data" :key="index1" :class="{[$style.hoveringRow]: isHoveringRow(index1)}">
|
||||
<td
|
||||
v-for="(data, index2) in row"
|
||||
:key="index2"
|
||||
:data-row="index1"
|
||||
:data-col="index2"
|
||||
@mouseenter="onMouseEnterCell"
|
||||
@mouseleave="onMouseLeaveCell"
|
||||
|
@ -136,9 +142,10 @@
|
|||
<script lang="ts">
|
||||
/* eslint-disable prefer-spread */
|
||||
|
||||
import { INodeUi, IRootState, ITableData, IUiState } from '@/Interface';
|
||||
import { getPairedItemId } from '@/pairedItemUtils';
|
||||
import Vue, { PropType } from 'vue';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { INodeUi, ITableData } from '@/Interface';
|
||||
import { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
import Draggable from './Draggable.vue';
|
||||
import { shorten } from './helpers';
|
||||
|
@ -163,9 +170,18 @@ export default mixins(externalHooks).extend({
|
|||
runIndex: {
|
||||
type: Number,
|
||||
},
|
||||
outputIndex: {
|
||||
type: Number,
|
||||
},
|
||||
totalRuns: {
|
||||
type: Number,
|
||||
},
|
||||
pageOffset: {
|
||||
type: Number,
|
||||
},
|
||||
hasDefaultHoverState: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -174,6 +190,7 @@ export default mixins(externalHooks).extend({
|
|||
draggingPath: null as null | string,
|
||||
hoveringPath: null as null | string,
|
||||
mappingHintVisible: false,
|
||||
activeRow: null as number | null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -187,12 +204,35 @@ export default mixins(externalHooks).extend({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
hoveringItem(): IUiState['ndv']['hoveringItem'] {
|
||||
return this.$store.getters['ui/hoveringItem'];
|
||||
},
|
||||
pairedItemMappings(): IRootState['workflowExecutionPairedItemMappings'] {
|
||||
return this.$store.getters['workflowExecutionPairedItemMappings'];
|
||||
},
|
||||
tableData(): ITableData {
|
||||
return this.convertToTable(this.inputData);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
shorten,
|
||||
isHoveringRow(row: number): boolean {
|
||||
if (row === this.activeRow) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const itemIndex = this.pageOffset + row;
|
||||
if (itemIndex === 0 && !this.hoveringItem && this.hasDefaultHoverState && this.distanceFromActive === 1) {
|
||||
return true;
|
||||
}
|
||||
const itemNodeId = getPairedItemId(this.node.name, this.runIndex || 0, this.outputIndex || 0, itemIndex);
|
||||
if (!this.hoveringItem || !this.pairedItemMappings[itemNodeId]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hoveringItemId = getPairedItemId(this.hoveringItem.nodeName, this.hoveringItem.runIndex, this.hoveringItem.outputIndex, this.hoveringItem.itemIndex);
|
||||
return this.pairedItemMappings[itemNodeId].has(hoveringItemId);
|
||||
},
|
||||
onMouseEnterCell(e: MouseEvent) {
|
||||
const target = e.target;
|
||||
if (target && this.mappingEnabled) {
|
||||
|
@ -201,9 +241,19 @@ export default mixins(externalHooks).extend({
|
|||
this.activeColumn = parseInt(col, 10);
|
||||
}
|
||||
}
|
||||
|
||||
if (target) {
|
||||
const row = (target as HTMLElement).dataset.row;
|
||||
if (row && !isNaN(parseInt(row, 10))) {
|
||||
this.activeRow = parseInt(row, 10);
|
||||
this.$emit('activeRowChanged', this.pageOffset + this.activeRow);
|
||||
}
|
||||
}
|
||||
},
|
||||
onMouseLeaveCell() {
|
||||
this.activeColumn = -1;
|
||||
this.activeRow = null;
|
||||
this.$emit('activeRowChanged', null);
|
||||
},
|
||||
onMouseEnterKey(path: string[], colIndex: number) {
|
||||
this.hoveringPath = this.getCellExpression(path, colIndex);
|
||||
|
@ -438,6 +488,7 @@ export default mixins(externalHooks).extend({
|
|||
position: sticky;
|
||||
top: 0;
|
||||
color: var(--color-text-dark);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
td {
|
||||
|
@ -449,6 +500,27 @@ export default mixins(externalHooks).extend({
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
td:first-child, td:nth-last-child(2) {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
&:after { // add border without shifting content
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 2px;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
td:nth-last-child(2):after {
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
td:first-child:after {
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
border-right: var(--border-base);
|
||||
|
@ -565,9 +637,16 @@ export default mixins(externalHooks).extend({
|
|||
|
||||
.tableRightMargin {
|
||||
// becomes necessary with large tables
|
||||
background-color: var(--color-background-base) !important;
|
||||
width: var(--spacing-s);
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.hoveringRow {
|
||||
td:first-child:after, td:nth-last-child(2):after {
|
||||
background-color: var(--color-secondary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -43,6 +43,7 @@ import {
|
|||
XYPosition,
|
||||
ITag,
|
||||
IUpdateInformation,
|
||||
TargetItem,
|
||||
} from '../../Interface';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
|
@ -54,6 +55,7 @@ import { isEqual } from 'lodash';
|
|||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { getSourceItems } from '@/pairedItemUtils';
|
||||
|
||||
let cachedWorkflowKey: string | null = '';
|
||||
let cachedWorkflow: Workflow | null = null;
|
||||
|
@ -98,6 +100,7 @@ export const workflowHelpers = mixins(
|
|||
|
||||
if (!workflowRunData[parentNodeName] ||
|
||||
workflowRunData[parentNodeName].length <= runIndex ||
|
||||
!workflowRunData[parentNodeName][runIndex] ||
|
||||
!workflowRunData[parentNodeName][runIndex].hasOwnProperty('data') ||
|
||||
workflowRunData[parentNodeName][runIndex].data === undefined ||
|
||||
!workflowRunData[parentNodeName][runIndex].data!.hasOwnProperty(inputName)
|
||||
|
@ -526,24 +529,47 @@ export const workflowHelpers = mixins(
|
|||
},
|
||||
|
||||
|
||||
resolveParameter(parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[]) {
|
||||
const itemIndex = 0;
|
||||
resolveParameter(parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], opts: {targetItem?: TargetItem, inputNodeName?: string, inputRunIndex?: number, inputBranchIndex?: number} = {}): IDataObject | null {
|
||||
let itemIndex = opts?.targetItem?.itemIndex || 0;
|
||||
|
||||
const inputName = 'main';
|
||||
const activeNode = this.$store.getters.activeNode;
|
||||
const workflow = this.getCurrentWorkflow();
|
||||
const parentNode = workflow.getParentNodes(activeNode.name, inputName, 1);
|
||||
const workflowRunData = this.$store.getters.getWorkflowRunData as IRunData | null;
|
||||
let parentNode = workflow.getParentNodes(activeNode.name, inputName, 1);
|
||||
const executionData = this.$store.getters.getWorkflowExecution as IExecutionResponse | null;
|
||||
|
||||
const workflowRunData = this.$store.getters.getWorkflowRunData as IRunData | null;
|
||||
let runIndexParent = 0;
|
||||
if (workflowRunData !== null && parentNode.length) {
|
||||
const firstParentWithWorkflowRunData = parentNode.find((parentNodeName) => workflowRunData[parentNodeName]);
|
||||
if (firstParentWithWorkflowRunData) {
|
||||
runIndexParent = workflowRunData[firstParentWithWorkflowRunData].length - 1;
|
||||
if (opts?.inputNodeName && !parentNode.includes(opts.inputNodeName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let runIndexParent = opts?.inputRunIndex ?? 0;
|
||||
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]);
|
||||
if (opts.targetItem && opts?.targetItem?.nodeName === activeNode.name && executionData) {
|
||||
const sourceItems = getSourceItems(executionData, opts.targetItem);
|
||||
if (!sourceItems.length) {
|
||||
return null;
|
||||
}
|
||||
parentNode = [sourceItems[0].nodeName];
|
||||
runIndexParent = sourceItems[0].runIndex;
|
||||
itemIndex = sourceItems[0].itemIndex;
|
||||
if (nodeConnection) {
|
||||
nodeConnection.sourceIndex = sourceItems[0].outputIndex;
|
||||
}
|
||||
} else {
|
||||
parentNode = opts.inputNodeName ? [opts.inputNodeName] : parentNode;
|
||||
if (nodeConnection) {
|
||||
nodeConnection.sourceIndex = opts.inputBranchIndex ?? nodeConnection.sourceIndex;
|
||||
}
|
||||
|
||||
if (opts?.inputRunIndex === undefined && workflowRunData !== null && parentNode.length) {
|
||||
const firstParentWithWorkflowRunData = parentNode.find((parentNodeName) => workflowRunData[parentNodeName]);
|
||||
if (firstParentWithWorkflowRunData) {
|
||||
runIndexParent = workflowRunData[firstParentWithWorkflowRunData].length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]);
|
||||
let connectionInputData = this.connectionInputData(parentNode, activeNode.name, inputName, runIndexParent, nodeConnection);
|
||||
|
||||
let runExecutionData: IRunExecutionData;
|
||||
|
@ -601,8 +627,8 @@ export const workflowHelpers = mixins(
|
|||
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
};
|
||||
|
||||
let runIndexCurrent = 0;
|
||||
if (workflowRunData !== null && workflowRunData[activeNode.name]) {
|
||||
let runIndexCurrent = opts?.targetItem?.runIndex ?? 0;
|
||||
if (opts?.targetItem === undefined && workflowRunData !== null && workflowRunData[activeNode.name]) {
|
||||
runIndexCurrent = workflowRunData[activeNode.name].length -1;
|
||||
}
|
||||
const executeData = this.executeData(parentNode, activeNode.name, inputName, runIndexCurrent);
|
||||
|
@ -610,12 +636,15 @@ export const workflowHelpers = mixins(
|
|||
return workflow.expression.getParameterValue(parameter, runExecutionData, runIndexCurrent, itemIndex, activeNode.name, connectionInputData, 'manual', this.$store.getters.timezone, additionalKeys, executeData, false) as IDataObject;
|
||||
},
|
||||
|
||||
resolveExpression(expression: string, siblingParameters: INodeParameters = {}) {
|
||||
resolveExpression(expression: string, siblingParameters: INodeParameters = {}, opts: {targetItem?: TargetItem, inputNodeName?: string, inputRunIndex?: number, inputBranchIndex?: number, c?: number} = {}) {
|
||||
const parameters = {
|
||||
'__xxxxxxx__': expression,
|
||||
...siblingParameters,
|
||||
};
|
||||
const returnData = this.resolveParameter(parameters) as IDataObject;
|
||||
const returnData: IDataObject | null = this.resolveParameter(parameters, opts);
|
||||
if (!returnData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof returnData['__xxxxxxx__'] === 'object') {
|
||||
const workflow = this.getCurrentWorkflow();
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
import Vue from 'vue';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
import {
|
||||
IExecutionResponse,
|
||||
IFakeDoor,
|
||||
IFakeDoorLocation,
|
||||
IRootState,
|
||||
|
@ -117,12 +118,16 @@ const module: Module<IUiState, IRootState> = {
|
|||
sessionId: '',
|
||||
input: {
|
||||
displayMode: 'table',
|
||||
nodeName: undefined,
|
||||
run: undefined,
|
||||
branch: undefined,
|
||||
data: {
|
||||
isEmpty: true,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
displayMode: 'table',
|
||||
branch: undefined,
|
||||
data: {
|
||||
isEmpty: true,
|
||||
},
|
||||
|
@ -133,6 +138,7 @@ const module: Module<IUiState, IRootState> = {
|
|||
},
|
||||
focusedMappableInput: '',
|
||||
mappingTelemetry: {},
|
||||
hoveringItem: null,
|
||||
},
|
||||
mainPanelPosition: 0.5,
|
||||
draggable: {
|
||||
|
@ -174,8 +180,17 @@ const module: Module<IUiState, IRootState> = {
|
|||
],
|
||||
},
|
||||
getters: {
|
||||
areExpressionsDisabled(state: IUiState) {
|
||||
return state.currentView === VIEWS.DEMO;
|
||||
ndvInputData: (state: IUiState, getters, rootState: IRootState, rootGetters) => {
|
||||
const executionData = rootGetters.getWorkflowExecution as IExecutionResponse | null;
|
||||
const inputNodeName: string | undefined = state.ndv.input.nodeName;
|
||||
const inputRunIndex: number = state.ndv.input.run ?? 0;
|
||||
const inputBranchIndex: number = state.ndv.input.branch?? 0;
|
||||
|
||||
if (!executionData || !inputNodeName || inputRunIndex === undefined || inputBranchIndex === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return executionData.data?.resultData?.runData?.[inputNodeName]?.[inputRunIndex]?.data?.main?.[inputBranchIndex];
|
||||
},
|
||||
isVersionsOpen: (state: IUiState) => {
|
||||
return state.modals[VERSIONS_MODAL_KEY].open;
|
||||
|
@ -230,9 +245,19 @@ const module: Module<IUiState, IRootState> = {
|
|||
mappingTelemetry: (state: IUiState) => state.ndv.mappingTelemetry,
|
||||
getCurrentView: (state: IUiState) => state.currentView,
|
||||
isNodeView: (state: IUiState) => [VIEWS.NEW_WORKFLOW.toString(), VIEWS.WORKFLOW.toString(), VIEWS.EXECUTION.toString()].includes(state.currentView),
|
||||
hoveringItem: (state: IUiState) => state.ndv.hoveringItem,
|
||||
ndvInputNodeName: (state: IUiState) => state.ndv.input.nodeName,
|
||||
ndvInputRunIndex: (state: IUiState) => state.ndv.input.run,
|
||||
ndvInputBranchIndex: (state: IUiState) => state.ndv.input.branch,
|
||||
getNDVDataIsEmpty: (state: IUiState) => (panel: 'input' | 'output'): boolean => state.ndv[panel].data.isEmpty,
|
||||
},
|
||||
mutations: {
|
||||
setInputNodeName: (state: IUiState, name: string | undefined) => {
|
||||
Vue.set(state.ndv.input, 'nodeName', name);
|
||||
},
|
||||
setInputRunIndex: (state: IUiState, run?: string) => {
|
||||
Vue.set(state.ndv.input, 'run', run);
|
||||
},
|
||||
setMainPanelDimensions: (state: IUiState, params: { panelType:string, dimensions: { relativeLeft?: number, relativeRight?: number, relativeWidth?: number }}) => {
|
||||
Vue.set(
|
||||
state.mainPanelDimensions,
|
||||
|
@ -337,6 +362,12 @@ const module: Module<IUiState, IRootState> = {
|
|||
resetMappingTelemetry(state: IUiState) {
|
||||
state.ndv.mappingTelemetry = {};
|
||||
},
|
||||
setHoveringItem(state: IUiState, item: null | IUiState['ndv']['hoveringItem']) {
|
||||
Vue.set(state.ndv, 'hoveringItem', item);
|
||||
},
|
||||
setNDVBranchIndex(state: IUiState, e: {pane: 'input' | 'output', branchIndex: number}) {
|
||||
Vue.set(state.ndv[e.pane], 'branch', e.branchIndex);
|
||||
},
|
||||
setNDVPanelDataIsEmpty(state: IUiState, payload: {panel: 'input' | 'output', isEmpty: boolean}) {
|
||||
Vue.set(state.ndv[payload.panel].data, 'isEmpty', payload.isEmpty);
|
||||
},
|
||||
|
|
159
packages/editor-ui/src/pairedItemUtils.ts
Normal file
159
packages/editor-ui/src/pairedItemUtils.ts
Normal file
|
@ -0,0 +1,159 @@
|
|||
import { INodeExecutionData, IPairedItemData, IRunData, ITaskData } from "n8n-workflow";
|
||||
import { IExecutionResponse, TargetItem } from "./Interface";
|
||||
import { isNotNull } from "./typeGuards";
|
||||
|
||||
export function getPairedItemId(node: string, run: number, output: number, item: number): string {
|
||||
return `${node}_r${run}_o${output}_i${item}`;
|
||||
}
|
||||
|
||||
|
||||
export function getSourceItems(data: IExecutionResponse, target: TargetItem): TargetItem[] {
|
||||
if (!data?.data?.resultData?.runData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const runData = data.data.resultData.runData;
|
||||
const taskData: ITaskData | undefined = runData[target.nodeName]?.[target.runIndex];
|
||||
const source = taskData?.source || [];
|
||||
if (source.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const item = taskData?.data?.main?.[target.outputIndex]?.[target.itemIndex];
|
||||
if (!item || item.pairedItem === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const pairedItem: IPairedItemData[] = Array.isArray(item.pairedItem) ? item.pairedItem : (typeof item.pairedItem === 'object' ? [item.pairedItem] : [{item: item.pairedItem}]);
|
||||
const sourceItems = pairedItem.map((item) => {
|
||||
const input = item.input || 0;
|
||||
return {
|
||||
nodeName: source?.[input]?.previousNode,
|
||||
runIndex: source?.[input]?.previousNodeRun || 0,
|
||||
itemIndex: item.item,
|
||||
outputIndex: source[input]?.previousNodeOutput || 0,
|
||||
};
|
||||
});
|
||||
|
||||
return sourceItems.filter((item): item is TargetItem => isNotNull(item));
|
||||
}
|
||||
|
||||
function addPairing(paths: {[item: string]: string[][]}, pairedItemId: string, pairedItem: IPairedItemData, sources: ITaskData['source']) {
|
||||
paths[pairedItemId] = paths[pairedItemId] || [];
|
||||
|
||||
const input = pairedItem.input || 0;
|
||||
const sourceNode = sources[input]?.previousNode;
|
||||
if (!sourceNode) { // trigger nodes for example
|
||||
paths[pairedItemId].push([pairedItemId]);
|
||||
return;
|
||||
}
|
||||
const sourceNodeOutput = sources[input]?.previousNodeOutput || 0;
|
||||
const sourceNodeRun = sources[input]?.previousNodeRun || 0;
|
||||
|
||||
const sourceItem = getPairedItemId(sourceNode, sourceNodeRun, sourceNodeOutput, pairedItem.item);
|
||||
if (!paths[sourceItem]) {
|
||||
paths[sourceItem] = [[sourceItem]]; // pinned data case
|
||||
}
|
||||
paths[sourceItem]?.forEach((path) => {
|
||||
paths?.[pairedItemId]?.push([...path, pairedItemId]);
|
||||
});
|
||||
}
|
||||
|
||||
function addPairedItemIdsRec(node: string, runIndex: number, runData: IRunData, seen: Set<string>, paths: {[item: string]: string[][]}, pinned: Set<string>) {
|
||||
const key = `${node}_r${runIndex}`;
|
||||
if (seen.has(key)) {
|
||||
return;
|
||||
}
|
||||
seen.add(key);
|
||||
|
||||
if (pinned.has(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeRunData = runData[node];
|
||||
if (!Array.isArray(nodeRunData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = nodeRunData[runIndex];
|
||||
if (!data?.data?.main) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sources = data.source || [];
|
||||
sources.forEach((source) => {
|
||||
if (source?.previousNode) {
|
||||
addPairedItemIdsRec(source.previousNode, source.previousNodeRun ?? 0, runData, seen, paths, pinned);
|
||||
}
|
||||
});
|
||||
|
||||
const mainData = data.data.main || [];
|
||||
mainData.forEach((outputData, output: number) => {
|
||||
if (!outputData) {
|
||||
return;
|
||||
}
|
||||
|
||||
outputData.forEach((executionData, item: number) => {
|
||||
const pairedItemId = getPairedItemId(node, runIndex, output, item);
|
||||
if (!executionData.pairedItem) {
|
||||
paths[pairedItemId] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const pairedItem = executionData.pairedItem;
|
||||
if (Array.isArray(pairedItem)) {
|
||||
pairedItem.forEach((item) => {
|
||||
addPairing(paths, pairedItemId, item, sources);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof pairedItem === 'object') {
|
||||
addPairing(paths, pairedItemId, pairedItem, sources);
|
||||
return;
|
||||
}
|
||||
|
||||
addPairing(paths, pairedItemId, {item: pairedItem}, sources);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getMapping(paths: {[item: string]: string[][]}): {[item: string]: Set<string>} {
|
||||
const mapping: {[itemId: string]: Set<string>} = {};
|
||||
|
||||
Object.keys(paths).forEach((item) => {
|
||||
paths?.[item]?.forEach((path) => {
|
||||
path.forEach((otherItem) => {
|
||||
if (otherItem !== item) {
|
||||
mapping[otherItem] = mapping[otherItem] || new Set();
|
||||
mapping[otherItem].add(item);
|
||||
|
||||
mapping[item] = mapping[item] || new Set();
|
||||
mapping[item].add(otherItem);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
export function getPairedItemsMapping(executionResponse: IExecutionResponse | null): {[itemId: string]: Set<string>} {
|
||||
if (!executionResponse?.data?.resultData?.runData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
const runData = executionResponse.data.resultData.runData;
|
||||
|
||||
const pinned = new Set(Object.keys(executionResponse.data.resultData.pinData || {}));
|
||||
|
||||
const paths: {[item: string]: string[][]} = {};
|
||||
Object.keys(runData).forEach((node) => {
|
||||
runData[node].forEach((_, runIndex: number) => {
|
||||
addPairedItemIdsRec(node, runIndex, runData, seen, paths, pinned);
|
||||
});
|
||||
});
|
||||
|
||||
return getMapping(paths);
|
||||
}
|
|
@ -124,11 +124,11 @@ export class I18nClass {
|
|||
* Hint for a top-level param.
|
||||
*/
|
||||
hint(
|
||||
{ name: parameterName, hint }: { name: string; hint: string; },
|
||||
{ name: parameterName, hint }: { name: string; hint?: string; },
|
||||
) {
|
||||
return context.dynamicRender({
|
||||
key: `${credentialPrefix}.${parameterName}.hint`,
|
||||
fallback: hint,
|
||||
fallback: hint || '',
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -174,11 +174,11 @@ export class I18nClass {
|
|||
* Placeholder for a `string` param.
|
||||
*/
|
||||
placeholder(
|
||||
{ name: parameterName, placeholder }: { name: string; placeholder: string; },
|
||||
{ name: parameterName, placeholder }: { name: string; placeholder?: string; },
|
||||
) {
|
||||
return context.dynamicRender({
|
||||
key: `${credentialPrefix}.${parameterName}.placeholder`,
|
||||
fallback: placeholder,
|
||||
fallback: placeholder || '',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -247,7 +247,7 @@ export class I18nClass {
|
|||
* - For a `collection` or `fixedCollection`, the placeholder is the button text.
|
||||
*/
|
||||
placeholder(
|
||||
parameter: { name: string; placeholder: string; type: string },
|
||||
parameter: { name: string; placeholder?: string; type: string },
|
||||
path: string,
|
||||
) {
|
||||
let middleKey = parameter.name;
|
||||
|
@ -259,7 +259,7 @@ export class I18nClass {
|
|||
|
||||
return context.dynamicRender({
|
||||
key: `${initialKey}.${middleKey}.placeholder`,
|
||||
fallback: parameter.placeholder,
|
||||
fallback: parameter.placeholder || '',
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -635,6 +635,8 @@
|
|||
"onboardingWorkflow.stickyContent": "## 👇 Get started faster \nLightning tour of the key concepts [3 min] \n\n[](https://www.youtube.com/watch?v=RpjQTGKm-ok)",
|
||||
"openWorkflow.workflowImportError": "Could not import workflow",
|
||||
"openWorkflow.workflowNotFoundError": "Could not find workflow",
|
||||
"parameterInput.expressionResult": "e.g. {result}",
|
||||
"parameterInput.emptyString": "[empty]",
|
||||
"parameterInput.customApiCall": "Custom API Call",
|
||||
"parameterInput.error": "ERROR",
|
||||
"parameterInput.expression": "Expression",
|
||||
|
|
|
@ -3,7 +3,6 @@ import Vuex from 'vuex';
|
|||
|
||||
import {
|
||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
DEFAULT_NODETYPE_VERSION,
|
||||
} from '@/constants';
|
||||
|
||||
import {
|
||||
|
@ -48,6 +47,7 @@ import {stringSizeInBytes} from "@/components/helpers";
|
|||
import {dataPinningEventBus} from "@/event-bus/data-pinning-event-bus";
|
||||
import communityNodes from './modules/communityNodes';
|
||||
import { isJsonKeyObject } from './utils';
|
||||
import { getPairedItemsMapping } from './pairedItemUtils';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
|
@ -77,6 +77,7 @@ const state: IRootState = {
|
|||
oauthCallbackUrls: {},
|
||||
n8nMetadata: {},
|
||||
workflowExecutionData: null,
|
||||
workflowExecutionPairedItemMappings: {},
|
||||
lastSelectedNode: null,
|
||||
lastSelectedNodeOutputIndex: null,
|
||||
nodeViewOffsetPosition: [0, 0],
|
||||
|
@ -379,6 +380,8 @@ export const store = new Vuex.Store({
|
|||
Vue.set(state.workflow.pinData, nameData.new, state.workflow.pinData[nameData.old]);
|
||||
Vue.delete(state.workflow.pinData, nameData.old);
|
||||
}
|
||||
|
||||
state.workflowExecutionPairedItemMappings = getPairedItemsMapping(state.workflowExecutionData);
|
||||
},
|
||||
|
||||
resetAllNodesIssues(state) {
|
||||
|
@ -633,6 +636,7 @@ export const store = new Vuex.Store({
|
|||
|
||||
setWorkflowExecutionData(state, workflowResultData: IExecutionResponse | null) {
|
||||
state.workflowExecutionData = workflowResultData;
|
||||
state.workflowExecutionPairedItemMappings = getPairedItemsMapping(state.workflowExecutionData);
|
||||
},
|
||||
addNodeExecutionData(state, pushData: IPushDataNodeExecuteAfter): void {
|
||||
if (state.workflowExecutionData === null || !state.workflowExecutionData.data) {
|
||||
|
@ -642,6 +646,7 @@ export const store = new Vuex.Store({
|
|||
Vue.set(state.workflowExecutionData.data.resultData.runData, pushData.nodeName, []);
|
||||
}
|
||||
state.workflowExecutionData.data.resultData.runData[pushData.nodeName].push(pushData.data);
|
||||
state.workflowExecutionPairedItemMappings = getPairedItemsMapping(state.workflowExecutionData);
|
||||
},
|
||||
clearNodeExecutionData(state, nodeName: string): void {
|
||||
if (state.workflowExecutionData === null || !state.workflowExecutionData.data) {
|
||||
|
@ -709,6 +714,9 @@ export const store = new Vuex.Store({
|
|||
},
|
||||
},
|
||||
getters: {
|
||||
workflowExecutionPairedItemMappings: (state): IRootState['workflowExecutionPairedItemMappings'] => {
|
||||
return state.workflowExecutionPairedItemMappings;
|
||||
},
|
||||
executedNode: (state): string | undefined => {
|
||||
return state.workflowExecutionData ? state.workflowExecutionData.executedNode : undefined;
|
||||
},
|
||||
|
|
|
@ -3,3 +3,7 @@ import { INodeParameterResourceLocator } from "n8n-workflow";
|
|||
export function isResourceLocatorValue(value: unknown): value is INodeParameterResourceLocator {
|
||||
return Boolean(typeof value === 'object' && value && 'mode' in value && 'value' in value);
|
||||
}
|
||||
|
||||
export function isNotNull<T>(value: T | null): value is T {
|
||||
return value !== null;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue