mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
feat(Merge Node)!: node tweaks n8n-4939 (#4321)
BREAKING CHANGE: The Merge node list of operations was rearranged. Merge node: 'Combine' operation was added with 'Combine Mode' option, operations 'Merge By Fields', 'Merge By Position' and 'Multiplex' placed under 'Combine Mode' option. To update -go to the workflows that use the Merge node, select 'Combine' operation and then choose an option from 'Combination Mode' that matches an operation that was previously used. If you want to continue even on error, you can set "Continue on Fail" to true.
This commit is contained in:
parent
1db4fa2bf8
commit
6a37071350
|
@ -2,6 +2,20 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.198.0
|
||||
|
||||
### What changed?
|
||||
|
||||
The Merge node list of operations was rearranged.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you are using the overhauled Merge node and 'Merge By Fields', 'Merge By Position' or 'Multiplex' operation.
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Go to the workflows that use the Merge node, select 'Combine' operation and then choose an option from 'Combination Mode' that matches an operation that was previously used. If you want to continue even on error, you can set "Continue on Fail" to true.
|
||||
|
||||
## 0.171.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -35,6 +35,7 @@ type MultipleMatches = 'all' | 'first';
|
|||
export type MatchFieldsOutput = 'both' | 'input1' | 'input2';
|
||||
|
||||
export type MatchFieldsJoinMode =
|
||||
| 'keepEverything'
|
||||
| 'keepMatches'
|
||||
| 'keepNonMatches'
|
||||
| 'enrichInput2'
|
||||
|
@ -294,12 +295,16 @@ export function selectMergeMethod(clashResolveOptions: ClashResolveOptions) {
|
|||
}
|
||||
}
|
||||
if (mergeMode === 'deepMerge') {
|
||||
return (target: IDataObject, ...source: IDataObject[]) =>
|
||||
mergeWith(target, ...source, customizer);
|
||||
return (target: IDataObject, ...source: IDataObject[]) => {
|
||||
const targetCopy = Object.assign({}, target);
|
||||
return mergeWith(targetCopy, ...source, customizer);
|
||||
};
|
||||
}
|
||||
if (mergeMode === 'shallowMerge') {
|
||||
return (target: IDataObject, ...source: IDataObject[]) =>
|
||||
assignWith(target, ...source, customizer);
|
||||
return (target: IDataObject, ...source: IDataObject[]) => {
|
||||
const targetCopy = Object.assign({}, target);
|
||||
return assignWith(targetCopy, ...source, customizer);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (mergeMode === 'deepMerge') {
|
||||
|
|
|
@ -49,13 +49,31 @@ const versionDescription: INodeTypeDescription = {
|
|||
displayName: 'Mode',
|
||||
name: 'mode',
|
||||
type: 'options',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Append',
|
||||
value: 'append',
|
||||
description: 'All items of input 1, then all items of input 2',
|
||||
},
|
||||
{
|
||||
name: 'Combine',
|
||||
value: 'combine',
|
||||
description: 'Merge matching items together',
|
||||
},
|
||||
{
|
||||
name: 'Choose Branch',
|
||||
value: 'chooseBranch',
|
||||
description: 'Output input data, without modifying it',
|
||||
},
|
||||
],
|
||||
default: 'append',
|
||||
description: 'How data of branches should be merged',
|
||||
},
|
||||
{
|
||||
displayName: 'Combination Mode',
|
||||
name: 'combinationMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Merge By Fields',
|
||||
value: 'mergeByFields',
|
||||
|
@ -71,16 +89,14 @@ const versionDescription: INodeTypeDescription = {
|
|||
value: 'multiplex',
|
||||
description: 'All possible item combinations (cross join)',
|
||||
},
|
||||
{
|
||||
name: 'Choose Branch',
|
||||
value: 'chooseBranch',
|
||||
description: 'Output input data, without modifying it',
|
||||
},
|
||||
],
|
||||
default: 'append',
|
||||
description: 'How data of branches should be merged',
|
||||
default: 'mergeByFields',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: ['combine'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// mergeByFields ------------------------------------------------------------------
|
||||
{
|
||||
displayName: 'Fields to Match',
|
||||
|
@ -119,7 +135,8 @@ const versionDescription: INodeTypeDescription = {
|
|||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: ['mergeByFields'],
|
||||
mode: ['combine'],
|
||||
combinationMode: ['mergeByFields'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -127,6 +144,7 @@ const versionDescription: INodeTypeDescription = {
|
|||
displayName: 'Output Type',
|
||||
name: 'joinMode',
|
||||
type: 'options',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Keep Matches',
|
||||
|
@ -136,7 +154,12 @@ const versionDescription: INodeTypeDescription = {
|
|||
{
|
||||
name: 'Keep Non-Matches',
|
||||
value: 'keepNonMatches',
|
||||
description: "Items that don't match (outer join)",
|
||||
description: "Items that don't match",
|
||||
},
|
||||
{
|
||||
name: 'Keep Everything',
|
||||
value: 'keepEverything',
|
||||
description: "Items that match merged together, plus items that don't match (outer join)",
|
||||
},
|
||||
{
|
||||
name: 'Enrich Input 1',
|
||||
|
@ -152,7 +175,8 @@ const versionDescription: INodeTypeDescription = {
|
|||
default: 'keepMatches',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: ['mergeByFields'],
|
||||
mode: ['combine'],
|
||||
combinationMode: ['mergeByFields'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -177,7 +201,8 @@ const versionDescription: INodeTypeDescription = {
|
|||
default: 'both',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: ['mergeByFields'],
|
||||
mode: ['combine'],
|
||||
combinationMode: ['mergeByFields'],
|
||||
joinMode: ['keepMatches'],
|
||||
},
|
||||
},
|
||||
|
@ -203,7 +228,8 @@ const versionDescription: INodeTypeDescription = {
|
|||
default: 'both',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: ['mergeByFields'],
|
||||
mode: ['combine'],
|
||||
combinationMode: ['mergeByFields'],
|
||||
joinMode: ['keepNonMatches'],
|
||||
},
|
||||
},
|
||||
|
@ -219,11 +245,6 @@ const versionDescription: INodeTypeDescription = {
|
|||
name: 'Wait for Both Inputs to Arrive',
|
||||
value: 'waitForBoth',
|
||||
},
|
||||
// not MVP
|
||||
// {
|
||||
// name: 'Immediately Pass the First Input to Arrive',
|
||||
// value: 'passFirst',
|
||||
// },
|
||||
],
|
||||
default: 'waitForBoth',
|
||||
displayOptions: {
|
||||
|
@ -284,7 +305,10 @@ export class MergeV2 implements INodeType {
|
|||
}
|
||||
}
|
||||
|
||||
if (mode === 'multiplex') {
|
||||
if (mode === 'combine') {
|
||||
const combinationMode = this.getNodeParameter('combinationMode', 0) as string;
|
||||
|
||||
if (combinationMode === 'multiplex') {
|
||||
const clashHandling = this.getNodeParameter(
|
||||
'options.clashHandling.values',
|
||||
0,
|
||||
|
@ -331,13 +355,17 @@ export class MergeV2 implements INodeType {
|
|||
return [returnData];
|
||||
}
|
||||
|
||||
if (mode === 'mergeByPosition') {
|
||||
if (combinationMode === 'mergeByPosition') {
|
||||
const clashHandling = this.getNodeParameter(
|
||||
'options.clashHandling.values',
|
||||
0,
|
||||
{},
|
||||
) as ClashResolveOptions;
|
||||
const includeUnpaired = this.getNodeParameter('options.includeUnpaired', 0, false) as boolean;
|
||||
const includeUnpaired = this.getNodeParameter(
|
||||
'options.includeUnpaired',
|
||||
0,
|
||||
false,
|
||||
) as boolean;
|
||||
|
||||
let input1 = this.getInputData(0);
|
||||
let input2 = this.getInputData(1);
|
||||
|
@ -394,18 +422,25 @@ export class MergeV2 implements INodeType {
|
|||
binary: {
|
||||
...merge({}, entry1.binary, entry2.binary),
|
||||
},
|
||||
pairedItem: [entry1.pairedItem as IPairedItemData, entry2.pairedItem as IPairedItemData],
|
||||
pairedItem: [
|
||||
entry1.pairedItem as IPairedItemData,
|
||||
entry2.pairedItem as IPairedItemData,
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (mode === 'mergeByFields') {
|
||||
if (combinationMode === 'mergeByFields') {
|
||||
const matchFields = checkMatchFieldsInput(
|
||||
this.getNodeParameter('mergeByFields.values', 0, []) as IDataObject[],
|
||||
);
|
||||
|
||||
const joinMode = this.getNodeParameter('joinMode', 0) as MatchFieldsJoinMode;
|
||||
const outputDataFrom = this.getNodeParameter('outputDataFrom', 0, '') as MatchFieldsOutput;
|
||||
const outputDataFrom = this.getNodeParameter(
|
||||
'outputDataFrom',
|
||||
0,
|
||||
'both',
|
||||
) as MatchFieldsOutput;
|
||||
const options = this.getNodeParameter('options', 0, {}) as MatchFieldsOptions;
|
||||
|
||||
options.joinMode = joinMode;
|
||||
|
@ -427,7 +462,11 @@ export class MergeV2 implements INodeType {
|
|||
);
|
||||
|
||||
if (!input2 || !matchFields.length) {
|
||||
if (joinMode === 'keepMatches' || joinMode === 'enrichInput2') {
|
||||
if (
|
||||
joinMode === 'keepMatches' ||
|
||||
joinMode === 'keepEverything' ||
|
||||
joinMode === 'enrichInput2'
|
||||
) {
|
||||
return [returnData];
|
||||
}
|
||||
return [input1];
|
||||
|
@ -435,24 +474,35 @@ export class MergeV2 implements INodeType {
|
|||
|
||||
const matches = findMatches(input1, input2, matchFields, options);
|
||||
|
||||
if (joinMode === 'keepMatches') {
|
||||
if (outputDataFrom === 'input1') {
|
||||
return [matches.matched.map((match) => match.entry)];
|
||||
}
|
||||
if (outputDataFrom === 'input2') {
|
||||
return [matches.matched2];
|
||||
}
|
||||
if (outputDataFrom === 'both') {
|
||||
if (joinMode === 'keepMatches' || joinMode === 'keepEverything') {
|
||||
let output: INodeExecutionData[] = [];
|
||||
const clashResolveOptions = this.getNodeParameter(
|
||||
'options.clashHandling.values',
|
||||
0,
|
||||
{},
|
||||
) as ClashResolveOptions;
|
||||
|
||||
const mergedEntries = mergeMatched(matches.matched, clashResolveOptions);
|
||||
|
||||
returnData.push(...mergedEntries);
|
||||
if (outputDataFrom === 'input1') {
|
||||
output = matches.matched.map((match) => match.entry);
|
||||
}
|
||||
if (outputDataFrom === 'input2') {
|
||||
output = matches.matched2;
|
||||
}
|
||||
if (outputDataFrom === 'both') {
|
||||
output = mergeMatched(matches.matched, clashResolveOptions);
|
||||
}
|
||||
|
||||
if (joinMode === 'keepEverything') {
|
||||
let unmatched1 = matches.unmatched1;
|
||||
let unmatched2 = matches.unmatched2;
|
||||
if (clashResolveOptions.resolveClash === 'addSuffix') {
|
||||
unmatched1 = addSuffixToEntriesKeys(unmatched1, '1');
|
||||
unmatched2 = addSuffixToEntriesKeys(unmatched2, '2');
|
||||
}
|
||||
output = [...output, ...unmatched1, ...unmatched2];
|
||||
}
|
||||
|
||||
returnData.push(...output);
|
||||
}
|
||||
|
||||
if (joinMode === 'keepNonMatches') {
|
||||
|
@ -481,12 +531,16 @@ export class MergeV2 implements INodeType {
|
|||
|
||||
if (clashResolveOptions.resolveClash === 'addSuffix') {
|
||||
const suffix = joinMode === 'enrichInput1' ? '1' : '2';
|
||||
returnData.push(...mergedEntries, ...addSuffixToEntriesKeys(matches.unmatched1, suffix));
|
||||
returnData.push(
|
||||
...mergedEntries,
|
||||
...addSuffixToEntriesKeys(matches.unmatched1, suffix),
|
||||
);
|
||||
} else {
|
||||
returnData.push(...mergedEntries, ...matches.unmatched1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode === 'chooseBranch') {
|
||||
const chooseBranchMode = this.getNodeParameter('chooseBranchMode', 0) as string;
|
||||
|
|
|
@ -87,7 +87,8 @@ export const optionsDescription: INodeProperties[] = [
|
|||
...clashHandlingProperties,
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/mode': ['mergeByFields'],
|
||||
'/mode': ['combine'],
|
||||
'/combinationMode': ['mergeByFields'],
|
||||
},
|
||||
hide: {
|
||||
'/joinMode': ['keepMatches', 'keepNonMatches'],
|
||||
|
@ -98,7 +99,8 @@ export const optionsDescription: INodeProperties[] = [
|
|||
...clashHandlingProperties,
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/mode': ['mergeByFields'],
|
||||
'/mode': ['combine'],
|
||||
'/combinationMode': ['mergeByFields'],
|
||||
'/joinMode': ['keepMatches'],
|
||||
'/outputDataFrom': ['both'],
|
||||
},
|
||||
|
@ -108,7 +110,8 @@ export const optionsDescription: INodeProperties[] = [
|
|||
...clashHandlingProperties,
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/mode': ['multiplex', 'mergeByPosition'],
|
||||
'/mode': ['combine'],
|
||||
'/combinationMode': ['multiplex', 'mergeByPosition'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -121,7 +124,8 @@ export const optionsDescription: INodeProperties[] = [
|
|||
'Whether to disallow referencing child fields using `parent.child` in the field name',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/mode': ['mergeByFields'],
|
||||
'/mode': ['combine'],
|
||||
'/combinationMode': ['mergeByFields'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -135,7 +139,8 @@ export const optionsDescription: INodeProperties[] = [
|
|||
'If there are different numbers of items in input 1 and input 2, whether to include the ones at the end with nothing to pair with',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/mode': ['mergeByPosition'],
|
||||
'/mode': ['combine'],
|
||||
'/combinationMode': ['mergeByPosition'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -158,7 +163,8 @@ export const optionsDescription: INodeProperties[] = [
|
|||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/mode': ['mergeByFields'],
|
||||
'/mode': ['combine'],
|
||||
'/combinationMode': ['mergeByFields'],
|
||||
'/joinMode': ['keepMatches'],
|
||||
'/outputDataFrom': ['both'],
|
||||
},
|
||||
|
@ -183,8 +189,9 @@ export const optionsDescription: INodeProperties[] = [
|
|||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/mode': ['mergeByFields'],
|
||||
'/joinMode': ['enrichInput1', 'enrichInput2'],
|
||||
'/mode': ['combine'],
|
||||
'/combinationMode': ['mergeByFields'],
|
||||
'/joinMode': ['enrichInput1', 'enrichInput2', 'keepEverything'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue