Skip to content

Commit c489d6a

Browse files
committed
1. Auto-expand first row (ExpandRowRecursive): After populating grouped rows in FetchGroupedPlansAsync, the first root row is recursively expanded to the deepest level so the user immediately sees the full hierarchy.
2. Load child plans (LoadHighlightedPlan_Click / LoadSelected_Click / CollectLeafPlans): • "Load Plan" on a grouped row (QueryHash, PlanHash, or Module level) now recursively collects all descendant leaf plans where both PlanId > 0 and QueryId > 0 • Rows with PlanId == 0 or QueryId == 0 are never loaded • "Load Selected" does the same expansion for each selected row 3. Golden headers (ApplyGroupByHeaderColors): • When GroupBy=QueryHash: "Query Hash" and "Plan Hash" column headers get gold (#FFD700) foreground • When GroupBy=Module: "Module" and "Query Hash" column headers get gold • When GroupBy=None: all header colors are reset to default 4. Column reorder (ReorderColumnsForGroupBy): • QueryId and PlanId columns are now pushed after the GroupBy key columns • QueryHash mode: Expand → Checkbox → QueryHash → PlanHash → QueryId → PlanId → rest • Module mode: Expand → Checkbox → Module → QueryHash → QueryId → PlanId → rest • None mode: original column order is fully restored
1 parent 1e92eaa commit c489d6a

2 files changed

Lines changed: 134 additions & 13 deletions

File tree

src/PlanViewer.App/Controls/QueryStoreGridControl.axaml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,19 @@
150150
</ContextMenu>
151151
</DataGrid.ContextMenu>
152152
<DataGrid.Columns>
153-
<DataGridTemplateColumn Header="" Width="200" IsVisible="False">
153+
<DataGridTemplateColumn Header="" Width="220" IsVisible="False">
154154
<DataGridTemplateColumn.CellTemplate>
155155
<DataTemplate x:DataType="local:QueryStoreRow">
156-
<StackPanel Orientation="Horizontal" Margin="{Binding IndentMargin}">
156+
<StackPanel Orientation="Horizontal" Margin="{Binding IndentMargin}"
157+
VerticalAlignment="Stretch">
157158
<Button Content="{Binding ExpandChevron}"
158159
IsVisible="{Binding HasChildren}"
159-
Width="24" Height="22" Padding="0"
160-
FontSize="14" FontWeight="Bold"
160+
MinWidth="20" Padding="2,0"
161+
FontSize="18" FontWeight="Bold"
161162
Background="Transparent" BorderThickness="0"
162163
Foreground="{DynamicResource ForegroundBrush}"
163-
VerticalAlignment="Center"
164+
VerticalAlignment="Stretch"
165+
VerticalContentAlignment="Center"
164166
Click="ExpandRow_Click"/>
165167
<TextBlock Text="{Binding GroupDisplayText}"
166168
VerticalAlignment="Center"

src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs

Lines changed: 127 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,14 @@ private async System.Threading.Tasks.Task FetchGroupedPlansAsync(
299299

300300
LoadButton.IsEnabled = true;
301301
SelectToggleButton.Content = "Select All";
302+
303+
// Auto-expand the first root row to the deepest level
304+
if (rootRows.Count > 0)
305+
{
306+
var first = rootRows[0];
307+
ExpandRowRecursive(first);
308+
}
309+
302310
UpdateStatusText();
303311
UpdateBarRatios();
304312

@@ -307,6 +315,29 @@ private async System.Threading.Tasks.Task FetchGroupedPlansAsync(
307315
_ = FetchGroupedWaitStatsAsync(_slicerStartUtc.Value, _slicerEndUtc.Value, ct);
308316
}
309317

318+
/// <summary>
319+
/// Recursively expands a row and all its children, inserting them into _filteredRows.
320+
/// </summary>
321+
private void ExpandRowRecursive(QueryStoreRow row)
322+
{
323+
if (!row.HasChildren) return;
324+
row.IsExpanded = true;
325+
326+
var idx = _filteredRows.IndexOf(row);
327+
if (idx < 0) return;
328+
329+
var insertAt = idx + 1;
330+
foreach (var child in row.Children)
331+
{
332+
_filteredRows.Insert(insertAt, child);
333+
insertAt++;
334+
}
335+
336+
// Recurse into each child that has children
337+
foreach (var child in row.Children)
338+
ExpandRowRecursive(child);
339+
}
340+
310341
/// <summary>
311342
/// Fetches per-plan wait stats for all real plan IDs found in the grouped hierarchy,
312343
/// assigns them to leaf rows, then consolidates upward to intermediate and root rows.
@@ -650,19 +681,65 @@ private void ReorderColumnsForGroupBy()
650681
cols[i].DisplayIndex = _savedColumnDisplayIndices[i];
651682
_savedColumnDisplayIndices = null;
652683
}
684+
// Reset header colors
685+
ApplyGroupByHeaderColors();
653686
return;
654687
}
655688

656689
// Save original order if not yet saved
657690
_savedColumnDisplayIndices ??= cols.Select(c => c.DisplayIndex).ToArray();
658691

