From da6a2453ae25993dcb5355065d8cafeb2e289f83 Mon Sep 17 00:00:00 2001 From: rferraton <16419423+rferraton@users.noreply.github.com> Date: Sun, 3 May 2026 10:37:46 +0200 Subject: [PATCH 1/8] switch the default grid GroupBy from "None" to "Query Hash" and don't open the first line by default --- .../Controls/QueryStoreGridControl.axaml | 4 ++-- .../Controls/QueryStoreGridControl.axaml.cs | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml index 3a9f670..6161d5b 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml @@ -70,7 +70,7 @@ + SelectedIndex="1" SelectionChanged="GroupBy_SelectionChanged"> @@ -150,7 +150,7 @@ - + _groupedRootRows = new(); // top-level rows for grouped mode public event EventHandler>? PlansSelected; @@ -89,6 +89,7 @@ public QueryStoreGridControl(ServerConnection serverConnection, ICredentialServi // Auto-fetch with default settings on connect Avalonia.Threading.Dispatcher.UIThread.Post(() => { + ReorderColumnsForGroupBy(); Fetch_Click(null, new RoutedEventArgs()); _initialOrderByLoaded = true; }, Avalonia.Threading.DispatcherPriority.Loaded); @@ -311,12 +312,7 @@ private async System.Threading.Tasks.Task FetchGroupedPlansAsync( LoadButton.IsEnabled = true; SelectToggleButton.Content = "Select All"; - // Auto-expand the first root row to the deepest level - if (rootRows.Count > 0) - { - var first = rootRows[0]; - ExpandRowRecursive(first); - } + UpdateStatusText(); UpdateBarRatios(); From 454be5df9c1409cd96bee69e76ab588f80622240 Mon Sep 17 00:00:00 2001 From: rferraton <16419423+rferraton@users.noreply.github.com> Date: Tue, 5 May 2026 01:31:20 +0200 Subject: [PATCH 2/8] =?UTF-8?q?The=20Query=20Stores=20Overview=20feature?= =?UTF-8?q?=20has=20been=20implemented.=20Here's=20a=20summary=20of=20what?= =?UTF-8?q?=20was=20created:=20New=20Files:=201.=20QueryStoreOverviewModel?= =?UTF-8?q?s.cs=20=E2=80=94=20Models=20for:=20=E2=80=A2=20QueryStoreState?= =?UTF-8?q?=20enum=20(Off,=20ReadOnly,=20ReadWrite)=20=E2=80=A2=20Database?= =?UTF-8?q?QueryStoreState=20=E2=80=94=20state=20per=20database=20?= =?UTF-8?q?=E2=80=A2=20DatabaseMetrics=20=E2=80=94=20aggregated=20metrics?= =?UTF-8?q?=20(total=20+=20avg)=20per=20database=20=E2=80=A2=20DatabaseTim?= =?UTF-8?q?eSlice=20=E2=80=94=20time=20slice=20data=20tagged=20by=20databa?= =?UTF-8?q?se=20=E2=80=A2=20DatabaseWaitCategoryTimeSlice=20=E2=80=94=20wa?= =?UTF-8?q?it=20stats=20tagged=20by=20database=202.=20QueryStoreOverviewSe?= =?UTF-8?q?rvice.cs=20=E2=80=94=20Parallel=20data=20fetching=20with:=20?= =?UTF-8?q?=E2=80=A2=20SemaphoreSlim=20throttling=20(default=20DOP=3D8)=20?= =?UTF-8?q?=E2=80=A2=20ConcurrentBag=20for=20thread-safe=20result=20col?= =?UTF-8?q?lection=20=E2=80=A2=20Methods:=20FetchAllStatesAsync(string,=20?= =?UTF-8?q?int,=20CancellationToken),=20FetchAllMetricsAsync(string,=20Lis?= =?UTF-8?q?t,=20DateTime,=20DateTime,=20int,=20CancellationToken),?= =?UTF-8?q?=20FetchAllTimeSlicesAsync(string,=20List,=20int,=20int?= =?UTF-8?q?,=20CancellationToken),=20FetchAllWaitStatsAsync(string,=20List?= =?UTF-8?q?,=20DateTime,=20DateTime,=20int,=20CancellationToken)?= =?UTF-8?q?=203.=20QueryStoreOverviewControl.axaml=20=E2=80=94=20Layout=20?= =?UTF-8?q?with=203=20rows:=20=E2=80=A2=20Row=201:=20Donut=20chart=20+=20c?= =?UTF-8?q?onsolidated=20time=20slicer=20+=20consolidated=20wait=20stats?= =?UTF-8?q?=20ribbon=20=E2=80=A2=20Row=202:=207=20bar=20chart=20cards=20(T?= =?UTF-8?q?otal=20metrics)=20=E2=80=A2=20Row=203:=207=20bar=20chart=20card?= =?UTF-8?q?s=20(Avg=20metrics)=204.=20QueryStoreOverviewControl.axaml.cs?= =?UTF-8?q?=20=E2=80=94=20Code-behind=20with:=20=E2=80=A2=20Donut=20chart?= =?UTF-8?q?=20(RW=3Dlight=20blue,=20RO=3Ddark=20blue,=20OFF=3Dgrey,=20cent?= =?UTF-8?q?er=20shows=20active/total)=20=E2=80=A2=20Consolidated=20time=20?= =?UTF-8?q?slicer=20(30-day,=2024h=20default=20selection)=20=E2=80=A2=20Co?= =?UTF-8?q?nsolidated=20wait=20stats=20ribbon=20(sum=20across=20databases)?= =?UTF-8?q?=20=E2=80=A2=20Top-N=20bar=20cards=20with=20consistent=20databa?= =?UTF-8?q?se=20colors,=20adaptive=20font=20color,=20tooltips,=20and=20rig?= =?UTF-8?q?ht-click=20"Drill=20Down=20to=20DB=20Query=20Store"=20context?= =?UTF-8?q?=20menu=20Modified=20Files:=205.=20QuerySessionControl.axaml=20?= =?UTF-8?q?=E2=80=94=20Added=20"QS=20Overview"=20button=206.=20QuerySessio?= =?UTF-8?q?nControl.axaml.cs=20=E2=80=94=20Added=20QueryStoreOverview=5FCl?= =?UTF-8?q?ick(object=3F,=20RoutedEventArgs)=20handler=20that=20opens=20th?= =?UTF-8?q?e=20overview=20tab=20and=20wires=20drill-down=20to=20open=20sin?= =?UTF-8?q?gle-DB=20Query=20Store=20tabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/QuerySessionControl.axaml | 5 + .../Controls/QuerySessionControl.axaml.cs | 70 +++ .../Controls/QueryStoreOverviewControl.axaml | 44 ++ .../QueryStoreOverviewControl.axaml.cs | 555 ++++++++++++++++++ .../Models/QueryStoreOverviewModels.cs | 73 +++ .../Services/QueryStoreOverviewService.cs | 336 +++++++++++ 6 files changed, 1083 insertions(+) create mode 100644 src/PlanViewer.App/Controls/QueryStoreOverviewControl.axaml create mode 100644 src/PlanViewer.App/Controls/QueryStoreOverviewControl.axaml.cs create mode 100644 src/PlanViewer.Core/Models/QueryStoreOverviewModels.cs create mode 100644 src/PlanViewer.Core/Services/QueryStoreOverviewService.cs diff --git a/src/PlanViewer.App/Controls/QuerySessionControl.axaml b/src/PlanViewer.App/Controls/QuerySessionControl.axaml index 99ca2c0..a6830a2 100644 --- a/src/PlanViewer.App/Controls/QuerySessionControl.axaml +++ b/src/PlanViewer.App/Controls/QuerySessionControl.axaml @@ -57,6 +57,11 @@ Height="28" Padding="8,0" FontSize="12" Theme="{StaticResource AppButton}" ToolTip.Tip="Analyze top queries from Query Store"/> +