mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
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:
parent
5b9c650e55
commit
6092f6c41e
|
@ -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}`;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
@ -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]',
|
||||
|
|
31
packages/editor-ui/src/utils/mappingUtils.ts
Normal file
31
packages/editor-ui/src/utils/mappingUtils.ts
Normal 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)} }}`;
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
// ----------------------------------------------------------------------------------------------------------
|
||||
{
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1039,6 +1039,7 @@ export interface INodeProperties {
|
|||
>;
|
||||
extractValue?: INodePropertyValueExtractor;
|
||||
modes?: INodePropertyMode[];
|
||||
requiresDataPath?: 'single' | 'multiple';
|
||||
}
|
||||
|
||||
export interface INodePropertyModeTypeOptions {
|
||||
|
|
Loading…
Reference in a new issue