Skip to content

Plan viewer: properties panel row model, filter box, copy menu, remembered width - #4588

Merged
erikdarlingdata merged 1 commit into
devfrom
viewer/4574-properties-row-model
Sep 28, 2026
Merged

erikdarlingdata merged 1 commit into
devfrom
viewer/4574-properties-row-model

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4574, part of #4511.

Why

The plan viewer's properties panel built a flat tree of controls per row with no row model, no filter box, and no copy menu, and reset the panel width to a fixed 320px on every node selection. PerformanceStudio's Avalonia viewer (erikdarlingdata/PerformanceStudio@435e12d, @e156e56, @217c580, @b5ba82e) added a row model, a filter box, a copy menu, and a remembered width; this ports that to PM's shared WPF PlanViewerControl.

What changes

  • A shared row model (PerformanceMonitor.PlanAnalysis.PropertyRows: PropertyRow, PropertySection) records each row's label, value, isCode, and searchable text as the panel builds, instead of only adding it to the visual tree. Put in PerformanceMonitor.PlanAnalysis (not PerformanceMonitor.Ui, which is net10.0-windows-only) so a unit test can pin the filter and copy logic without WPF.
  • A filter box under the panel header: case-insensitive match on label and value; a section with no matching rows hides; a section whose own title matches keeps all its rows; Esc clears the filter while it has focus; the text sticks across node selections (the panel is rebuilt on every selection, so ApplyPropertiesFilter() re-runs after each rebuild).
  • A copy menu on each value control: "Copy value", "Copy name and value", "Copy all properties" — replacing the stock read-only TextBox context menu (greyed-out Cut/Copy, live Paste on data nobody can edit). "Copy all properties" renders from the row model, not the visual tree, so a code value (a predicate, a CREATE INDEX statement) pastes back out verbatim.
  • The clipboard write is wrapped in a guarded try/catch (TrySetClipboardText) against the transient CLIPBRD_E_CANT_OPEN failure another process briefly holding the clipboard can cause — the write-side sibling of PM's existing guarded-read helper (ClipboardText.cs). Kept minimal and local to this file rather than adding a new shared writer class; a later change can promote it if a second caller needs it.
  • The panel width is now set only on the hidden→visible transition and remembered for the session (default 380, clamped 280–800 while the panel is open), instead of being reset to a fixed 320px on every node selection. MinWidth/MaxWidth are cleared on close so they don't hold a strip of the panel open.

Lite: gets this too (shared control). Both the Darling Viewer and Lite host PerformanceMonitor.Ui.PlanViewerControl, so this change reaches both with no per-app work.

PM-ahead / Not ported: PS's splitter hover-highlight cosmetic touch (from @b5ba82e) is not ported — WPF's GridSplitter styling isn't a drop-in match for Avalonia's, and the issue text called it optional.

Test plan

