Skip to content

Release v2.5.0#767

Merged
erikdarlingdata merged 82 commits intomainfrom
dev
Mar 31, 2026
Merged

Release v2.5.0#767
erikdarlingdata merged 82 commits intomainfrom
dev

Conversation

@erikdarlingdata
Copy link
Copy Markdown
Owner

Summary

Release v2.5.0 — merges all changes from dev to main since v2.4.0.

Highlights

  • InstallerGui retired — installation/upgrade/uninstall now integrated into Dashboard's Add Server dialog via new Installer.Core shared library
  • Overview tab for Lite with 2x2 resource chart grid
  • Chart drill-down on CPU, Memory, TempDB, Blocking, Deadlock charts (both editions)
  • Grid-to-slicer overlay for Query Stats, Procedure Stats, Query Store tabs
  • Query heatmap tab (both editions)
  • Webhook notifications for alerts
  • Per-server collector schedule intervals
  • Alert archival — dismiss, mute, structured telemetry, stale-data indicators
  • Dashboard read-only connection intent
  • DuckDB memory capped at 2 GB during parquet compaction
  • Memory leak fixes in Lite
  • Doomed transaction / XACT_STATE error handling fixes
  • FinOps fixes: PERCENTILE_CONT syntax, RDS database_id, copy/export on all grids, server name in error logs

Testing performed

  • Fresh install (sql2016), upgrade installs (sql2017, sql2022), idempotency, uninstall + reinstall
  • Azure SQL DB (Lite) and AWS RDS (Dashboard + Lite) cloud platform testing
  • 10-minute error-free log monitoring on both apps
  • 38 installer tests + 232 Lite tests passing
  • All data survival checks verified

See CHANGELOG.md for the full list.

🤖 Generated with Claude Code

HannahVernon and others added 30 commits March 10, 2026 17:08
- Parse detail_text to extract Database, Query Text, and Wait Type
  when using 'Mute This Alert' from alert history (both editions)
- Add PopulateFromDetailText() to AlertMuteContext for structured
  field extraction from the label: value format
- Add 'Default expiration for new mute rules' dropdown to Settings
  in both editions (1 hour, 24 hours, 7 days, Never; default 24h)
- MuteRuleDialog now selects the configured default expiration
  instead of always defaulting to 'Never'
- Persist setting as mute_rule_default_expiration in settings.json
  (Lite) and preferences.json (Dashboard)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The XML doc claimed Job Name extraction but the parser did not
implement it. Add the missing branch in both Dashboard and Lite
editions so the behavior matches the documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Collapse newlines in Truncate/TruncateText so detail_text fields
  stay single-line in the label: value format
- Handle multi-line query values in PopulateFromDetailText by
  accumulating continuation lines until the next indented field
- Recognize variant query labels (Blocked Query, Blocking Query,
  Victim SQL) in addition to Query

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Explain that the field is a case-insensitive substring match and
suggest entering a distinctive fragment like a table or procedure name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
#680)

GetServerNameForStorage() used only the server hostname to generate
storage keys, so multiple Azure SQL databases on the same logical
server (e.g. myserver.database.windows.net) produced identical
server_ids and their metrics were conflated.

Include DatabaseName in the storage key when set, following the
existing pattern used for ReadOnlyIntent connections.

Fixes #677

Co-authored-by: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com>
Co-authored-by: thithanhtuyennguyen <tuyen.nguyen@wwinc.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#678)

If the model database has been configured with file sizes larger than
our defaults (1024MB data, 256MB log), CREATE DATABASE fails with
error 1803.

Two layers of protection:
1. Pre-check model file sizes via sys.master_files (TRY/CATCH for
   restricted permissions, NULL-safe fallback to defaults)
