Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- **Five ready-made dashboards, so a new user's Custom Views page is not an empty one** ([#2480]) - no custom view ships seeded, so the first thing a new user meets at `#/views` is a blank page and a blank canvas over an 82-read catalog. The first-run hero already knew what would help and rendered it as three **inert** chips - "Top waits by server", "CPU trend over time", "Slowest procedures by database", no handler, no href - which is the [#2437] defect shape (a promise rendered as a caption) on the one page nobody arrives at with context. Those chips are now the real templates: **Server health at a glance**, **CPU investigation**, **Blocking and deadlocks**, **Memory pressure** and **Configuration review**, each created in one click against a server picked beside them, and they sit in the existing "New from template" menu alongside the notebook seeds ([#1563] D7) rather than in a second affordance. **Templates, not seeded rows**: seeding would need a migration rung, a `StorageVersion` bump, four pinned test files and a Viewer probe sentinel for content that is not schema - and a seeded row RESURRECTS itself on the next upgrade after the user deletes it, with no reset path if an editing seat breaks one. A template is created only when asked for, and what lands is an ordinary view the user owns, edits and can delete for good. The two halves of the menu behave differently on purpose: a notebook template links to the composer pre-filled because its composed panels re-scope live from the notebook's own controls, while a dashboard template is v1 READ panels whose `server` param is **static** (`renderView` threads variables and range into composed panels only), so it is created against the server chosen in the menu - and the server name goes into the view NAME, so two servers' copies of one template do not collide on the unique-name constraint. **Honest on day one**, which for a starter dashboard is the whole game - it is the first screen a UAT tester opens, on the store with the least data it will ever have. Every table and chart panel carries its own empty-state sentence (a count pin, not a spot-check), and the two reads that CANNOT be honest on a fresh install are deliberately absent rather than merely unused: analysis findings (the pass writes nothing for 24 hours) and Query Store (a target with it off has nothing, ever). The chart case is the one that bites without looking like it - `get_blocking_trend` and `get_deadlock_trend` answer an IDLE server with `trend: []` and no `{status,message}` envelope, so those panels say an empty trend means none happened rather than inheriting a sentence about collection. Every empty and failure state is stated rather than blank: a fleet with no servers says why the dashboards are unavailable instead of rendering an empty picker, and a refused create surfaces the backend's own message verbatim (with a 409 getting its own sentence, since a second click of the same template is the likely failure) because flattening it to "could not create" would hide the one line that says which panel is wrong. Pinned by the same invariant the built-in pages carry - every read exists in the shipped dispatch, every parameter key is one its read binds, every viz is in the shipped vocabulary - and each of the five definitions was fed to the service's own `ValidateDefinition`, the authority that would refuse the POST.
- **The web dashboard's server page gets the viewer's tabs: twelve sections, 61 of the 82 served reads, and a time range** ([#2475]) - the service dispatched **82** reads at `GET /api/read/{name}` and the built-in pages reached **23** of them; `#/server/{name}` was one scroll of seven panels against the desktop viewer's **65** `TabItem`s. The gap was never backend work - `panels.js` has been a generic renderer over those reads since #1562 - so this is descriptors over the unchanged `renderPanel` seam: twelve sub-tabs (Overview, Wait Stats, CPU, Memory, Blocking, File I/O, Queries, Configuration, Config Changes, Activity, System Events, Collection Health) carrying ~70 panels, reaching **61** reads. The header carries the WHY beneath the band badge - `Warning` has three unrelated causes (a real metric breach, a server awaiting its first collection, a collector error), so a badge reading "Warning" with no way to ask why is [#2422] rebuilt on a new surface; the fleet's own reason string and the fleet card's severity chips are rendered there, the chips through `fleet.js`'s own `metricBands` so there is one implementation rather than two, and neither is re-derived in the browser (R1). Every web grid that renders query text now puts it immediately right of its time/identity anchor ([#1949]) and the pin that enforced that on one array enforces it on all seven. The tab id rides in the hash (`#/server/{name}/{tab}`) so a section is deep-linkable and survives the 60s refresh, and an unknown or absent id resolves to Overview, which is what keeps every existing `#/server/{name}` link working. A page-level range picker (1h/4h/12h/24h/7d/30d) is the twin of the viewer's toolbar presets - **not** persisted, because a page that reopens on a 30-day window is slow for a reason the reader cannot see; panels whose read takes no window at all say "latest snapshot" rather than inheriting a label that would misdescribe them. Two reads were previously **unreachable from a browser** because they require a parameter no UI collected - `get_wait_trend` needs a `wait_type` and `get_perfmon_trend` a `counter_name` - and both now have a picker, the wait one seeded from the rows of the table directly above it so the picker and the table cannot disagree. **No fifth viz kind was added**: the property-grid shape that tempted one is served by `stat` (the reads returning a flat object) and `table` (the ones already returning rows), and a fifth kind that lived only in `panels.js` would be a page-only special case, while doing it properly means `KnownVizList`, `derive.js` and an editor config arm - composer surface this change does not need. What the browser genuinely cannot do is **stated in the tab where a reader goes looking for it** rather than left as a page that quietly lacks a feature: plan analysis, the query heatmap, cached-plan retrieval and actual-plan re-execution need a plan renderer and a command back to the monitored server, and the block-chain view and interactive deadlock graph need a graph viewer - the Blocking tab hands over the captured blocked-process-report and deadlock-graph XML verbatim instead of pretending. Every data panel supplies its own empty-state sentence and both helpers THROW without one. vizTable's generic "No rows in this window" reads as a fault on a collector that is off, opt-in, or daily; and the chart case was worse - `get_blocking_trend` and `get_deadlock_trend` answer an IDLE server with `trend: []` and no `{status,message}` envelope at all, so a perfectly healthy server was told its blocking chart did not have "enough data points to chart yet". `vizLine` now renders a descriptor's `emptyText` at exactly ZERO rows and still falls through at one (where the chart's own sentence is the true one) and when no `emptyText` was authored, so every stored view predating this is unchanged. A read feeding several panels on one tab is fetched ONCE (`fanout`) rather than per descriptor - `readTool` has no cache, so `get_collection_health`, which rolls up seven days of collector logs and computes sweep pressure, was running three times to open its own tab; six such duplicates existed across five tabs and a pin now refuses a seventh. Guarded by an invariant rather than by spot-checks: every read name the module mentions must exist in the shipped dispatch, every parameter key must be one its read actually binds (an unknown query key is silently ignored, so `limit` sent to a read binding `top` quietly returns the default), every viz must be in the shipped vocabulary, and no `get_pg_*` read may appear until the fleet payload can tell a PostgreSQL target from a SQL Server one - it carries `engine_edition`, not a `CollectorTargetEngine`, so a PostgreSQL panel today would render on all 42 SQL Servers, permanently empty.

### Changed
Expand Down Expand Up @@ -2848,6 +2849,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2312]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2312
[#2306]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2306
[#2302]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2302
[#2480]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2480
[#2437]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2437
[#1563]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/1563
[#2475]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2475
[#2422]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2422
[#2460]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2460
Expand Down
Loading
Loading