New file: Darling/Darling.Tests/Viewer4574Tests.cs, 18 pins on PropertyRows/PropertyRow (filter section-title match and case-insensitivity, row matching, section-visibility, width clamp, CopyValue/CopyLabelAndValue, BuildPropertiesText for ordinary/code/block rows and section-skipping).

  • RED on dev: compile-only. PropertyRows/PropertySection don't exist on dev; built the same test file against a detached worktree at dev's tip (130bc6a) and got CS0246: The type or namespace name 'PropertyRow' could not be found / same for PropertySection.
  • Mutation: changed RowMatches's own-text comparison from StringComparison.OrdinalIgnoreCase to .Ordinal, rebuilt, reran — RowMatches_OwnTextMatch_CaseInsensitiveOnLabelAndValue failed as expected. Reverted, rebuilt, reran green.
  • GREEN on this branch: dotnet Darling.Tests.dll -class Darling.Tests.Viewer4574Tests → Total: 18, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0.
  • Built clean (0 warnings, 0 errors) on: Darling.Tests, PerformanceMonitor.PlanAnalysis (via that build), PerformanceMonitor.Ui, Lite/PerformanceMonitorLite.csproj, Lite.Tests, Darling/PerformanceMonitor.Darling.Viewer.
  • Ran the full required class list (Viewer4574Tests + every PlanSync*/Viewer* class + the named Actual*/Darling*/ShowPlanParser*/etc. classes): Total: 261, Errors: 0, Failed: 12. The 12 failures are all in DarlingAnalysisPipelineTests/DarlingMcpPlanToolsLivePostgresTests, pre-existing live-Postgres tests whose fixture couldn't connect on this rig (no psql/pg_isready available here to stand up the store) — unrelated to this change; nothing in this PR touches that pipeline. PlanViewerBenefitDisplayTests, PlanViewerCapabilityPinTests, PlanViewerOriginNavigationTests, and the new Viewer4574Tests (the classes actually exercising viewer/plan-analysis surfaces this PR touches) are all green.
  • Lite.Tests builds clean but its discovery doesn't run on macOS; not exercised here — the properties panel isn't a Lite-only surface (it's the shared PerformanceMonitor.Ui control, pinned via Darling.Tests above), so nothing Lite-specific needed a new Lite pin.
  • Analyzer warnings grep (warning (CA|CS|IDE)[0-9]+) was empty on every build log above.

Screenshot plan (UI, not covered by the unit pins): open a plan with at least one node that has a section with several properties (e.g. a Clustered Index Scan node — "Object", "Estimated Costs", "Estimated Rows" sections). Select the node, open the Properties panel, and check:

  1. A filter textbox appears directly under the "Operator Properties" / "Node ID: N" header, above the first section.
  2. Typing a label or value substring (e.g. "cost") hides every row that doesn't match in every section, and collapses (hides) any section left with zero matching rows.
  3. Typing a section's own title (e.g. "Estimated Rows") keeps every row in that section visible regardless of each row's own text.
  4. Pressing Esc while the filter box has focus clears the filter and restores all rows/sections.
  5. Selecting a different node with the filter text still present keeps the same filter text and re-applies it to the new node's rows.
  6. Right-clicking a property value shows "Copy value" / "Copy name and value" / a separator / "Copy all properties" — not the stock Cut/Copy/Paste menu.
  7. "Copy all properties" on the clipboard, pasted into a text editor, shows the operator header, then each section title with its rows as " Label: Value" lines, and any code-valued row (e.g. "Full Name") with the value on its own line beneath the label.
  8. Dragging the properties/canvas splitter to a new width, closing the panel (X button), then reopening it on a different node reopens at the dragged width (clamped to 280–800), not back at a fixed 320px.

CHANGELOG

SECTION: Changed
ENTRY: - The plan viewer's properties panel can be filtered and copied, and keeps its width ([#4588]) - The panel now has a filter box, a copy menu with "Copy value" / "Copy name and value" / "Copy all properties", and remembers the width you drag it to for the session.
REF: [#4588]: #4588

…bered width

The plan viewer's properties panel built a flat tree of controls per row with
no row model, filter box, or copy menu, and reset the panel width to a fixed
320px on every node selection.

Adds a shared row model (label, value, isCode, searchable text) in
PerformanceMonitor.PlanAnalysis.PropertyRows, so the filter and the copy
menu work from recorded data instead of the visual tree. On top of that:

- A filter box under the panel header: case-insensitive on label and value;
  a section with no matching rows hides; a section whose own title matches
  keeps all its rows; Esc clears the filter while it has focus; the text
  sticks across node selections.
- A copy menu on each value control ('Copy value', 'Copy name and value',
  'Copy all properties'), replacing the stock read-only TextBox menu
  (greyed-out Cut/Copy, live Paste on data nobody can edit). 'Copy all
  properties' renders from the row model, so code values paste back out
  verbatim. The clipboard write is guarded against the transient
  CLIPBRD_E_CANT_OPEN failure another process holding the clipboard can
  cause.
- The panel width is set only on the hidden-to-visible transition and
  remembered for the session (default 380, clamped 280-800 while open),
  instead of being reset on every selection.

Lite and the Darling Viewer both host the shared PlanViewerControl, so both
get this without any change on their side.

## CHANGELOG entry
SECTION: Changed
ENTRY: The plan viewer's properties panel now has a filter box, a copy menu
with 'Copy value' / 'Copy name and value' / 'Copy all properties', and
remembers the width you drag it to for the session. ([#PR])
[#PR]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/PR
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 28, 2026 15:44
@erikdarlingdata
erikdarlingdata merged commit 93b3a27 into dev Sep 28, 2026
17 of 20 checks passed
@erikdarlingdata
erikdarlingdata deleted the viewer/4574-properties-row-model branch September 28, 2026 15:44
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.

1 participant