mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(Summarize Node): Fix type casting of strings and numbers (#14259)
This commit is contained in:
@@ -1,23 +1,22 @@
|
|||||||
import {
|
import {
|
||||||
NodeOperationError,
|
|
||||||
NodeConnectionTypes,
|
|
||||||
type IExecuteFunctions,
|
type IExecuteFunctions,
|
||||||
type INodeExecutionData,
|
type INodeExecutionData,
|
||||||
type INodeType,
|
type INodeType,
|
||||||
type INodeTypeDescription,
|
type INodeTypeDescription,
|
||||||
|
NodeConnectionTypes,
|
||||||
type NodeExecutionHint,
|
type NodeExecutionHint,
|
||||||
type IDataObject,
|
NodeOperationError,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type Aggregations,
|
type Aggregations,
|
||||||
NUMERICAL_AGGREGATIONS,
|
NUMERICAL_AGGREGATIONS,
|
||||||
type SummarizeOptions,
|
type SummarizeOptions,
|
||||||
aggregationToArray,
|
aggregateAndSplitData,
|
||||||
aggregationToArrayWithOriginalTypes,
|
|
||||||
checkIfFieldExists,
|
checkIfFieldExists,
|
||||||
fieldValueGetter,
|
fieldValueGetter,
|
||||||
splitData,
|
flattenAggregationResultToArray,
|
||||||
|
flattenAggregationResultToObject,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
export class Summarize implements INodeType {
|
export class Summarize implements INodeType {
|
||||||
@@ -321,13 +320,14 @@ export class Summarize implements INodeType {
|
|||||||
|
|
||||||
const nodeVersion = this.getNode().typeVersion;
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
|
|
||||||
const aggregationResult = splitData(
|
const aggregationResult = aggregateAndSplitData({
|
||||||
fieldsToSplitBy,
|
splitKeys: fieldsToSplitBy,
|
||||||
newItems,
|
inputItems: newItems,
|
||||||
fieldsToSummarize,
|
fieldsToSummarize,
|
||||||
options,
|
options,
|
||||||
getValue,
|
getValue,
|
||||||
);
|
convertKeysToString: nodeVersion === 1,
|
||||||
|
});
|
||||||
|
|
||||||
const fieldsNotFound: NodeExecutionHint[] = [];
|
const fieldsNotFound: NodeExecutionHint[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -350,36 +350,27 @@ export class Summarize implements INodeType {
|
|||||||
|
|
||||||
if (options.outputFormat === 'singleItem') {
|
if (options.outputFormat === 'singleItem') {
|
||||||
const executionData: INodeExecutionData = {
|
const executionData: INodeExecutionData = {
|
||||||
json: aggregationResult,
|
json: flattenAggregationResultToObject(aggregationResult),
|
||||||
pairedItem: newItems.map((_v, index) => ({
|
pairedItem: newItems.map((_v, index) => ({
|
||||||
item: index,
|
item: index,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
return [[executionData]];
|
return [[executionData]];
|
||||||
} else {
|
} else {
|
||||||
if (!fieldsToSplitBy.length) {
|
if (!fieldsToSplitBy.length && 'pairedItems' in aggregationResult) {
|
||||||
const { pairedItems, ...json } = aggregationResult;
|
const { pairedItems, returnData } = aggregationResult;
|
||||||
const executionData: INodeExecutionData = {
|
const executionData: INodeExecutionData = {
|
||||||
json,
|
json: returnData,
|
||||||
pairedItem: ((pairedItems as number[]) || []).map((index: number) => ({
|
pairedItem: (pairedItems ?? []).map((index) => ({ item: index })),
|
||||||
item: index,
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
return [[executionData]];
|
return [[executionData]];
|
||||||
}
|
}
|
||||||
let returnData: IDataObject[] = [];
|
const flatAggregationResults = flattenAggregationResultToArray(aggregationResult);
|
||||||
if (nodeVersion > 1) {
|
const executionData = flatAggregationResults.map((item) => {
|
||||||
returnData = aggregationToArrayWithOriginalTypes(aggregationResult, fieldsToSplitBy);
|
const { pairedItems, returnData } = item;
|
||||||
} else {
|
|
||||||
returnData = aggregationToArray(aggregationResult, fieldsToSplitBy);
|
|
||||||
}
|
|
||||||
const executionData = returnData.map((item) => {
|
|
||||||
const { pairedItems, ...json } = item;
|
|
||||||
return {
|
return {
|
||||||
json,
|
json: returnData,
|
||||||
pairedItem: ((pairedItems as number[]) || []).map((index: number) => ({
|
pairedItem: (pairedItems ?? []).map((index) => ({ item: index })),
|
||||||
item: index,
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return [executionData];
|
return [executionData];
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Test Summarize Node, aggregateAndSplitData should not convert numbers to strings: array 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Qty": 1,
|
||||||
|
"Sku": 12345,
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0G",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Qty": 2,
|
||||||
|
"Sku": 12345,
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Qty": 1,
|
||||||
|
"Sku": 6534563534,
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Test Summarize Node, aggregateAndSplitData should not convert numbers to strings: result 1`] = `
|
||||||
|
{
|
||||||
|
"fieldName": "Sku",
|
||||||
|
"splits": Map {
|
||||||
|
12345 => {
|
||||||
|
"fieldName": "Qty",
|
||||||
|
"splits": Map {
|
||||||
|
1 => {
|
||||||
|
"pairedItems": [
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0G",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
"pairedItems": [
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
6534563534 => {
|
||||||
|
"fieldName": "Qty",
|
||||||
|
"splits": Map {
|
||||||
|
1 => {
|
||||||
|
"pairedItems": [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Test Summarize Node, aggregateAndSplitData should not convert strings to numbers: array 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Qty": "1",
|
||||||
|
"Sku": "012345",
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0G",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Qty": "2",
|
||||||
|
"Sku": "012345",
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Qty": "1",
|
||||||
|
"Sku": "06534563534",
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Test Summarize Node, aggregateAndSplitData should not convert strings to numbers: result 1`] = `
|
||||||
|
{
|
||||||
|
"fieldName": "Sku",
|
||||||
|
"splits": Map {
|
||||||
|
"012345" => {
|
||||||
|
"fieldName": "Qty",
|
||||||
|
"splits": Map {
|
||||||
|
"1" => {
|
||||||
|
"pairedItems": [
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0G",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"2" => {
|
||||||
|
"pairedItems": [
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"06534563534" => {
|
||||||
|
"fieldName": "Qty",
|
||||||
|
"splits": Map {
|
||||||
|
"1" => {
|
||||||
|
"pairedItems": [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"appended_Warehouse": [
|
||||||
|
"BER_0L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Test Summarize Node, aggregateAndSplitData with skipEmptySplitFields=true should skip empty split fields: array 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Sku": 12345,
|
||||||
|
"concatenated_Warehouse": "BER_0G//{"name":"BER_0G3"}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pairedItems": [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"Sku": "{}",
|
||||||
|
"concatenated_Warehouse": "BER_0L",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Test Summarize Node, aggregateAndSplitData with skipEmptySplitFields=true should skip empty split fields: result 1`] = `
|
||||||
|
{
|
||||||
|
"fieldName": "Sku",
|
||||||
|
"splits": Map {
|
||||||
|
12345 => {
|
||||||
|
"pairedItems": [
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"concatenated_Warehouse": "BER_0G//{"name":"BER_0G3"}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"{}" => {
|
||||||
|
"pairedItems": [
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
"returnData": {
|
||||||
|
"concatenated_Warehouse": "BER_0L",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import {
|
||||||
|
fieldValueGetter,
|
||||||
|
aggregateAndSplitData,
|
||||||
|
flattenAggregationResultToArray,
|
||||||
|
type Aggregations,
|
||||||
|
} from '../../utils';
|
||||||
|
|
||||||
|
describe('Test Summarize Node, aggregateAndSplitData', () => {
|
||||||
|
test('should not convert strings to numbers', () => {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
Sku: '012345',
|
||||||
|
Warehouse: 'BER_0G',
|
||||||
|
Qty: '1',
|
||||||
|
_itemIndex: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sku: '012345',
|
||||||
|
Warehouse: 'BER_0L',
|
||||||
|
Qty: '2',
|
||||||
|
_itemIndex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sku: '06534563534',
|
||||||
|
Warehouse: 'BER_0L',
|
||||||
|
Qty: '1',
|
||||||
|
_itemIndex: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const aggregations: Aggregations = [
|
||||||
|
{
|
||||||
|
aggregation: 'append',
|
||||||
|
field: 'Warehouse',
|
||||||
|
includeEmpty: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = aggregateAndSplitData({
|
||||||
|
splitKeys: ['Sku', 'Qty'],
|
||||||
|
inputItems: data,
|
||||||
|
fieldsToSummarize: aggregations,
|
||||||
|
options: { continueIfFieldNotFound: true },
|
||||||
|
getValue: fieldValueGetter(),
|
||||||
|
});
|
||||||
|
expect(result).toMatchSnapshot('result');
|
||||||
|
expect(flattenAggregationResultToArray(result)).toMatchSnapshot('array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not convert numbers to strings', () => {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
Sku: 12345,
|
||||||
|
Warehouse: 'BER_0G',
|
||||||
|
Qty: 1,
|
||||||
|
_itemIndex: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sku: 12345,
|
||||||
|
Warehouse: 'BER_0L',
|
||||||
|
Qty: 2,
|
||||||
|
_itemIndex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sku: 6534563534,
|
||||||
|
Warehouse: 'BER_0L',
|
||||||
|
Qty: 1,
|
||||||
|
_itemIndex: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const aggregations: Aggregations = [
|
||||||
|
{
|
||||||
|
aggregation: 'append',
|
||||||
|
field: 'Warehouse',
|
||||||
|
includeEmpty: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = aggregateAndSplitData({
|
||||||
|
splitKeys: ['Sku', 'Qty'],
|
||||||
|
inputItems: data,
|
||||||
|
fieldsToSummarize: aggregations,
|
||||||
|
options: { continueIfFieldNotFound: true },
|
||||||
|
getValue: fieldValueGetter(),
|
||||||
|
});
|
||||||
|
expect(result).toMatchSnapshot('result');
|
||||||
|
expect(flattenAggregationResultToArray(result)).toMatchSnapshot('array');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with skipEmptySplitFields=true', () => {
|
||||||
|
test('should skip empty split fields', () => {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
Sku: 12345,
|
||||||
|
Warehouse: 'BER_0G',
|
||||||
|
_itemIndex: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Warehouse: 'BER_0L',
|
||||||
|
_itemIndex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sku: {},
|
||||||
|
Warehouse: 'BER_0L',
|
||||||
|
_itemIndex: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sku: 12345,
|
||||||
|
Warehouse: { name: 'BER_0G3' },
|
||||||
|
_itemIndex: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const aggregations: Aggregations = [
|
||||||
|
{
|
||||||
|
aggregation: 'concatenate',
|
||||||
|
field: 'Warehouse',
|
||||||
|
separateBy: 'other',
|
||||||
|
customSeparator: '//',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = aggregateAndSplitData({
|
||||||
|
splitKeys: ['Sku'],
|
||||||
|
inputItems: data,
|
||||||
|
fieldsToSummarize: aggregations,
|
||||||
|
options: { continueIfFieldNotFound: true, skipEmptySplitFields: true },
|
||||||
|
getValue: fieldValueGetter(),
|
||||||
|
});
|
||||||
|
expect(result).toMatchSnapshot('result');
|
||||||
|
expect(flattenAggregationResultToArray(result)).toMatchSnapshot('array');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { isNaN } from 'lodash';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import {
|
import {
|
||||||
type IDataObject,
|
|
||||||
type GenericValue,
|
type GenericValue,
|
||||||
|
type IDataObject,
|
||||||
type IExecuteFunctions,
|
type IExecuteFunctions,
|
||||||
NodeOperationError,
|
NodeOperationError,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
@@ -56,42 +55,13 @@ function isEmpty<T>(value: T) {
|
|||||||
return value === undefined || value === null || value === '';
|
return value === undefined || value === null || value === '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseReturnData(returnData: IDataObject) {
|
function normalizeFieldName(fieldName: string) {
|
||||||
const regexBrackets = /[\]\["]/g;
|
return fieldName.replace(/[\]\["]/g, '').replace(/[ .]/g, '');
|
||||||
const regexSpaces = /[ .]/g;
|
|
||||||
for (const key of Object.keys(returnData)) {
|
|
||||||
if (key.match(regexBrackets)) {
|
|
||||||
const newKey = key.replace(regexBrackets, '');
|
|
||||||
returnData[newKey] = returnData[key];
|
|
||||||
delete returnData[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const key of Object.keys(returnData)) {
|
|
||||||
if (key.match(regexSpaces)) {
|
|
||||||
const newKey = key.replace(regexSpaces, '_');
|
|
||||||
returnData[newKey] = returnData[key];
|
|
||||||
delete returnData[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFieldName(fieldName: string[]) {
|
|
||||||
const regexBrackets = /[\]\["]/g;
|
|
||||||
const regexSpaces = /[ .]/g;
|
|
||||||
fieldName = fieldName.map((field) => {
|
|
||||||
field = field.replace(regexBrackets, '');
|
|
||||||
field = field.replace(regexSpaces, '_');
|
|
||||||
return field;
|
|
||||||
});
|
|
||||||
return fieldName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fieldValueGetter = (disableDotNotation?: boolean) => {
|
export const fieldValueGetter = (disableDotNotation?: boolean) => {
|
||||||
if (disableDotNotation) {
|
return (item: IDataObject, field: string) =>
|
||||||
return (item: IDataObject, field: string) => item[field];
|
disableDotNotation ? item[field] : get(item, field);
|
||||||
} else {
|
|
||||||
return (item: IDataObject, field: string) => get(item, field);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function checkIfFieldExists(
|
export function checkIfFieldExists(
|
||||||
@@ -207,126 +177,111 @@ function aggregateData(
|
|||||||
fieldsToSummarize: Aggregations,
|
fieldsToSummarize: Aggregations,
|
||||||
options: SummarizeOptions,
|
options: SummarizeOptions,
|
||||||
getValue: ValueGetterFn,
|
getValue: ValueGetterFn,
|
||||||
) {
|
): { returnData: IDataObject; pairedItems?: number[] } {
|
||||||
const returnData = fieldsToSummarize.reduce((acc, aggregation) => {
|
const returnData = Object.fromEntries(
|
||||||
acc[`${AggregationDisplayNames[aggregation.aggregation]}${aggregation.field}`] = aggregate(
|
fieldsToSummarize.map((aggregation) => {
|
||||||
data,
|
const key = normalizeFieldName(
|
||||||
aggregation,
|
`${AggregationDisplayNames[aggregation.aggregation]}${aggregation.field}`,
|
||||||
getValue,
|
);
|
||||||
);
|
const result = aggregate(data, aggregation, getValue);
|
||||||
return acc;
|
return [key, result];
|
||||||
}, {} as IDataObject);
|
}),
|
||||||
parseReturnData(returnData);
|
);
|
||||||
|
|
||||||
if (options.outputFormat === 'singleItem') {
|
if (options.outputFormat === 'singleItem') {
|
||||||
parseReturnData(returnData);
|
return { returnData };
|
||||||
return returnData;
|
|
||||||
} else {
|
|
||||||
return { ...returnData, pairedItems: data.map((item) => item._itemIndex as number) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { returnData, pairedItems: data.map((item) => item._itemIndex as number) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function splitData(
|
type AggregationResult = { returnData: IDataObject; pairedItems?: number[] };
|
||||||
splitKeys: string[],
|
type NestedAggregationResult =
|
||||||
data: IDataObject[],
|
| AggregationResult
|
||||||
fieldsToSummarize: Aggregations,
|
| { fieldName: string; splits: Map<unknown, NestedAggregationResult> };
|
||||||
options: SummarizeOptions,
|
|
||||||
getValue: ValueGetterFn,
|
// Using Map to preserve types
|
||||||
) {
|
// With a plain JS object, keys are converted to string
|
||||||
if (!splitKeys || splitKeys.length === 0) {
|
export function aggregateAndSplitData({
|
||||||
return aggregateData(data, fieldsToSummarize, options, getValue);
|
splitKeys,
|
||||||
|
inputItems,
|
||||||
|
fieldsToSummarize,
|
||||||
|
options,
|
||||||
|
getValue,
|
||||||
|
convertKeysToString = false,
|
||||||
|
}: {
|
||||||
|
splitKeys: string[] | undefined;
|
||||||
|
inputItems: IDataObject[];
|
||||||
|
fieldsToSummarize: Aggregations;
|
||||||
|
options: SummarizeOptions;
|
||||||
|
getValue: ValueGetterFn;
|
||||||
|
convertKeysToString?: boolean; // Legacy option for v1
|
||||||
|
}): NestedAggregationResult {
|
||||||
|
if (!splitKeys?.length) {
|
||||||
|
return aggregateData(inputItems, fieldsToSummarize, options, getValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [firstSplitKey, ...restSplitKeys] = splitKeys;
|
const [firstSplitKey, ...restSplitKeys] = splitKeys;
|
||||||
|
|
||||||
const groupedData = data.reduce((acc, item) => {
|
const groupedItems = new Map<unknown, IDataObject[]>();
|
||||||
let keyValue = getValue(item, firstSplitKey) as string;
|
for (const item of inputItems) {
|
||||||
|
let key = getValue(item, firstSplitKey);
|
||||||
|
|
||||||
if (typeof keyValue === 'object') {
|
if (key && typeof key === 'object') {
|
||||||
keyValue = JSON.stringify(keyValue);
|
key = JSON.stringify(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.skipEmptySplitFields && typeof keyValue !== 'number' && !keyValue) {
|
if (convertKeysToString) {
|
||||||
return acc;
|
key = normalizeFieldName(String(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acc[keyValue] === undefined) {
|
if (options.skipEmptySplitFields && typeof key !== 'number' && !key) {
|
||||||
acc[keyValue] = [item];
|
continue;
|
||||||
} else {
|
|
||||||
(acc[keyValue] as IDataObject[]).push(item);
|
|
||||||
}
|
}
|
||||||
return acc;
|
|
||||||
}, {} as IDataObject);
|
|
||||||
|
|
||||||
return Object.keys(groupedData).reduce((acc, key) => {
|
const group = groupedItems.get(key) ?? [];
|
||||||
const value = groupedData[key] as IDataObject[];
|
groupedItems.set(key, group.concat([item]));
|
||||||
acc[key] = splitData(restSplitKeys, value, fieldsToSummarize, options, getValue);
|
|
||||||
return acc;
|
|
||||||
}, {} as IDataObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function aggregationToArray(
|
|
||||||
aggregationResult: IDataObject,
|
|
||||||
fieldsToSplitBy: string[],
|
|
||||||
previousStage: IDataObject = {},
|
|
||||||
) {
|
|
||||||
const returnData: IDataObject[] = [];
|
|
||||||
fieldsToSplitBy = parseFieldName(fieldsToSplitBy);
|
|
||||||
const splitFieldName = fieldsToSplitBy[0];
|
|
||||||
const isNext = fieldsToSplitBy[1];
|
|
||||||
|
|
||||||
if (isNext === undefined) {
|
|
||||||
for (const fieldName of Object.keys(aggregationResult)) {
|
|
||||||
returnData.push({
|
|
||||||
...previousStage,
|
|
||||||
[splitFieldName]: fieldName,
|
|
||||||
...(aggregationResult[fieldName] as IDataObject),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return returnData;
|
|
||||||
} else {
|
|
||||||
for (const key of Object.keys(aggregationResult)) {
|
|
||||||
returnData.push(
|
|
||||||
...aggregationToArray(aggregationResult[key] as IDataObject, fieldsToSplitBy.slice(1), {
|
|
||||||
...previousStage,
|
|
||||||
[splitFieldName]: key,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return returnData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const splits = new Map(
|
||||||
|
Array.from(groupedItems.entries()).map(([groupKey, items]) => [
|
||||||
|
groupKey,
|
||||||
|
aggregateAndSplitData({
|
||||||
|
splitKeys: restSplitKeys,
|
||||||
|
inputItems: items,
|
||||||
|
fieldsToSummarize,
|
||||||
|
options,
|
||||||
|
getValue,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { fieldName: firstSplitKey, splits };
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOriginalFieldValue = (field: string | number) =>
|
export function flattenAggregationResultToObject(result: NestedAggregationResult): IDataObject {
|
||||||
field === 'null' ? null : isNaN(Number(field)) ? field : Number(field);
|
if ('splits' in result) {
|
||||||
|
return Object.fromEntries(
|
||||||
export function aggregationToArrayWithOriginalTypes(
|
Array.from(result.splits.entries()).map(([key, value]) => [
|
||||||
aggregationResult: IDataObject,
|
key,
|
||||||
fieldsToSplitBy: string[],
|
flattenAggregationResultToObject(value),
|
||||||
previousStage: IDataObject = {},
|
]),
|
||||||
) {
|
);
|
||||||
const returnData: IDataObject[] = [];
|
|
||||||
fieldsToSplitBy = parseFieldName(fieldsToSplitBy);
|
|
||||||
const splitFieldName = fieldsToSplitBy[0];
|
|
||||||
const isNext = fieldsToSplitBy[1];
|
|
||||||
|
|
||||||
if (isNext === undefined) {
|
|
||||||
for (const fieldName of Object.keys(aggregationResult)) {
|
|
||||||
returnData.push({
|
|
||||||
...previousStage,
|
|
||||||
[splitFieldName]: getOriginalFieldValue(fieldName),
|
|
||||||
...(aggregationResult[fieldName] as IDataObject),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return returnData;
|
|
||||||
} else {
|
|
||||||
for (const key of Object.keys(aggregationResult)) {
|
|
||||||
returnData.push(
|
|
||||||
...aggregationToArray(aggregationResult[key] as IDataObject, fieldsToSplitBy.slice(1), {
|
|
||||||
...previousStage,
|
|
||||||
[splitFieldName]: getOriginalFieldValue(key),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return returnData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result.returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenAggregationResultToArray(
|
||||||
|
result: NestedAggregationResult,
|
||||||
|
): AggregationResult[] {
|
||||||
|
if ('splits' in result) {
|
||||||
|
return Array.from(result.splits.entries()).flatMap(([value, innerResult]) =>
|
||||||
|
flattenAggregationResultToArray(innerResult).map((v) => {
|
||||||
|
v.returnData[normalizeFieldName(result.fieldName)] = value as IDataObject;
|
||||||
|
return v;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [result];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user