feat(editor): Add mapping support for data paths (#5191)

* feat: add data path flag

* chore: update types

* feat: use path for data

* feat: add support for multiple values

* fix: handle if not prev node

* fix: update node

* fix: handle multi part path

* feat: add support for multiple vals for field

* feat: add support for table transforms

* feat: use dot notation

* feat: fix bug where brackets removed

* fix: handle dots, fix unit tests

* test: update snapshot

* test: fix tests

* test: add test for edge case
This commit is contained in:
Mutasem Aldmour 2023-01-30 14:42:08 +03:00 committed by GitHub
parent 5b9c650e55
commit 6092f6c41e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 679 additions and 66 deletions

View file

@ -229,12 +229,31 @@ export default mixins(showMessage).extend({
}
},
onDrop(data: string) {
this.forceShowExpression = true;
const useDataPath = !!this.parameter.requiresDataPath && data.startsWith('{{ $json');
if (!useDataPath) {
this.forceShowExpression = true;
}
setTimeout(() => {
if (this.node) {
const prevValue = this.isResourceLocator ? this.value.value : this.value;
let updatedValue: string;
if (typeof prevValue === 'string' && prevValue.startsWith('=') && prevValue.length > 1) {
if (useDataPath) {
const newValue = data
.replace('{{ $json', '')
.replace(new RegExp('^\\.'), '')
.replace(new RegExp('}}$'), '')
.trim();
if (prevValue && this.parameter.requiresDataPath === 'multiple') {
updatedValue = `${prevValue}, ${newValue}`;
} else {
updatedValue = newValue;
}
} else if (
typeof prevValue === 'string' &&
prevValue.startsWith('=') &&
prevValue.length > 1
) {
updatedValue = `${prevValue} ${data}`;
} else {
updatedValue = `=${data}`;

View file

@ -79,6 +79,7 @@ import { externalHooks } from '@/mixins/externalHooks';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import MappingPill from './MappingPill.vue';
import { getMappedExpression } from '@/utils/mappingUtils';
const runDataJsonActions = () => import('@/components/RunDataJsonActions.vue');
@ -169,11 +170,13 @@ export default mixins(externalHooks).extend({
return shorten(el.dataset.name || '', 16, 2);
},
getJsonParameterPath(path: string): string {
const convertedPath = convertPath(path);
return `{{ ${convertedPath.replace(
/^(\["?\d"?])/,
this.distanceFromActive === 1 ? '$json' : `$node["${this.node!.name}"].json`,
)} }}`;
const subPath = path.replace(/^(\["?\d"?])/, ''); // remove item position
return getMappedExpression({
nodeName: this.node.name,
distanceFromActive: this.distanceFromActive,
path: subPath,
});
},
onDragStart(el: HTMLElement) {
if (el && el.dataset.path) {

View file

@ -60,4 +60,23 @@ describe('RunDataJsonSchema.vue', () => {
});
expect(container).toMatchSnapshot();
});
it('renders schema with spaces and dots', () => {
renderOptions.props.data = [
{
'hello world': [
{
test: {
'more to think about': 1,
},
'test.how': 'ignore',
},
],
},
];
const { container } = render(RunDataJsonSchema, renderOptions, (vue) => {
vue.use(PiniaVuePlugin);
});
expect(container).toMatchSnapshot();
});
});

View file

@ -2,6 +2,7 @@
import { computed } from 'vue';
import { INodeUi, Schema } from '@/Interface';
import { checkExhaustive, shorten } from '@/utils';
import { getMappedExpression } from '@/utils/mappingUtils';
type Props = {
schema: Schema;
@ -35,7 +36,12 @@ const text = computed(() =>
);
const getJsonParameterPath = (path: string): string =>
`{{ ${props.distanceFromActive === 1 ? '$json' : `$node["${props.node!.name}"].json`}${path} }}`;
getMappedExpression({
nodeName: props.node!.name,
distanceFromActive: props.distanceFromActive,
path,
});
const transitionDelay = (i: number) => `${i * 0.033}s`;
const getIconBySchemaType = (type: Schema['type']): string => {

View file

@ -170,6 +170,7 @@ import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import MappingPill from './MappingPill.vue';
import { getMappedExpression } from '@/utils/mappingUtils';
const MAX_COLUMNS_LIMIT = 40;
@ -315,11 +316,11 @@ export default mixins(externalHooks).extend({
return '';
}
if (this.distanceFromActive === 1) {
return `{{ $json["${column}"] }}`;
}
return `{{ $node["${this.node.name}"].json["${column}"] }}`;
return getMappedExpression({
nodeName: this.node.name,
distanceFromActive: this.distanceFromActive,
path: [column],
});
},
getPathNameFromTarget(el: HTMLElement) {
if (!el) {
@ -343,21 +344,12 @@ export default mixins(externalHooks).extend({
if (!this.node) {
return '';
}
const expr = path.reduce((accu: string, key: string | number) => {
if (typeof key === 'number') {
return `${accu}[${key}]`;
}
return `${accu}["${key}"]`;
}, '');
const column = this.tableData.columns[colIndex];
if (this.distanceFromActive === 1) {
return `{{ $json["${column}"]${expr} }}`;
}
return `{{ $node["${this.node.name}"].json["${column}"]${expr} }}`;
return getMappedExpression({
nodeName: this.node.name,
distanceFromActive: this.distanceFromActive,
path: [column, ...path],
});
},
isEmpty(value: unknown): boolean {
return (

View file

@ -33,9 +33,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label"
data-depth="1"
data-name="name"
data-path="[\\"name\\"]"
data-path=".name"
data-target="mappable"
data-value="{{ $json[\\"name\\"] }}"
data-value="{{ $json.name }}"
>
<font-awesome-icon-stub
icon="font"
@ -70,9 +70,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label"
data-depth="1"
data-name="age"
data-path="[\\"age\\"]"
data-path=".age"
data-target="mappable"
data-value="{{ $json[\\"age\\"] }}"
data-value="{{ $json.age }}"
>
<font-awesome-icon-stub
icon="hashtag"
@ -107,9 +107,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label"
data-depth="1"
data-name="hobbies"
data-path="[\\"hobbies\\"]"
data-path=".hobbies"
data-target="mappable"
data-value="{{ $json[\\"hobbies\\"] }}"
data-value="{{ $json.hobbies }}"
>
<font-awesome-icon-stub
icon="list"
@ -152,9 +152,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label"
data-depth="2"
data-name="string[0]"
data-path="[\\"hobbies\\"][0]"
data-path=".hobbies[0]"
data-target="mappable"
data-value="{{ $json[\\"hobbies\\"][0] }}"
data-value="{{ $json.hobbies[0] }}"
>
<font-awesome-icon-stub
icon="font"
@ -191,9 +191,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label"
data-depth="2"
data-name="string[1]"
data-path="[\\"hobbies\\"][1]"
data-path=".hobbies[1]"
data-target="mappable"
data-value="{{ $json[\\"hobbies\\"][1] }}"
data-value="{{ $json.hobbies[1] }}"
>
<font-awesome-icon-stub
icon="font"
@ -259,3 +259,503 @@ exports[`RunDataJsonSchema.vue > renders schema for empty data 1`] = `
</div>
</div>
`;
exports[`RunDataJsonSchema.vue > renders schema with spaces 1`] = `
<div>
<div
class="_schemaWrapper_1mtap_1"
>
<div
class=""
>
<div
class="_schema_1mtap_1"
>
<div
class="_item_14xdy_1"
>
<!---->
<!---->
<!---->
<!---->
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="array"
>
<span
class="_label_14xdy_89"
data-depth="1"
data-name="hello world"
data-path="[\\"hello world\\"]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"] }}"
>
<font-awesome-icon-stub
icon="list"
size="sm"
/>
<!---->
<span
class=""
>
hello world
</span>
</span>
</div>
<!---->
<input
checked="checked"
id="array-0-0"
type="checkbox"
/>
<label
class="_toggle_14xdy_20"
for="array-0-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
</label>
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="object"
>
<span
class="_label_14xdy_89"
data-depth="2"
data-name="object[0]"
data-path="[\\"hello world\\"][0]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0] }}"
>
<font-awesome-icon-stub
icon="cube"
size="sm"
/>
<span>
hello world
</span>
<span
class="_arrayIndex_14xdy_94"
>
[0]
</span>
</span>
</div>
<!---->
<input
checked="checked"
id="object-1-0"
type="checkbox"
/>
<label
class="_toggle_14xdy_20"
for="object-1-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
</label>
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="object"
>
<span
class="_label_14xdy_89"
data-depth="3"
data-name="test"
data-path="[\\"hello world\\"][0].test"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0].test }}"
>
<font-awesome-icon-stub
icon="cube"
size="sm"
/>
<!---->
<span
class=""
>
test
</span>
</span>
</div>
<!---->
<input
checked="checked"
id="object-2-0"
type="checkbox"
/>
<label
class="_toggle_14xdy_20"
for="object-2-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
</label>
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="number"
>
<span
class="_label_14xdy_89"
data-depth="4"
data-name="more to think about"
data-path="[\\"hello world\\"][0].test[\\"more to think about\\"]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0].test[\\"more to think about\\"] }}"
>
<font-awesome-icon-stub
icon="hashtag"
size="sm"
/>
<!---->
<span
class=""
>
more to think about
</span>
</span>
</div>
<span
class="_text_14xdy_100"
>
1
</span>
<!---->
<!---->
<!---->
</div>
</div>
</div>
<div
class="_item_14xdy_1"
style="transition-delay: 0.033s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="string"
>
<span
class="_label_14xdy_89"
data-depth="3"
data-name="test.how"
data-path="[\\"hello world\\"][0][\\"test.how\\"]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0][\\"test.how\\"] }}"
>
<font-awesome-icon-stub
icon="font"
size="sm"
/>
<!---->
<span
class=""
>
test.how
</span>
</span>
</div>
<span
class="_text_14xdy_100"
>
ignore
</span>
<!---->
<!---->
<!---->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="teleporter hidden"
data-v-d4e6e290=""
/>
</div>
</div>
</div>
`;
exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
<div>
<div
class="_schemaWrapper_1mtap_1"
>
<div
class=""
>
<div
class="_schema_1mtap_1"
>
<div
class="_item_14xdy_1"
>
<!---->
<!---->
<!---->
<!---->
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="array"
>
<span
class="_label_14xdy_89"
data-depth="1"
data-name="hello world"
data-path="[\\"hello world\\"]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"] }}"
>
<font-awesome-icon-stub
icon="list"
size="sm"
/>
<!---->
<span
class=""
>
hello world
</span>
</span>
</div>
<!---->
<input
checked="checked"
id="array-0-0"
type="checkbox"
/>
<label
class="_toggle_14xdy_20"
for="array-0-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
</label>
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="object"
>
<span
class="_label_14xdy_89"
data-depth="2"
data-name="object[0]"
data-path="[\\"hello world\\"][0]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0] }}"
>
<font-awesome-icon-stub
icon="cube"
size="sm"
/>
<span>
hello world
</span>
<span
class="_arrayIndex_14xdy_94"
>
[0]
</span>
</span>
</div>
<!---->
<input
checked="checked"
id="object-1-0"
type="checkbox"
/>
<label
class="_toggle_14xdy_20"
for="object-1-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
</label>
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="object"
>
<span
class="_label_14xdy_89"
data-depth="3"
data-name="test"
data-path="[\\"hello world\\"][0].test"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0].test }}"
>
<font-awesome-icon-stub
icon="cube"
size="sm"
/>
<!---->
<span
class=""
>
test
</span>
</span>
</div>
<!---->
<input
checked="checked"
id="object-2-0"
type="checkbox"
/>
<label
class="_toggle_14xdy_20"
for="object-2-0"
>
<font-awesome-icon-stub
icon="angle-up"
/>
</label>
<div
class="_sub_14xdy_14"
>
<div
class="_item_14xdy_1"
style="transition-delay: 0s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="number"
>
<span
class="_label_14xdy_89"
data-depth="4"
data-name="more to think about"
data-path="[\\"hello world\\"][0].test[\\"more to think about\\"]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0].test[\\"more to think about\\"] }}"
>
<font-awesome-icon-stub
icon="hashtag"
size="sm"
/>
<!---->
<span
class=""
>
more to think about
</span>
</span>
</div>
<span
class="_text_14xdy_100"
>
1
</span>
<!---->
<!---->
<!---->
</div>
</div>
</div>
<div
class="_item_14xdy_1"
style="transition-delay: 0.033s;"
>
<div
class="_pill_14xdy_51 _mappable_14xdy_70"
title="string"
>
<span
class="_label_14xdy_89"
data-depth="3"
data-name="test.how"
data-path="[\\"hello world\\"][0][\\"test.how\\"]"
data-target="mappable"
data-value="{{ $json[\\"hello world\\"][0][\\"test.how\\"] }}"
>
<font-awesome-icon-stub
icon="font"
size="sm"
/>
<!---->
<span
class=""
>
test.how
</span>
</span>
</div>
<span
class="_text_14xdy_100"
>
ignore
</span>
<!---->
<!---->
<!---->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="teleporter hidden"
data-v-d4e6e290=""
/>
</div>
</div>
</div>
`;

View file

@ -257,10 +257,31 @@ describe('Utils', () => {
type: 'array',
key: 'people',
value: [
{ type: 'string', value: 'Joe', key: '0', path: '["people"][0]' },
{ type: 'string', value: 'John', key: '1', path: '["people"][1]' },
{ type: 'string', value: 'Joe', key: '0', path: '.people[0]' },
{ type: 'string', value: 'John', key: '1', path: '.people[1]' },
],
path: '["people"]',
path: '.people',
},
],
path: '',
},
],
[
{ 'with space': [], 'with.dot': 'test' },
{
type: 'object',
value: [
{
type: 'array',
key: 'with space',
value: [],
path: '["with space"]',
},
{
type: 'string',
key: 'with.dot',
value: 'test',
path: '["with.dot"]',
},
],
path: '',
@ -278,8 +299,8 @@ describe('Utils', () => {
type: 'object',
key: '0',
value: [
{ type: 'string', key: 'name', value: 'John', path: '[0]["name"]' },
{ type: 'number', key: 'age', value: '22', path: '[0]["age"]' },
{ type: 'string', key: 'name', value: 'John', path: '[0].name' },
{ type: 'number', key: 'age', value: '22', path: '[0].age' },
],
path: '[0]',
},
@ -287,8 +308,8 @@ describe('Utils', () => {
type: 'object',
key: '1',
value: [
{ type: 'string', key: 'name', value: 'Joe', path: '[1]["name"]' },
{ type: 'number', key: 'age', value: '33', path: '[1]["age"]' },
{ type: 'string', key: 'name', value: 'Joe', path: '[1].name' },
{ type: 'number', key: 'age', value: '33', path: '[1].age' },
],
path: '[1]',
},
@ -308,16 +329,16 @@ describe('Utils', () => {
type: 'object',
key: '0',
value: [
{ type: 'string', key: 'name', value: 'John', path: '[0]["name"]' },
{ type: 'number', key: 'age', value: '22', path: '[0]["age"]' },
{ type: 'string', key: 'name', value: 'John', path: '[0].name' },
{ type: 'number', key: 'age', value: '22', path: '[0].age' },
{
type: 'array',
key: 'hobbies',
value: [
{ type: 'string', key: '0', value: 'surfing', path: '[0]["hobbies"][0]' },
{ type: 'string', key: '1', value: 'traveling', path: '[0]["hobbies"][1]' },
{ type: 'string', key: '0', value: 'surfing', path: '[0].hobbies[0]' },
{ type: 'string', key: '1', value: 'traveling', path: '[0].hobbies[1]' },
],
path: '[0]["hobbies"]',
path: '[0].hobbies',
},
],
path: '[0]',
@ -326,16 +347,16 @@ describe('Utils', () => {
type: 'object',
key: '1',
value: [
{ type: 'string', key: 'name', value: 'Joe', path: '[1]["name"]' },
{ type: 'number', key: 'age', value: '33', path: '[1]["age"]' },
{ type: 'string', key: 'name', value: 'Joe', path: '[1].name' },
{ type: 'number', key: 'age', value: '33', path: '[1].age' },
{
type: 'array',
key: 'hobbies',
value: [
{ type: 'string', key: '0', value: 'skateboarding', path: '[1]["hobbies"][0]' },
{ type: 'string', key: '1', value: 'gaming', path: '[1]["hobbies"][1]' },
{ type: 'string', key: '0', value: 'skateboarding', path: '[1].hobbies[0]' },
{ type: 'string', key: '1', value: 'gaming', path: '[1].hobbies[1]' },
],
path: '[1]["hobbies"]',
path: '[1].hobbies',
},
],
path: '[1]',
@ -381,8 +402,8 @@ describe('Utils', () => {
type: 'object',
key: '0',
value: [
{ type: 'string', key: 'name', value: 'John', path: '[0][0]["name"]' },
{ type: 'number', key: 'age', value: '22', path: '[0][0]["age"]' },
{ type: 'string', key: 'name', value: 'John', path: '[0][0].name' },
{ type: 'number', key: 'age', value: '22', path: '[0][0].age' },
],
path: '[0][0]',
},
@ -390,8 +411,8 @@ describe('Utils', () => {
type: 'object',
key: '1',
value: [
{ type: 'string', key: 'name', value: 'Joe', path: '[0][1]["name"]' },
{ type: 'number', key: 'age', value: '33', path: '[0][1]["age"]' },
{ type: 'string', key: 'name', value: 'Joe', path: '[0][1].name' },
{ type: 'number', key: 'age', value: '33', path: '[0][1].age' },
],
path: '[0][1]',
},
@ -430,16 +451,16 @@ describe('Utils', () => {
type: 'string',
key: '0',
value: '2022-11-22T00:00:00.000Z',
path: '[0]["dates"][0][0]',
path: '[0].dates[0][0]',
},
{
type: 'string',
key: '1',
value: '2022-11-23T00:00:00.000Z',
path: '[0]["dates"][0][1]',
path: '[0].dates[0][1]',
},
],
path: '[0]["dates"][0]',
path: '[0].dates[0]',
},
{
type: 'array',
@ -449,19 +470,19 @@ describe('Utils', () => {
type: 'string',
key: '0',
value: '2022-12-22T00:00:00.000Z',
path: '[0]["dates"][1][0]',
path: '[0].dates[1][0]',
},
{
type: 'string',
key: '1',
value: '2022-12-23T00:00:00.000Z',
path: '[0]["dates"][1][1]',
path: '[0].dates[1][1]',
},
],
path: '[0]["dates"][1]',
path: '[0].dates[1]',
},
],
path: '[0]["dates"]',
path: '[0].dates',
},
],
path: '[0]',

View file

@ -0,0 +1,31 @@
export function generatePath(root: string, path: Array<string | number>): string {
return path.reduce((accu: string, part: string | number) => {
if (typeof part === 'number') {
return `${accu}[${part}]`;
}
if (part.includes(' ') || part.includes('.')) {
return `${accu}["${part}"]`;
}
return `${accu}.${part}`;
}, root);
}
export function getMappedExpression({
nodeName,
distanceFromActive,
path,
}: {
nodeName: string;
distanceFromActive: number;
path: Array<string | number> | string;
}) {
const root = distanceFromActive === 1 ? '$json' : generatePath('$node', [nodeName, 'json']);
if (typeof path === 'string') {
return `{{ ${root}${path} }}`;
}
return `{{ ${generatePath(root, path)} }}`;
}

View file

@ -2,6 +2,7 @@ import dateformat from 'dateformat';
import { IDataObject, jsonParse } from 'n8n-workflow';
import { Schema, Optional, Primitives } from '@/Interface';
import { isObj } from '@/utils/typeGuards';
import { generatePath } from '@/utils/mappingUtils';
/*
Constants and utility functions than can be used to manipulate different data types and objects
@ -231,7 +232,7 @@ export const getSchema = (input: Optional<Primitives | object>, path = ''): Sche
type: 'object',
value: Object.entries(input).map(([k, v]) => ({
key: k,
...getSchema(v, path + `["${k}"]`),
...getSchema(v, generatePath(path, [k])),
})),
path,
};

View file

@ -52,6 +52,7 @@ export class CompareDatasets implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
{
displayName: 'Input B Field',
@ -61,6 +62,7 @@ export class CompareDatasets implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
],
},
@ -126,6 +128,7 @@ export class CompareDatasets implements INodeType {
resolve: ['mix'],
},
},
requiresDataPath: 'multiple',
},
{
displayName: 'Options',
@ -143,6 +146,7 @@ export class CompareDatasets implements INodeType {
hint: 'Enter the field names as text, separated by commas',
description:
"Fields that shouldn't be included when checking whether two items are the same",
requiresDataPath: 'multiple',
},
{
displayName: 'Fuzzy Compare',

View file

@ -139,6 +139,7 @@ export class ItemLists implements INodeType {
},
},
description: 'The name of the input field to break out into separate items',
requiresDataPath: 'single',
},
{
displayName: 'Include',
@ -197,6 +198,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
],
},
@ -256,6 +258,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
{
displayName: 'Rename Field',
@ -276,6 +279,7 @@ export class ItemLists implements INodeType {
default: '',
description:
'The name of the field to put the aggregated data in. Leave blank to use the input field name.',
requiresDataPath: 'single',
},
],
},
@ -346,6 +350,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
],
},
@ -382,6 +387,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
],
},
@ -453,6 +459,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
],
},
@ -488,6 +495,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
],
},
@ -544,6 +552,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
{
displayName: 'Order',

View file

@ -123,6 +123,7 @@ export const description: INodeProperties[] = [
aggregation: [...NUMERICAL_AGGREGATIONS, 'countUnique', 'count'],
},
},
requiresDataPath: 'single',
},
{
displayName: 'Field',
@ -138,6 +139,7 @@ export const description: INodeProperties[] = [
aggregation: NUMERICAL_AGGREGATIONS,
},
},
requiresDataPath: 'single',
},
{
displayName: 'Field',
@ -153,6 +155,7 @@ export const description: INodeProperties[] = [
aggregation: ['countUnique', 'count'],
},
},
requiresDataPath: 'single',
},
// ----------------------------------------------------------------------------------------------------------
{
@ -245,6 +248,7 @@ export const description: INodeProperties[] = [
'/options.outputFormat': ['singleItem'],
},
},
requiresDataPath: 'multiple',
},
{
displayName: 'Fields to Group By',
@ -261,6 +265,7 @@ export const description: INodeProperties[] = [
'/options.outputFormat': ['singleItem'],
},
},
requiresDataPath: 'multiple',
},
// ----------------------------------------------------------------------------------------------------------
{

View file

@ -122,6 +122,7 @@ const versionDescription: INodeTypeDescription = {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
{
displayName: 'Input 2 Field',
@ -131,6 +132,7 @@ const versionDescription: INodeTypeDescription = {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
],
},

View file

@ -1039,6 +1039,7 @@ export interface INodeProperties {
>;
extractValue?: INodePropertyValueExtractor;
modes?: INodePropertyMode[];
requiresDataPath?: 'single' | 'multiple';
}
export interface INodePropertyModeTypeOptions {