-
Notifications
You must be signed in to change notification settings - Fork 50.7k
feat(Aggregate Node): add group by functionality to aggregate node #22058
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Introduced the ability to group items based on specified fields. Added validation for conflicts between group-by fields and aggregated/destination fields. Included comprehensive unit tests and a workflow for the new grouping feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 issues found across 3 files
Prompt for AI agents (all 3 issues)
Understand the root cause of the following 3 issues and fix them.
<file name="packages/nodes-base/nodes/Transform/Aggregate/Aggregate.node.ts">
<violation number="1" location="packages/nodes-base/nodes/Transform/Aggregate/Aggregate.node.ts:486">
Destination field conflict detection only checks exact name matches, so grouping by nested fields (e.g. `user.country`) and outputting to the parent key (`user`) overwrites the group key in the result. Use a prefix-aware comparison to block parent/child path conflicts.</violation>
<violation number="2" location="packages/nodes-base/nodes/Transform/Aggregate/Aggregate.node.ts:570">
Aggregated field conflict detection also checks only exact names, so nested paths like `user` vs `user.country` overwrite each other and drop the recorded group value. Block parent/child overlaps between group-by fields and aggregated output fields before writing them.</violation>
<violation number="3" location="packages/nodes-base/nodes/Transform/Aggregate/Aggregate.node.ts:645">
The implementation for 'pairedItem' in grouped aggregation only preserves the data lineage for the first item in each group, discarding the link to all other items. This contradicts the established pattern of using a `pairedItem` array for aggregated items and breaks the expectation of full data traceability, which is a key architectural feature.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
packages/nodes-base/nodes/Transform/Aggregate/Aggregate.node.ts
Outdated
Show resolved
Hide resolved
packages/nodes-base/nodes/Transform/Aggregate/Aggregate.node.ts
Outdated
Show resolved
Hide resolved
Adds a new function `hasNestedPathConflict` to accurately detect conflicts between destination/output fields and group by fields when nested paths are involved. This enhances the robustness of the Aggregate node's validation, preventing ambiguous or incorrect error messages and ensuring proper handling of complex field structures. Includes comprehensive unit tests to cover various conflict scenarios, including parent-child relationships and non-nested shared prefixes.
|
Reiterating, I'd like to clarify the intended behaviour when aggregating data. Currently, mapping all relevant source items to the The Conflict:
Is there a functional advantage to mapping all items right now if it renders the standard Specific Context/Use Case:
|
|
Hey @pemontto, Thank you for your contribution. We appreciate the time and effort you’ve taken to submit this pull request. Before we can proceed, please ensure the following: Regarding new nodes: If your node integrates with an AI service that you own or represent, please email [email protected] and we will be happy to discuss the best approach. About review timelines: Thank you again for contributing to n8n. |
Summary
Group By and Aggregate go together like peas and carrots 🥕- Forrest Gump (probably)This PR adds Group By support to the Aggregate node, enabling users to group items by field values before aggregating. This is a fundamental data manipulation pattern that brings n8n closer to standards like SQL GROUP BY, Pandas groupby(), or Excel Pivot Tables.
Features
✅ Group by fields: Split data into groups based on one or multiple fields (comma-separated).
✅ Flexible Modes: Supports both "Individual Fields" and "All Item Data" aggregation modes within the groups.
✅ Binary Support: Full support for including/grouping binary data.
Why Add This?
Group By is essential for structured data aggregation. It allows users to move from a global aggregation to specific buckets.
Before (One aggregation across all items):
After (Group By 'domain'):
Outstanding Questions
Item Linking (pairedItem): Initially, I mapped all constituent input items to the pairedItem array for the resulting group (which seemed semantically correct). However, I didn't observe any functional use in the UI when compared to just mapping the first item. I found just using the first item at least gives me a way to trace back to an originating item, i.e. allowing
$('previous node').itemto work.Mapping all related items as an array would return the same as if no mapping took place:

Sample Workflow
Click to expand workflow JSON
{ "nodes": [ { "parameters": {}, "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ 0, -80 ], "id": "28bdc599-680f-4306-b43f-a972c5ac5ab4", "name": "When clicking ‘Execute workflow’" }, { "parameters": { "aggregate": "aggregateAllItemData", "options": { "groupByFields": "domain", "includeBinaries": true } }, "type": "n8n-nodes-base.aggregate", "typeVersion": 1, "position": [ 880, -176 ], "id": "29086978-669e-49de-a153-8ab5cf423f3a", "name": "Aggregate All" }, { "parameters": { "fieldsToAggregate": { "fieldToAggregate": [ { "fieldToAggregate": "email" }, { "fieldToAggregate": "confirmed", "renameField": true, "outputFieldName": "T/F" } ] }, "options": { "groupByFields": "domain" } }, "type": "n8n-nodes-base.aggregate", "typeVersion": 1, "position": [ 448, 16 ], "id": "68650cbd-3885-4e68-9398-57ac13b75d55", "name": "Aggregate Individual" }, { "parameters": { "operation": "toText", "sourceProperty": "email", "options": {} }, "type": "n8n-nodes-base.convertToFile", "typeVersion": 1.1, "position": [ 448, -176 ], "id": "4facc738-c1ef-4923-a44b-24e687a98adf", "name": "Convert to File" }, { "parameters": { "mode": "raw", "jsonOutput": "={{ \n$('Emails').item.json\n}}", "includeOtherFields": true, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 656, -176 ], "id": "c2ff68b1-7552-444a-93b5-dbe6b5f903a3", "name": "Add Fields" }, { "parameters": { "jsCode": "return [\n {\n json: {\n email: \"[email protected]\",\n confirmed: true,\n domain: 'hotmail.com'\n }\n },\n {\n json: {\n email: \"[email protected]\",\n confirmed: false,\n domain: 'yahoo.com'\n }\n },\n {\n json: {\n email: \"[email protected]\",\n confirmed: false,\n domain: 'yahoo.com'\n }\n },\n {\n json: {\n email: \"[email protected]\",\n confirmed: false,\n domain: 'gmail.com'\n }\n },\n {\n json: {\n email: \"[email protected]\",\n confirmed: true,\n domain: 'yahoo.com'\n }\n },\n]" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 224, -80 ], "id": "c74ca043-1115-4d60-9834-f08cab7dfd04", "name": "Emails" }, { "parameters": { "assignments": { "assignments": [ { "id": "f5422421-b865-4488-b261-33a95f51b724", "name": "orig_data", "value": "={{ $('Emails').item.json }}", "type": "string" } ] }, "includeOtherFields": true, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 1088, -176 ], "id": "12963bce-e1c4-42f3-b86b-ca3e3af8ce7f", "name": "PairedItem" } ], "connections": { "When clicking ‘Execute workflow’": { "main": [ [ { "node": "Emails", "type": "main", "index": 0 } ] ] }, "Aggregate All": { "main": [ [ { "node": "PairedItem", "type": "main", "index": 0 } ] ] }, "Convert to File": { "main": [ [ { "node": "Add Fields", "type": "main", "index": 0 } ] ] }, "Add Fields": { "main": [ [ { "node": "Aggregate All", "type": "main", "index": 0 } ] ] }, "Emails": { "main": [ [ { "node": "Aggregate Individual", "type": "main", "index": 0 }, { "node": "Convert to File", "type": "main", "index": 0 } ] ] } }, "pinData": {} }Review / Merge checklist
-->
release/backport(if the PR is an urgent fix that needs to be backported)Note
Adds configurable group-by to
Aggregatewith nested-field conflict checks, per-group aggregation for both modes, and binary handling, plus comprehensive tests and example workflow.packages/nodes-base/nodes/Transform/Aggregate/Aggregate.node.ts):options.groupByFieldsenabling grouping by one or more fields (dot-notation supported) with per-group outputs.aggregateIndividualFieldsandaggregateAllItemData, re-aggregating within each group.hasNestedPathConflictbetween group-by fields and output/destination fields.Add Option.packages/nodes-base/nodes/Transform/Aggregate/test/Aggregate.groupby.test.ts):packages/nodes-base/nodes/Transform/Aggregate/test/workflow.groupby.json):domainfor both aggregation modes and field include/exclude variants.Written by Cursor Bugbot for commit a93b338. This will update automatically on new commits. Configure here.