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
14 changed files with 679 additions and 66 deletions

View File

@@ -229,12 +229,31 @@ export default mixins(showMessage).extend({
} }
}, },
onDrop(data: string) { onDrop(data: string) {
const useDataPath = !!this.parameter.requiresDataPath && data.startsWith('{{ $json');
if (!useDataPath) {
this.forceShowExpression = true; this.forceShowExpression = true;
}
setTimeout(() => { setTimeout(() => {
if (this.node) { if (this.node) {
const prevValue = this.isResourceLocator ? this.value.value : this.value; const prevValue = this.isResourceLocator ? this.value.value : this.value;
let updatedValue: string; 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}`; updatedValue = `${prevValue} ${data}`;
} else { } else {
updatedValue = `=${data}`; updatedValue = `=${data}`;

View File

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

View File

@@ -60,4 +60,23 @@ describe('RunDataJsonSchema.vue', () => {
}); });
expect(container).toMatchSnapshot(); 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 { computed } from 'vue';
import { INodeUi, Schema } from '@/Interface'; import { INodeUi, Schema } from '@/Interface';
import { checkExhaustive, shorten } from '@/utils'; import { checkExhaustive, shorten } from '@/utils';
import { getMappedExpression } from '@/utils/mappingUtils';
type Props = { type Props = {
schema: Schema; schema: Schema;
@@ -35,7 +36,12 @@ const text = computed(() =>
); );
const getJsonParameterPath = (path: string): string => 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 transitionDelay = (i: number) => `${i * 0.033}s`;
const getIconBySchemaType = (type: Schema['type']): string => { const getIconBySchemaType = (type: Schema['type']): string => {

View File

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

View File

@@ -33,9 +33,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label" class="label"
data-depth="1" data-depth="1"
data-name="name" data-name="name"
data-path="[\\"name\\"]" data-path=".name"
data-target="mappable" data-target="mappable"
data-value="{{ $json[\\"name\\"] }}" data-value="{{ $json.name }}"
> >
<font-awesome-icon-stub <font-awesome-icon-stub
icon="font" icon="font"
@@ -70,9 +70,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label" class="label"
data-depth="1" data-depth="1"
data-name="age" data-name="age"
data-path="[\\"age\\"]" data-path=".age"
data-target="mappable" data-target="mappable"
data-value="{{ $json[\\"age\\"] }}" data-value="{{ $json.age }}"
> >
<font-awesome-icon-stub <font-awesome-icon-stub
icon="hashtag" icon="hashtag"
@@ -107,9 +107,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label" class="label"
data-depth="1" data-depth="1"
data-name="hobbies" data-name="hobbies"
data-path="[\\"hobbies\\"]" data-path=".hobbies"
data-target="mappable" data-target="mappable"
data-value="{{ $json[\\"hobbies\\"] }}" data-value="{{ $json.hobbies }}"
> >
<font-awesome-icon-stub <font-awesome-icon-stub
icon="list" icon="list"
@@ -152,9 +152,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label" class="label"
data-depth="2" data-depth="2"
data-name="string[0]" data-name="string[0]"
data-path="[\\"hobbies\\"][0]" data-path=".hobbies[0]"
data-target="mappable" data-target="mappable"
data-value="{{ $json[\\"hobbies\\"][0] }}" data-value="{{ $json.hobbies[0] }}"
> >
<font-awesome-icon-stub <font-awesome-icon-stub
icon="font" icon="font"
@@ -191,9 +191,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = `
class="label" class="label"
data-depth="2" data-depth="2"
data-name="string[1]" data-name="string[1]"
data-path="[\\"hobbies\\"][1]" data-path=".hobbies[1]"
data-target="mappable" data-target="mappable"
data-value="{{ $json[\\"hobbies\\"][1] }}" data-value="{{ $json.hobbies[1] }}"
> >
<font-awesome-icon-stub <font-awesome-icon-stub
icon="font" icon="font"
@@ -259,3 +259,503 @@ exports[`RunDataJsonSchema.vue > renders schema for empty data 1`] = `
</div> </div>
</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', type: 'array',
key: 'people', key: 'people',
value: [ value: [
{ type: 'string', value: 'Joe', key: '0', path: '["people"][0]' }, { type: 'string', value: 'Joe', key: '0', path: '.people[0]' },
{ type: 'string', value: 'John', key: '1', path: '["people"][1]' }, { 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: '', path: '',
@@ -278,8 +299,8 @@ describe('Utils', () => {
type: 'object', type: 'object',
key: '0', key: '0',
value: [ value: [
{ type: 'string', key: 'name', value: 'John', path: '[0]["name"]' }, { type: 'string', key: 'name', value: 'John', path: '[0].name' },
{ type: 'number', key: 'age', value: '22', path: '[0]["age"]' }, { type: 'number', key: 'age', value: '22', path: '[0].age' },
], ],
path: '[0]', path: '[0]',
}, },
@@ -287,8 +308,8 @@ describe('Utils', () => {
type: 'object', type: 'object',
key: '1', key: '1',
value: [ value: [
{ type: 'string', key: 'name', value: 'Joe', path: '[1]["name"]' }, { type: 'string', key: 'name', value: 'Joe', path: '[1].name' },
{ type: 'number', key: 'age', value: '33', path: '[1]["age"]' }, { type: 'number', key: 'age', value: '33', path: '[1].age' },
], ],
path: '[1]', path: '[1]',
}, },
@@ -308,16 +329,16 @@ describe('Utils', () => {
type: 'object', type: 'object',
key: '0', key: '0',
value: [ value: [
{ type: 'string', key: 'name', value: 'John', path: '[0]["name"]' }, { type: 'string', key: 'name', value: 'John', path: '[0].name' },
{ type: 'number', key: 'age', value: '22', path: '[0]["age"]' }, { type: 'number', key: 'age', value: '22', path: '[0].age' },
{ {
type: 'array', type: 'array',
key: 'hobbies', key: 'hobbies',
value: [ value: [
{ type: 'string', key: '0', value: 'surfing', path: '[0]["hobbies"][0]' }, { type: 'string', key: '0', value: 'surfing', path: '[0].hobbies[0]' },
{ type: 'string', key: '1', value: 'traveling', path: '[0]["hobbies"][1]' }, { type: 'string', key: '1', value: 'traveling', path: '[0].hobbies[1]' },
], ],
path: '[0]["hobbies"]', path: '[0].hobbies',
}, },
], ],
path: '[0]', path: '[0]',
@@ -326,16 +347,16 @@ describe('Utils', () => {
type: 'object', type: 'object',
key: '1', key: '1',
value: [ value: [
{ type: 'string', key: 'name', value: 'Joe', path: '[1]["name"]' }, { type: 'string', key: 'name', value: 'Joe', path: '[1].name' },
{ type: 'number', key: 'age', value: '33', path: '[1]["age"]' }, { type: 'number', key: 'age', value: '33', path: '[1].age' },
{ {
type: 'array', type: 'array',
key: 'hobbies', key: 'hobbies',
value: [ value: [
{ type: 'string', key: '0', value: 'skateboarding', path: '[1]["hobbies"][0]' }, { type: 'string', key: '0', value: 'skateboarding', path: '[1].hobbies[0]' },
{ type: 'string', key: '1', value: 'gaming', path: '[1]["hobbies"][1]' }, { type: 'string', key: '1', value: 'gaming', path: '[1].hobbies[1]' },
], ],
path: '[1]["hobbies"]', path: '[1].hobbies',
}, },
], ],
path: '[1]', path: '[1]',
@@ -381,8 +402,8 @@ describe('Utils', () => {
type: 'object', type: 'object',
key: '0', key: '0',
value: [ value: [
{ type: 'string', key: 'name', value: 'John', path: '[0][0]["name"]' }, { type: 'string', key: 'name', value: 'John', path: '[0][0].name' },
{ type: 'number', key: 'age', value: '22', path: '[0][0]["age"]' }, { type: 'number', key: 'age', value: '22', path: '[0][0].age' },
], ],
path: '[0][0]', path: '[0][0]',
}, },
@@ -390,8 +411,8 @@ describe('Utils', () => {
type: 'object', type: 'object',
key: '1', key: '1',
value: [ value: [
{ type: 'string', key: 'name', value: 'Joe', path: '[0][1]["name"]' }, { type: 'string', key: 'name', value: 'Joe', path: '[0][1].name' },
{ type: 'number', key: 'age', value: '33', path: '[0][1]["age"]' }, { type: 'number', key: 'age', value: '33', path: '[0][1].age' },
], ],
path: '[0][1]', path: '[0][1]',
}, },
@@ -430,16 +451,16 @@ describe('Utils', () => {
type: 'string', type: 'string',
key: '0', key: '0',
value: '2022-11-22T00:00:00.000Z', value: '2022-11-22T00:00:00.000Z',
path: '[0]["dates"][0][0]', path: '[0].dates[0][0]',
}, },
{ {
type: 'string', type: 'string',
key: '1', key: '1',
value: '2022-11-23T00:00:00.000Z', 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', type: 'array',
@@ -449,19 +470,19 @@ describe('Utils', () => {
type: 'string', type: 'string',
key: '0', key: '0',
value: '2022-12-22T00:00:00.000Z', value: '2022-12-22T00:00:00.000Z',
path: '[0]["dates"][1][0]', path: '[0].dates[1][0]',
}, },
{ {
type: 'string', type: 'string',
key: '1', key: '1',
value: '2022-12-23T00:00:00.000Z', 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]', 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 { IDataObject, jsonParse } from 'n8n-workflow';
import { Schema, Optional, Primitives } from '@/Interface'; import { Schema, Optional, Primitives } from '@/Interface';
import { isObj } from '@/utils/typeGuards'; 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 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', type: 'object',
value: Object.entries(input).map(([k, v]) => ({ value: Object.entries(input).map(([k, v]) => ({
key: k, key: k,
...getSchema(v, path + `["${k}"]`), ...getSchema(v, generatePath(path, [k])),
})), })),
path, path,
}; };

View File

@@ -52,6 +52,7 @@ export class CompareDatasets implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', hint: ' Enter the field name as text',
requiresDataPath: 'single',
}, },
{ {
displayName: 'Input B Field', 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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', hint: ' Enter the field name as text',
requiresDataPath: 'single',
}, },
], ],
}, },
@@ -126,6 +128,7 @@ export class CompareDatasets implements INodeType {
resolve: ['mix'], resolve: ['mix'],
}, },
}, },
requiresDataPath: 'multiple',
}, },
{ {
displayName: 'Options', displayName: 'Options',
@@ -143,6 +146,7 @@ export class CompareDatasets implements INodeType {
hint: 'Enter the field names as text, separated by commas', hint: 'Enter the field names as text, separated by commas',
description: description:
"Fields that shouldn't be included when checking whether two items are the same", "Fields that shouldn't be included when checking whether two items are the same",
requiresDataPath: 'multiple',
}, },
{ {
displayName: 'Fuzzy Compare', 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', description: 'The name of the input field to break out into separate items',
requiresDataPath: 'single',
}, },
{ {
displayName: 'Include', displayName: 'Include',
@@ -197,6 +198,7 @@ export class ItemLists implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', 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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', hint: ' Enter the field name as text',
requiresDataPath: 'single',
}, },
{ {
displayName: 'Rename Field', displayName: 'Rename Field',
@@ -276,6 +279,7 @@ export class ItemLists implements INodeType {
default: '', default: '',
description: description:
'The name of the field to put the aggregated data in. Leave blank to use the input field name.', '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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', 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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', 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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', 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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', 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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', hint: ' Enter the field name as text',
requiresDataPath: 'single',
}, },
{ {
displayName: 'Order', displayName: 'Order',

View File

@@ -123,6 +123,7 @@ export const description: INodeProperties[] = [
aggregation: [...NUMERICAL_AGGREGATIONS, 'countUnique', 'count'], aggregation: [...NUMERICAL_AGGREGATIONS, 'countUnique', 'count'],
}, },
}, },
requiresDataPath: 'single',
}, },
{ {
displayName: 'Field', displayName: 'Field',
@@ -138,6 +139,7 @@ export const description: INodeProperties[] = [
aggregation: NUMERICAL_AGGREGATIONS, aggregation: NUMERICAL_AGGREGATIONS,
}, },
}, },
requiresDataPath: 'single',
}, },
{ {
displayName: 'Field', displayName: 'Field',
@@ -153,6 +155,7 @@ export const description: INodeProperties[] = [
aggregation: ['countUnique', 'count'], aggregation: ['countUnique', 'count'],
}, },
}, },
requiresDataPath: 'single',
}, },
// ---------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------
{ {
@@ -245,6 +248,7 @@ export const description: INodeProperties[] = [
'/options.outputFormat': ['singleItem'], '/options.outputFormat': ['singleItem'],
}, },
}, },
requiresDataPath: 'multiple',
}, },
{ {
displayName: 'Fields to Group By', displayName: 'Fields to Group By',
@@ -261,6 +265,7 @@ export const description: INodeProperties[] = [
'/options.outputFormat': ['singleItem'], '/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 // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', hint: ' Enter the field name as text',
requiresDataPath: 'single',
}, },
{ {
displayName: 'Input 2 Field', displayName: 'Input 2 Field',
@@ -131,6 +132,7 @@ const versionDescription: INodeTypeDescription = {
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id', placeholder: 'e.g. id',
hint: ' Enter the field name as text', hint: ' Enter the field name as text',
requiresDataPath: 'single',
}, },
], ],
}, },

View File

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