659-
// Find the target column to promote
660-
// cols[4] = QueryHash, cols[6] = Module (from AXAML definition order)
661-
var targetColIndex = _groupByMode == QueryStoreGroupBy.QueryHash ? 4 : 6;
662-
var targetCol = cols[targetColIndex];
692+
// Column definition indices (AXAML order):
693+
// 0=Expand, 1=Checkbox, 2=QueryId, 3=PlanId, 4=QueryHash, 5=PlanHash, 6=Module
694+
if (_groupByMode == QueryStoreGroupBy.QueryHash)
695+
{
696+
// Order: Expand, Checkbox, QueryHash, PlanHash, QueryId, PlanId, ...
697+
cols[4].DisplayIndex = 2; // QueryHash → 2
698+
cols[5].DisplayIndex = 3; // PlanHash → 3
699+
cols[2].DisplayIndex = 4; // QueryId → 4
700+
cols[3].DisplayIndex = 5; // PlanId → 5
701+
}
702+
else // Module
703+
{
704+
// Order: Expand, Checkbox, Module, QueryHash, QueryId, PlanId, ...
705+
cols[6].DisplayIndex = 2; // Module → 2
706+
cols[4].DisplayIndex = 3; // QueryHash → 3
707+
cols[2].DisplayIndex = 4; // QueryId → 4
708+
cols[3].DisplayIndex = 5; // PlanId → 5
709+
}
663710

664-
// Move it to DisplayIndex 2 (right after expand[0] and checkbox[1])
665-
targetCol.DisplayIndex = 2;
711+
// Apply golden header colors for expandable columns
712+
ApplyGroupByHeaderColors();
713+
}
714+
715+
/// <summary>
716+
/// Applies golden foreground to column headers that represent expandable/collapsible
717+
/// grouping levels in the current GroupBy mode, and resets others.
718+
/// </summary>
719+
private void ApplyGroupByHeaderColors()
720+
{
721+
// Column definition indices: 4=QueryHash, 5=PlanHash, 6=Module
722+
var goldenCols = _groupByMode switch
723+
{
724+
QueryStoreGroupBy.QueryHash => new HashSet<int> { 4, 5 }, // QueryHash + PlanHash
725+
QueryStoreGroupBy.Module => new HashSet<int> { 6, 4 }, // Module + QueryHash
726+
_ => new HashSet<int>(),
727+
};
728+
729+
var goldenBrush = new SolidColorBrush(Color.FromRgb(0xFF, 0xD7, 0x00)); // Gold
730+
731+
for (int i = 0; i < ResultsGrid.Columns.Count; i++)
732+
{
733+
var col = ResultsGrid.Columns[i];
734+
if (col.Header is not StackPanel sp) continue;
735+
var label = sp.Children.OfType<TextBlock>().LastOrDefault();
736+
if (label == null) continue;
737+
738+
if (goldenCols.Contains(i))
739+
label.Foreground = goldenBrush;
740+
else
741+
label.ClearValue(TextBlock.ForegroundProperty);
742+
}
666743
}
667744

668745
private void ExpandRow_Click(object? sender, RoutedEventArgs e)
@@ -1006,15 +1083,57 @@ private void SelectToggle_Click(object? sender, RoutedEventArgs e)
10061083

10071084
private void LoadSelected_Click(object? sender, RoutedEventArgs e)
10081085
{
1009-
var selected = _filteredRows.Where(r => r.IsSelected).Select(r => r.Plan).ToList();
1086+
List<QueryStorePlan> selected;
1087+
if (_groupByMode != QueryStoreGroupBy.None)
1088+
{
1089+
// In grouped mode, expand selected grouped rows to their leaf plans
1090+
selected = _filteredRows
1091+
.Where(r => r.IsSelected)
1092+
.SelectMany(r => r.HasChildren ? CollectLeafPlans(r) : (r.PlanId > 0 && r.QueryId > 0 ? [r.Plan] : []))
1093+
.ToList();
1094+
}
1095+
else
1096+
{
1097+
selected = _filteredRows.Where(r => r.IsSelected).Select(r => r.Plan).ToList();
1098+
}
10101099
if (selected.Count > 0)
10111100
PlansSelected?.Invoke(this, selected);
10121101
}
10131102

10141103
private void LoadHighlightedPlan_Click(object? sender, RoutedEventArgs e)
10151104
{
1016-
if (ResultsGrid.SelectedItem is QueryStoreRow row)
1105+
if (ResultsGrid.SelectedItem is not QueryStoreRow row) return;
1106+
1107+
// In grouped mode, load all descendant leaf plans with real IDs
1108+
if (_groupByMode != QueryStoreGroupBy.None && row.HasChildren)
1109+
{
1110+
var leafPlans = CollectLeafPlans(row);
1111+
if (leafPlans.Count > 0)
1112+
PlansSelected?.Invoke(this, leafPlans);
1113+
}
1114+
else if (row.PlanId > 0 && row.QueryId > 0)
1115+
{
10171116
PlansSelected?.Invoke(this, new List<QueryStorePlan> { row.Plan });
1117+
}
1118+
}
1119+
1120+
/// <summary>
1121+
/// Recursively collects all leaf-level plans (PlanId > 0 and QueryId > 0) from a grouped row and its descendants.
1122+
/// </summary>
1123+
private static List<QueryStorePlan> CollectLeafPlans(QueryStoreRow row)
1124+
{
1125+
var plans = new List<QueryStorePlan>();
1126+
if (row.Children.Count == 0)
1127+
{
1128+
if (row.PlanId > 0 && row.QueryId > 0)
1129+
plans.Add(row.Plan);
1130+
}
1131+
else
1132+
{
1133+
foreach (var child in row.Children)
1134+
plans.AddRange(CollectLeafPlans(child));
1135+
}
1136+
return plans;
10181137
}
10191138

10201139
private async void ViewHistory_Click(object? sender, RoutedEventArgs e)

0 commit comments

Comments
 (0)