MudDataGrid: Fix nested group expansion for multilevel grouping (#11725)#11729
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #11729 +/- ##
==========================================
- Coverage 91.35% 91.31% -0.04%
==========================================
Files 466 467 +1
Lines 14697 14709 +12
Branches 2853 2867 +14
==========================================
+ Hits 13426 13432 +6
- Misses 632 635 +3
- Partials 639 642 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| /// <param name="columnName">The name of the grouped column.</param> | ||
| /// <param name="key">The group key identifying the specific group to expand or collapse.</param> | ||
| /// <param name="expanded">Whether the group should be expanded (true) or collapsed (false).</param> | ||
| public void ToggleGroupExpand(string columnName, object? key, bool expanded) |
There was a problem hiding this comment.
There is no need to expose this method.
Can you maintain the scope internal?
internal is accessible in test.
There was a problem hiding this comment.
Thank you for the feedback!
As I mentioned in the PR description, just to clarify our use case: in our project, we need to control group expansion programmatically. At the moment, since ToggleGroupExpand is not publicly accessible, we have to rely on reflection to invoke it, which is not ideal or maintainable in the long run. As a MudDataGrid user, I see real value in making this method part of the public API – toggling a specific group node programmatically is a legitimate requirement for many scenarios and should not be considered “internal-only” functionality.
Restricting GroupHierarchyKeysCollection to internal would also cause practical issues for us, as this would effectively block us from being able to use ToggleGroupExpand altogether. If this class is internal, then the GroupDefinition.HierarchyKeys property would also effectively become internal, which would prevent client code from accessing composite keys for advanced scenarios, or passing them to ToggleGroupExpand. This would greatly limit extensibility and force us into more reflection-based workarounds.
Additionally, having GroupHierarchyKeysCollection and ToggleGroupExpand available in the public API would enable advanced scenarios such as determining the precise subgroup within a GroupTemplate, and would provide a better user experience when building the content of a GroupTemplate in DataGridGroupRow, since it can be useful to know exactly which subgroup you are in.
In summary, exposing this type and method makes the library much more flexible for power users. We would greatly appreciate reconsidering making them public.
There was a problem hiding this comment.
I agree, thank for the clarification.
|
|
||
| #nullable enable | ||
|
|
||
| namespace MudBlazor.Components.DataGrid |
|
|
||
| #nullable enable | ||
|
|
||
| namespace MudBlazor.Components.DataGrid |
There was a problem hiding this comment.
The namespace should be MudBlazor, like the class GroupDefinition.
|
|
||
| namespace MudBlazor.Components.DataGrid | ||
| { | ||
| public class GroupHierarchyKeysCollection(IList<object?> list) : ReadOnlyCollection<object?>(list) |
There was a problem hiding this comment.
There is no need to expose this class.
Can make this new class internal?
There was a problem hiding this comment.
Added an answer in the comment section above
| { | ||
| public class GroupHierarchyKeysCollection(IList<object?> list) : ReadOnlyCollection<object?>(list) | ||
| { | ||
| public string HierarchyPath => string.Join('>', this); |
There was a problem hiding this comment.
I don't think this work when the key is of a type that don't override ToString.
A more viable solution would be to store the keys in an array and compare each key one by one.
There was a problem hiding this comment.
I have updated the implementation based on your suggestion. GroupHierarchyKeysCollection now overrides Equals and GetHashCode to compare and hash keys element by element. Also added unit tests for GroupHierarchyKeysCollectionto cover these changes.
1f76f9c to
d643a7d
Compare
|
Since we specifically refer to Row Detail View as hierarchy I feel the naming conventions might need to be adjusted. In addition with a quick overview you did a good job in most spots with public API pieces of adding appropriate XML documentation, but any piece that is to be public needs 100% xml documentation including class. Finally would it not make more sense to make the GroupKey public at this point and instead of passing object? pass GroupKey to the new method? Overall good work. |
|
@versile2, thank you for your review and suggestions! What do you think about renaming |
GroupKeyPath works for me. or GroupingKeysCollection is probably fine. I misread your code previously and thought you were using the GroupKey in the GroupingKeysCollection. You are correct, no reason to expose it and keep it simple. When you've got all your changes pushed to the PR tag me I'll add a full review. |
| return true; | ||
| } | ||
|
|
||
| public override int GetHashCode() |
There was a problem hiding this comment.
Can you consider to use HashCode?
The documentation has a example where the hash is computed from a collection.
| </MudDataGrid> | ||
|
|
||
| @code { | ||
| private MudDataGrid<Model> _dataGrid = null!; |
There was a problem hiding this comment.
removed unused field
|
|
@versile2 Added changes based on your suggestions |
|
@vernou, I've made the suggested changes. Please let me know if there's anything else I can do. |
There was a problem hiding this comment.
Pull Request Overview
This PR fixes a bug in multi-level group expansion for the MudDataGrid component. Previously, expanding/collapsing a nested group would affect all subgroups with the same key across different parent groups. Now, the expansion state is properly tracked using the full group hierarchy path.
- Introduces
GroupKeyPathclass to track the complete path through nested group levels - Refactors the
ToggleGroupExpandAsyncmethod toToggleGroupExpandwith improved parameters and public visibility - Updates group expansion logic to use hierarchical key paths instead of simple keys
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
MudDataGrid.razor.cs |
Core logic changes for hierarchical group tracking and method refactoring |
GroupKeyPath.cs |
New class representing a read-only collection of group keys forming a unique path |
GroupDefinition.cs |
Adds KeyPath property to track group hierarchy |
DataGridGroupRow.razor.cs |
Updates group expansion call to use new method signature |
GroupKeyPathTests.cs |
Unit tests for the new GroupKeyPath class |
DataGridGroupingTests.cs |
Integration test for multilevel group expansion behavior |
DataGridMultilevelGroupingNestedGroupExpansionTest.razor |
Test component for verifying nested group expansion |
| // if it has a key we see if it differs from the definition Expanded State and update accordingly | ||
| // if it doesn't we add it if the new state doesn't match the definition | ||
| var col = RenderedColumns.FirstOrDefault(x => x.GroupBy == groupDef.Selector); | ||
| var col = RenderedColumns.FirstOrDefault(x => x.PropertyName == columnName); |
There was a problem hiding this comment.
The method now searches for columns by PropertyName instead of using the GroupBy selector. This could fail to find the correct column if PropertyName doesn't match the columnName parameter, especially for columns with custom selectors.
| var col = RenderedColumns.FirstOrDefault(x => x.PropertyName == columnName); | |
| // Try to find the column by group selector or property name | |
| var col = RenderedColumns.FirstOrDefault(x => | |
| (x.GroupBy != null && x.GroupBy.ToString() == columnName) || | |
| x.PropertyName == columnName); |
| Title = innerGroup.Title, | ||
| Parent = newGroupDefinition, | ||
| Grouping = innerGroup.Grouping, | ||
| KeyPath = new GroupKeyPath(innerGroup.KeyPath), |
There was a problem hiding this comment.
Creating a new GroupKeyPath from innerGroup.KeyPath assumes KeyPath implements IList<object?>, but it's a ReadOnlyCollection. This will likely cause a runtime error since ReadOnlyCollection doesn't implement IList<object?>.
| KeyPath = new GroupKeyPath(innerGroup.KeyPath), | |
| KeyPath = new GroupKeyPath(innerGroup.KeyPath.ToList()), |
| /// <remarks> | ||
| /// Two <see cref="GroupKeyPath"/> instances are equal if they contain the same elements in the same order. | ||
| /// </remarks> | ||
| public class GroupKeyPath(IList<object?> list) : ReadOnlyCollection<object?>(list) |
There was a problem hiding this comment.
The constructor parameter should validate that the input list is not null to prevent runtime exceptions when creating instances.
| /// <param name="columnName">The name of the grouped column.</param> | ||
| /// <param name="key">The group key identifying the specific group to expand or collapse.</param> | ||
| /// <param name="expanded">Whether the group should be expanded (true) or collapsed (false).</param> | ||
| public void ToggleGroupExpand(string columnName, object? key, bool expanded) |
There was a problem hiding this comment.
I would prefer this method signature :
/// <summary>
/// Expands or collapses the specified group.
/// </summary>
/// <param name="groupKeys">The group keys identifying the specific group to expand or collapse.</param>
/// <param name="expanded">Whether the group should be expanded (true) or collapsed (false).</param>
public void ExpandGroup(GroupKeyPath groupKeys, bool expanded)There was a problem hiding this comment.
Well it's not so much Expand, or Collapse, or Toggle, It should be SetGroupExpansion or something along those lines. bool expanded feeds how you want it, so it's not truly a toggle. It can expand, and it can collapse though.
I tested the PR, it's work fine. Just I dislike the new type name Also this type is just a immutable collection with override |
I don't have an opinion on the naming here, but there is an internal type GroupKey I'm sure could collide if it's too close. It is also removable although it would take more than a few minutes. |
|
I prefer |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request effectively resolves an issue with multi-level group expansion in MudDataGrid by introducing a GroupKeyPath to uniquely identify nested groups. The changes are well-structured, including the addition of a new GroupKeyPath class with corresponding unit tests, and a new integration test to verify the fix. The refactoring of ToggleGroupExpandAsync to ToggleGroupExpand and making it public improves the API's clarity and usability. A significant improvement is the deep cloning of InnerGroup definitions, which prevents state corruption across different group branches.
My main feedback concerns a potential bug where a column's Title is used to look up a column by its PropertyName, which could fail if they are different. I've provided a suggestion to make this lookup more robust.
| _expanded = !_expanded; | ||
| if (Items != null) | ||
| DataGrid.ToggleGroupExpandAsync(GroupDefinition.Title, Items.Key, GroupDefinition, _expanded); | ||
| DataGrid.ToggleGroupExpand(GroupDefinition.Title, GroupDefinition.KeyPath, _expanded); |
There was a problem hiding this comment.
Using GroupDefinition.Title to identify the column in ToggleGroupExpand can be unreliable, as it's used to look up the column by PropertyName. A column's Title can be different from its PropertyName, which would cause this to fail.
To make this more robust, I suggest passing the PropertyName instead. This requires a few changes:
-
In
src/MudBlazor/Components/DataGrid/GroupDefinition.cs, add aPropertyNameproperty:public string PropertyName { get; set; } = string.Empty;
-
In
src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs, populate this new property inProcessGroup()and when cloningInnerGroupinGetGroupDefinitions():// In ProcessGroup() return new() { //... PropertyName = column.PropertyName, //... }; // In GetGroupDefinitions() inside the innerGroup clone newGroupDefinition.InnerGroup = new GroupDefinition<T> { //... PropertyName = innerGroup.PropertyName, //... };
-
Finally, update this line to use the new property.
DataGrid.ToggleGroupExpand(GroupDefinition.PropertyName, GroupDefinition.KeyPath, _expanded);|
@vernou , I would prefer to keep 'path' in the name because it better reflects the purpose of this class |
|
Thanks! |



This PR fixes an issue with multi-level group expansion in
MudDataGrid. Previously, expanding or collapsing a nested group node expanded/collapsed all subgroups with the same key, regardless of the parent group.Now, the expansion state is tracked by the full group hierarchy—expanding a specific subgroup only affects that particular group node in its parent, not others with the same key in different branches.
This PR also renames the method
ToggleGroupExpandAsynctoToggleGroupExpandfor clarity, and updates its parameters:titleparameter has been renamed tocolumnNamefor better clarity.GroupDefinition<T> groupDefparameter was removed, since the target column can now be found directly bycolumnNamewithout needing the selector.It is also suggested to make the method public. This would make it possible to toggle specific group nodes programmatically, not just via UI buttons. For instance, in our project, we currently have to use reflection to invoke this method because it is not publicly accessible.
There are no visual changes to the UI apart from correct expand/collapse behavior for grouped nodes.
Checklist:
dev).Fixes #11725