2. If CREATE DATABASE still fails with error 1803, parse the required
   size from the error message and retry once

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…) (#697)

Slicer time ranges were derived from data buckets, not the requested
window. This caused two issues:
- Sparse data tabs (Active Queries, Procedures) showed only 1 hour
  even when 4h was selected
- Dense data tabs (Query Stats, Query Store) showed an extra future
  hour via .AddHours(1) on the last bucket

Fix: pass the requested UTC time range into LoadData, fill empty
hourly buckets to span the full window, and use the requested range
for DataStartUtc/DataEndUtc instead of deriving from bucket positions.

Also adds time-range slicers to Blocked Process Reports and Deadlocks
sub-tabs (ported from worktree agent work, adapted to use the
slicer-level FillEmptyBuckets for consistency across all 6 slicers).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e slicers (#681) (#698)

* Port slicer time range fix to Dashboard, add Blocking/Deadlock/Default Trace slicers (#681)

Dashboard slicer control had the same issues as Lite:
- Extra future hour from .AddHours(1) on last bucket
- Sparse data tabs showing wrong time span

Fixes:
- Add FillEmptyBuckets and requestedStart/requestedEnd to Dashboard
  slicer control (same pattern as Lite)
- Pass time range to all 4 existing slicer LoadData calls
- Single-bucket rendering support (count < 1 guard)

New slicers:
- Blocking events slicer (Locking > Blocking sub-tab)
- Deadlock slicer (Locking > Deadlocks sub-tab)
- Default Trace slicer (Default Trace tab)

Each with slicer data queries, event handlers, and range-change
filtering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix slicer range label not updating on time display mode change

UpdateRangeLabel() wasn't called from Redraw(), so switching
Server/Local/UTC updated the x-axis labels but left the range
indicator text in the old format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Force time display mode to Server Time on startup

Charts always render in server time but the dropdown remembered the
last selection, making it look like Local/UTC was active when it
wasn't. Reset to Server Time on startup until charts support
time conversion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#699)

Clicking a row in the Top Queries, Top Procedures, or Query Store grid
overlays that item's individual trend on the slicer chart above the
grid. Orange line on its own Y scale so it's visible at any magnitude.

- Overlay metric follows the slicer sort column (CPU, duration, reads,
  etc.) — changing sort re-computes the overlay automatically
- Per-interval deltas computed from cumulative DMV values; zero-delta
  intervals filtered out (query not executing)
- Query Store labels use module name when available, falls back to
  Query ID / Plan ID
- History queries updated with fromDate/toDate support to match the
  current view's time range
- Minimum 3 data points required to show an overlay

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…) (#700)

* Dashboard: grid-to-slicer overlay for query/procedure/Query Store (#683)

Port of Lite overlay feature. Click a row in Top Queries, Top
Procedures, or Query Store → orange trend overlay on the slicer.

- Uses server-computed delta fields (TotalElapsedTimeDelta, etc.)
  instead of computing from cumulative values
- Metric follows slicer sort column, re-fires on sort change
- Query Store uses ModuleName when available, falls back to Query ID
- Same slicer overlay rendering: own Y scale, 3+ points minimum,
  clamped to chart area, no markers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Switch overlay from lines to dots, clamp to visible range, no minimum

- Dots instead of lines — clearer for sparse and dense data alike
- Every execution gets a dot (minimum 1 point, not 3)
- Dots outside the visible time range are skipped
- Fix Int16/Int32 cast bug in Dashboard query stats history reader
- Applied to both Lite and Dashboard slicer controls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix overlay dots extending past chart, preserve selection on refresh

- Clamp dots to first/last bucket time (not DataEnd which extends
  past the drawn area)
- Lite: skip ClearOverlay during _isRefreshing so overlay persists
  through auto-refresh
- Both Lite and Dashboard slicer controls updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove temporary context file

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
QueryPerformanceContent.IsRefreshing flag prevents SelectionChanged
from clearing the overlay when ItemsSource replacement deselects rows
during auto-refresh. Set by ServerTab around RefreshAllDataAsync calls.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clicking Investigate navigates to the relevant tab with a 2-hour
window centered on the incident:
- Blocking → Locking > Blocked Process Reports
- Deadlocking → Locking > Deadlocks
- Blocking and Deadlocking → Locking > Trends
- CPU Scheduling Pressure → Query Performance (root cause, not symptom)
- Memory Pressure/Grant/Clerk Growth → Memory

Global date pickers are populated with the incident window so the user
can Apply to All tabs for further investigation.

Config/stability issues (Server Configuration, Query Store, Database
Configuration, SQL Server Stability) show a dialog with the
investigate query and Copy to Clipboard button.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…) (#705)

Right-click a spike on any of these charts → context menu with
drill-down action that navigates to the relevant detail tab with a
±15 min window around the clicked point:

- CPU chart → Queries > Active Queries
- Memory chart → Memory > Memory Grants
- TempDB chart → Queries > Active Queries (spills visible there)
- Blocking trend → Blocking > Blocked Process Reports
- Deadlock trend → Blocking > Deadlocks

Custom date pickers are populated with the drill-down window so the
user can explore other tabs at the same time range.

Generic AddChartDrillDownMenuItem helper reusable for future charts.
Lite only — Dashboard port to follow.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#706)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues:
1. Slicer range label showed "(0h)" for sub-hour ranges because
   TotalHours was formatted with F0. Now shows "30m" for ranges < 1h.
2. Setting custom pickers programmatically (from drill-down) triggered
   cascading refreshes mid-update. Added _isRefreshing guards to
   CustomDateRange_Changed, CustomTimeCombo_Changed, and
   TimeRangeCombo_SelectionChanged.

Note: The slicer displaying "10:00" when pickers show "13:00" is
correct — slicer shows server time (Pacific), pickers show local
time (Eastern). This is not a bug.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spike chart hover snaps to zero-baseline transition point, which can
be minutes before actual data. ±30 min catches the full incident.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cks (#682) (#709)

Port of Lite drill-down feature. Right-click chart spike → navigate
to relevant detail tab with ±30 min window:

- Blocking Events chart → Locking > Blocked Process Reports
- Deadlocks chart → Locking > Deadlocks
- CPU Trends chart → Query Performance (via event from ResourceMetricsContent)
- TempDB Trends chart → Query Performance (via event from ResourceMetricsContent)
- Memory Overview chart → Query Performance (via event from MemoryContent)

Global date pickers populated for cross-tab investigation.
Child UserControls raise ChartDrillDownRequested events handled by ServerTab.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hover helpers were null when captured during constructor. Changed to
lazy Func<> so the helper is read at menu-open time.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fixed navigation to Queries > Active Queries (was landing on
  Performance Trends)
- Added drill-down to all 4 Server Trends charts (CPU, TempDB,
  Memory, Perfmon)
- Added drill-down to all Memory charts (Overview, Grants, Clerks,
  Pressure Events)
- Added drill-down to Blocking Duration and Deadlock Wait Time charts
- No chart left behind

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends drill-down coverage to:
- Overview: all 4 Resource Overview charts (CPU, Memory, I/O, Waits)
- Locking: Lock Wait Stats, Current Waits Duration/Blocked
- Memory: Grant Sizing, Grant Activity, Clerks, Pressure Events

Also fixes navigation to Active Queries (was Performance Trends).
Skipped Resource Metrics detail charts and Query Performance Trends
pending #689/#691 view consolidation.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UseLayoutRounding="True" to Windows that render graphs to remove blur on graph lines and legend text.

Co-authored-by: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com>
Co-authored-by: Daniel Hugo <Daniel.Hugo@multichoice.co.za>
New first tab showing at-a-glance server health:
- CPU Utilization (SQL CPU %)
- Memory Utilization (Buffer Pool + Memory Grants)
- File I/O Latency (avg read/write ms across all databases)
- Wait Statistics (top 5 wait types by total time)

No pickers — responds to the global time range picker. All data
from existing collectors, no new queries. Chart drill-down enabled
on all four charts. Tab indices updated for the insertion.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
)

@@Version can exceed 255 characters on some SQL Server editions.
This caused installation_history inserts to fail, making the
installer think it was always a fresh install.

- Fresh installs: column defined as nvarchar(512)
- Upgrades: ALTER COLUMN in 2.4.0-to-2.5.0 upgrade script
- Idempotent: checks current max_length before altering

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HannahVernon and others added 29 commits March 27, 2026 19:22
* Pre-fill context fields and add default expiration setting

- Parse detail_text to extract Database, Query Text, and Wait Type
  when using 'Mute This Alert' from alert history (both editions)
- Add PopulateFromDetailText() to AlertMuteContext for structured
  field extraction from the label: value format
- Add 'Default expiration for new mute rules' dropdown to Settings
  in both editions (1 hour, 24 hours, 7 days, Never; default 24h)
- MuteRuleDialog now selects the configured default expiration
  instead of always defaulting to 'Never'
- Persist setting as mute_rule_default_expiration in settings.json
  (Lite) and preferences.json (Dashboard)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add missing Job Name parsing to PopulateFromDetailText

The XML doc claimed Job Name extraction but the parser did not
implement it. Add the missing branch in both Dashboard and Lite
editions so the behavior matches the documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix mute dialog showing truncated query text for blocking alerts

- Collapse newlines in Truncate/TruncateText so detail_text fields
  stay single-line in the label: value format
- Handle multi-line query values in PopulateFromDetailText by
  accumulating continuation lines until the next indented field
- Recognize variant query labels (Blocked Query, Blocking Query,
  Victim SQL) in addition to Query

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add tooltip to Query Text field in mute rule dialog

Explain that the field is a case-insensitive substring match and
suggest entering a distinctive fragment like a table or procedure name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add Dismiss Selected to context menu and View Log button to sidebar

Context menu dismiss (both editions):
- Added 'Dismiss Selected' menu item with  icon to the right-click
  context menu in Alert History DataGrid
- Reuses existing DismissSelected_Click handler  no new code needed

View Log button (both editions):
- Added 'View Log' button to sidebar footer in MainWindow
- Opens the current day's log file via ShellExecute; falls back to
  opening the log directory if the file doesn't exist yet
- Lite: exposed GetCurrentLogFile() and GetLogDirectory() on AppLogger
- Dashboard: uses existing Logger.GetCurrentLogFile() / GetLogDirectory()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dashboard (#741): Auto-refresh timer was overwriting Active Queries
data after a right-click drill-down. Added _isDrillDownActive flag
that prevents RefreshActiveQueriesAsync from running until the user
switches sub-tabs or changes the time range.

Lite (#742): Drill-down handlers (CPU, Memory, TempDB, Heatmap)
navigated to Active Queries but didn't refresh the slicer. Added
LoadActiveQueriesSlicerAsync() call to each handler.

Fixes #741, Fixes #742

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix drill-down navigation bugs in Dashboard and Lite
The colorbar panel was being added on every refresh without removing
the previous one. Track it in _legendPanels so ClearChart removes it.

Also widened Lite's _legendPanels dictionary from LegendPanel? to
IPanel? to accommodate ColorBar (which implements IPanel but not
LegendPanel).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cking

Fix heatmap colorbar stacking on refresh
Lite: FormatServerTime expects UTC but drill-down handlers passed
server time, causing double-offset display (e.g. "01:40" instead of
"08:40"). Fixed by converting back to UTC before formatting.

Dashboard: Heatmap drill-down now updates _activeQueriesFromDate/ToDate
and refreshes the slicer, so the slicer shows the drill-down window
instead of the stale 24h range.

Fixes #745

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix drill-down timezone mismatches and slicer state
Lite:
- Drill-down label now converts server time back to UTC before calling
  FormatServerTime (fixes double-offset showing "01:40" instead of "08:40")
- SetDrillDownTimeRange uses ConvertForDisplay to respect display mode
  (Server Time / Local / UTC) instead of always showing local time
- Slicer query pads ±1 hour for narrow drill-down windows so hourly
  buckets aren't missed

Dashboard:
- Heatmap drill-down now fires DrillDownTimeRangeRequested event so
  parent ServerTab populates custom range pickers
- Bucket time is server time (SQL Server stores local), no UTC conversion needed
- SetPickersFromDateTime respects current display mode via ConvertForDisplay
- Slicer query pads ±1 hour for narrow drill-down windows
- Active queries state fields updated on drill-down so slicer matches

Fixes #745

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix drill-down timezone and picker display issues
Both apps:
- Pickers now display time in the current display mode instead of
  always showing local time
- Added DisplayTimeToServerTime() to reverse ConvertForDisplay()
- Switching display mode re-converts picker values without triggering
  cascading refreshes or validation errors

Dashboard:
- Added _suppressPickerUpdates flag to prevent GlobalCustomDateTime_Changed
  and GlobalTimeCombo_Changed from firing during mode switch
- SetPickersFromDateTime uses ConvertForDisplay

Lite:
- SetDrillDownTimeRange and SetPickersFromDateTime use ConvertForDisplay
- All picker reads use DisplayTimeToServerTime instead of LocalToServerTime
- _isRefreshing suppresses refreshes during mode switch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make custom range pickers respect display mode
…-718

fix: dismiss reliability - write lock, batched UPDATE, transaction, timeout (#718)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…755)

Phase 1 extraction of shared installation logic into Installer.Core class
library. InstallerGui and Installer.Tests now consume the shared library
instead of duplicating code. CLI Installer refactor is next.

New files:
- Installer.Core/InstallationService.cs — all static install/upgrade methods
- Installer.Core/DependencyInstaller.cs — community dependency downloads
- Installer.Core/ScriptProvider.cs — filesystem + embedded resource abstraction
- Installer.Core/Patterns.cs — shared regex patterns ([GeneratedRegex])
- Installer.Core/Models/ — InstallationProgress, ServerInfo, InstallationResult,
  UpgradeInfo, InstallationResultCode (enum mapping CLI exit codes 0-8)

Key changes:
- SQL scripts embedded as assembly resources for future Dashboard integration
- ScriptProvider.FromDirectory() for CLI/GUI, FromEmbeddedResources() for Dashboard
- AutoDiscover() searches filesystem then falls back to embedded
- Comprehensive [DEBUG] logging throughout all methods for GUI diagnostics
- upgrade.txt missing warning (was silently skipped, now logged)
- GenerateSummaryReport gains optional outputDirectory parameter

Retargeted:
- InstallerGui references Installer.Core, old InstallationService.cs deleted
- Installer.Tests targets net8.0 (was net8.0-windows), no WPF dependency
- Tests use ScriptProvider.FromDirectory() instead of raw file paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Program.cs rewritten from 2,122 lines to 1,172 lines (45% reduction).
All duplicated logic removed — CLI now delegates to Installer.Core for:

- Connection string building (InstallationService.BuildConnectionString)
- Connection testing (InstallationService.TestConnectionAsync)
- Script discovery (ScriptProvider.FromDirectory)
- SQL file execution (InstallationService.ExecuteInstallationAsync)
- Upgrade detection and execution (ExecuteAllUpgradesAsync)
- Uninstall (InstallationService.ExecuteUninstallAsync)
- Version detection (InstallationService.GetInstalledVersionAsync)
- Community dependencies (DependencyInstaller)
- Validation (InstallationService.RunValidationAsync)
- Installation history (InstallationService.LogInstallationHistoryAsync)
- Summary reports (InstallationService.GenerateSummaryReport)

What stays in Program.cs (console-specific):
- Argument parsing, interactive prompts, retry loop
- Console output helpers (WriteSuccess/WriteError/WriteWarning)
- ReadPassword, WaitForExit, CheckForInstallerUpdateAsync
- Error log generation (WriteErrorLog)

Fixes during review:
- Added missing exit code 8 (UpgradesFailed) to --help text
- Fixed encryption default label in error message (was "optional", is "mandatory")

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ollection_table (#756)

When calculate_deltas was called inside a collector's transaction and failed,
the CATCH block tried to INSERT into collection_log while the transaction was
doomed (XACT_STATE = -1), swallowing the real error with "The current transaction
cannot be committed." Same pattern in ensure_collection_table where INSERT
happened before ROLLBACK.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ollection_table (#756) (#757)

When calculate_deltas was called inside a collector's transaction and failed,
the CATCH block tried to INSERT into collection_log while the transaction was
doomed (XACT_STATE = -1), swallowing the real error with "The current transaction
cannot be committed." Same pattern in ensure_collection_table where INSERT
happened before ROLLBACK.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add Installer.Core shared library and retarget InstallerGui + Tests (#755)

Phase 1 extraction of shared installation logic into Installer.Core class
library. InstallerGui and Installer.Tests now consume the shared library
instead of duplicating code. CLI Installer refactor is next.

New files:
- Installer.Core/InstallationService.cs — all static install/upgrade methods
- Installer.Core/DependencyInstaller.cs — community dependency downloads
- Installer.Core/ScriptProvider.cs — filesystem + embedded resource abstraction
- Installer.Core/Patterns.cs — shared regex patterns ([GeneratedRegex])
- Installer.Core/Models/ — InstallationProgress, ServerInfo, InstallationResult,
  UpgradeInfo, InstallationResultCode (enum mapping CLI exit codes 0-8)

Key changes:
- SQL scripts embedded as assembly resources for future Dashboard integration
- ScriptProvider.FromDirectory() for CLI/GUI, FromEmbeddedResources() for Dashboard
- AutoDiscover() searches filesystem then falls back to embedded
- Comprehensive [DEBUG] logging throughout all methods for GUI diagnostics
- upgrade.txt missing warning (was silently skipped, now logged)
- GenerateSummaryReport gains optional outputDirectory parameter

Retargeted:
- InstallerGui references Installer.Core, old InstallationService.cs deleted
- Installer.Tests targets net8.0 (was net8.0-windows), no WPF dependency
- Tests use ScriptProvider.FromDirectory() instead of raw file paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Refactor CLI Installer to thin wrapper over Installer.Core (#755)

Program.cs rewritten from 2,122 lines to 1,172 lines (45% reduction).
All duplicated logic removed — CLI now delegates to Installer.Core for:

- Connection string building (InstallationService.BuildConnectionString)
- Connection testing (InstallationService.TestConnectionAsync)
- Script discovery (ScriptProvider.FromDirectory)
- SQL file execution (InstallationService.ExecuteInstallationAsync)
- Upgrade detection and execution (ExecuteAllUpgradesAsync)
- Uninstall (InstallationService.ExecuteUninstallAsync)
- Version detection (InstallationService.GetInstalledVersionAsync)
- Community dependencies (DependencyInstaller)
- Validation (InstallationService.RunValidationAsync)
- Installation history (InstallationService.LogInstallationHistoryAsync)
- Summary reports (InstallationService.GenerateSummaryReport)

What stays in Program.cs (console-specific):
- Argument parsing, interactive prompts, retry loop
- Console output helpers (WriteSuccess/WriteError/WriteWarning)
- ReadPassword, WaitForExit, CheckForInstallerUpdateAsync
- Error log generation (WriteErrorLog)

Fixes during review:
- Added missing exit code 8 (UpgradesFailed) to --help text
- Fixed encryption default label in error message (was "optional", is "mandatory")

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix doomed transaction error handling in delta framework and ensure_collection_table (#756)

When calculate_deltas was called inside a collector's transaction and failed,
the CATCH block tried to INSERT into collection_log while the transaction was
doomed (XACT_STATE = -1), swallowing the real error with "The current transaction
cannot be committed." Same pattern in ensure_collection_table where INSERT
happened before ROLLBACK.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add XACT_STATE check after third-party proc calls in XML processors (#695)

sp_BlitzLock and sp_HumanEventsBlockViewer can fail internally and doom
the caller's transaction. Without checking XACT_STATE after the call,
the next write attempt produces "cannot be committed" which swallows
the real error. Now we detect the doomed state, rollback, and surface
a meaningful error message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…758) (#760)

- DeltaCalculator: add ClearServer() to free cache when server tab closes
- MainWindow: store event handler delegates so they can be unsubscribed
  on tab close (AlertCountsChanged, ApplyTimeRangeRequested, ManualRefreshRequested)
- ChartHoverHelper: add Dispose() to unsubscribe MouseMove/MouseLeave events
- ServerTab: add DisposeChartHelpers() to clean up all 27 hover helpers
- CloseServerTab now calls all cleanup methods

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract Installer.Core shared library and refactor CLI (#755)
The in-memory DuckDB connection used for archive compaction was using the
default memory_limit (80% of physical RAM), causing multi-GB spikes when
decompressing large parquet archives. With 800 MB of compressed archives,
this easily hits 5-7 GB.

Set memory_limit=2GB so DuckDB spills to disk instead of consuming all
available RAM. Also set preserve_insertion_order=false to reduce memory
pressure since compaction has no meaningful row order.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Phase 2)

Add Server dialog now detects the PerformanceMonitor database state after
Test Connection and offers Install Now / Upgrade options inline.

New features:
- Database status detection: no DB → Install Now, old version → Upgrade,
  current version → "up to date"
- Full installation with progress bar, scrolling log, and cancel button
- Advanced options (collapsed): clean install, reset schedule, validation,
  community dependencies
- Credential separation: post-install prompt for lower-privilege monitoring
  credentials (SQL auth only)
- Installation report generation with View Report button
- "Skip, just add server" link to bypass installation
- Edit mode skips all installation detection (existing behavior preserved)

Technical:
- Dashboard references Installer.Core, uses ScriptProvider.FromEmbeddedResources()
  (SQL scripts embedded in assembly — no filesystem dependency)
- State machine (DialogState enum) drives panel visibility
- All UI updates wrapped in Dispatcher.Invoke for thread safety
- Connection test targets master (not PerformanceMonitor) so it succeeds
  when the database doesn't exist yet
- Save button uses MinWidth+Padding to fit "Save & Connect" text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…stall

Integrate installation into Dashboard Add Server dialog (#755 Phase 2)
InstallerGui functionality is now in Dashboard's Add Server dialog.

- Removed InstallerGui from PerformanceMonitor.sln
- Removed InstallerGui from build.yml, ci.yml, nightly.yml
- Removed InstallerGui publish step from build-dashboard.cmd
- Updated README, CONTRIBUTING, install/README to reference
  Dashboard + Installer.Core instead of InstallerGui

Note: InstallerGui/ directory is left in place for reference but is
no longer built, published, or signed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…llergui

Retire InstallerGui from solution and build pipeline (#755 Phase 3)
…index spools

The condition checked PhysicalOp.Contains("Spool") which matched both
"Index Spool" and "Table Spool". Now checks for "Index" specifically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lse-positive

Fix Rule 2: eager table spools no longer flagged as eager index spools
…ase testing (#766)

- Bump all project versions to 2.5.0
- Add v2.5.0 changelog entry and README sync (Overview tab, query heatmap, webhooks, chart drill-down)
- Fix PERCENTILE_CONT missing OVER() in FinOps VM right-sizing query
- Fix database_id column reference on AWS RDS sys.dm_db_persisted_sku_features
- Fix FinOps right-click copy/export broken on all Dashboard grids (PlacementTarget was row, not grid)
- Add server name to FinOps recommendation error logs
- Add null guard for _dbSizesFilterMgr race condition

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@erikdarlingdata erikdarlingdata merged commit e4580c7 into main Mar 31, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants