4.0.0: twelve new packages, optimistic concurrency, and a dashboard that shows them - #46
Merged
Merged
Conversation
The dispatcher skipped subscribing entirely when no IAlertSink was present. That was right while sinks were the only consumer of an alert; it is not right now that the dashboard reads the same history, because an application that installs alerting for the panel and configures delivery later gets a panel that can never fill. Also loads uptime for the checker the board selects on load. That selection is made by MarkLoaded rather than by a click, so it never went through the path that reads it, and the panel opened missing the columns every later selection showed.
Turns the Unreleased section into 4.0.0, reordered to Keep a Changelog's Added/Fixed/Security and with the two Fixed blocks merged. The intro says plainly that the major number is about the size of the release rather than a break in it, because every entry under it is additive. Documents the panels the board grows per feature package, in both the repository README and the dashboard package's own, including which of them survive read-only mode and why EXPLAIN does not.
A zero-capacity alert history threw on the first alert rather than discarding it: trimming ran before the enqueue, so an empty queue was the case on every call. Trimming after the enqueue removes the special case instead of guarding it, and HealthieAlertOptions.HistoryLength now clamps to a minimum of one the way MaxHistoryLength does. The rest is tidying the review found: the six insight contracts split one per file, since IHealthieInsights.cs was the only file in Abstractions holding more than one public type and no type of that name existed; a doc block that ended up with two summaries; an unused HasInsights property; the alerts markup indented a level deeper than its siblings; a CSS variable fallback no other call site carries and no theme leaves undefined; and _history grouped with the other injected fields rather than after an unrelated counter.
Twelve packages are new since v3.1.4, not eleven -- counted against the tag rather than from the list in the section, which had drifted.
The intro claimed nothing public changed shape. Diffing the public API of the eight pre-3.1.4 packages against the tag says otherwise: HealthieTools' constructor gained a defaulted HealthieMcpOptions parameter when it was fixed to read MaxHistoryPageSize. Source- compatible, binary-breaking, and worth naming -- it is the reason the major number is honest rather than only a size claim.
The sample installs the uptime package, so the panel grew a 24H cell and the hard-coded count of three failed. A count was the wrong assertion anyway: 24H appears once a segment exists and WORST only after an outage, both of which depend on how long the app has been up. Asserts the three that are always there instead, and that nothing unexpected joins them -- scoped to the stats grid, since the same label class dresses the GROUP and TAGS editors below it.
Both docs said it appears only on a follower. It appears whenever the leader-election package is installed, reading LEADER or FOLLOWER and naming the replica on hover -- which is the more useful behaviour when the package is opt-in and installed precisely because there is more than one replica. Caught by running a leader-elected instance rather than by reading the markup.
Grouping is on when the board opens. A group is a partition, so the sectioned view answers what is wrong and where without the reader doing it in their head; checkers with no group collect under one heading rather than vanishing. The stale tag-era names went with it -- _groupByTags and TagGroup partitioned on Group, which is the exact confusion CLAUDE.md warns about, preserved in the identifiers. The board ignored PulseSchedule entirely. It read state.Interval for the rate column, the aggregate CHECKS/MIN and the interval picker, and Interval is documented as ignored once Schedule is set -- so a cron checker advertised a rate it was not running at, was summed into the aggregate at that rate, and offered a picker that stored a field nothing reads. It now reads EffectiveSchedule, shows a cron expression as one, counts cron checkers separately rather than guessing a rate for them, and disables the picker while an expression is in force. A cron expression can also be set from the board. The scheduler judges it before anything is stored, through a new defaulted IPulseScheduler.TryValidateSchedule, because Cronos, Quartz and Temporal do not agree on dialects and the only answer worth having is from the implementation that will run it. SetIntervalAsync now clears the schedule, so choosing an interval takes effect. The side menu is a rail, not a drawer: this package ships no JavaScript, and an overlay drawer needs a focus trap to be honest about keyboard use. Collapsed it keeps its icons and its toggle; below 820px it becomes a scrolling strip above the list.
… the old default Three E2E grouping tests clicked the GROUP button to turn grouping on. With it on by default that click turns it off, so they asserted grouped structure on an ungrouped board -- one failed and two passed by luck, which is worse. The one that needed a flat count for comparison now takes the grouped count first and toggles to flat, which is the comparison it always wanted; the other two simply stop toggling. New coverage: the board opens grouped, the side menu narrows to a group and back, the cron editor applies a valid expression and refuses a bad one without disturbing what is stored, and the interval picker is inert for a cron checker and live for an interval one. The cron E2E tests first read the box before the click had round-tripped -- the box is already visible for whichever checker was selected on load, so waiting on it proves nothing. They wait on the panel title instead. Also: the unit test double derived from PulseChecker, so assembly scanning discovered it and could not construct it, which failed twenty registration, MCP and AI tests that have nothing to do with schedules. It takes only a state provider now, like every other checker in that assembly. Caught by running the whole suite rather than the filter.
…metrics The alerts list was a full-bleed band that shoved the whole board down when it opened, edge to edge with none of the card treatment every other panel has and a CLOSE button spanning half the width. It is now a view in the middle column, switched from the side menu alongside a new metrics view, so opening it changes one column instead of displacing the page. The history is written through the application's own IStateProvider, so a deployment on CosmosDB, Postgres or Redis keeps its alerts across a redeploy and one on the in-memory provider does not. No second storage contract, and no provider had to learn about alerts. Reads are paged -- an operator arriving after a restart is looking for what happened before it, which is exactly what a last-twenty list throws away -- and the view says how many are kept, of what cap, in which provider. Sinks are listed whether or not they have done anything, with delivered and failed counts and the last error. "Nothing has alerted" and "nothing is configured to deliver" looked identical and mean opposite things. A sink that recovers stops being shown as failing, because what matters is whether it is working now. Alerting is configurable from the board: minimum severity, deduplication window, delivery timeout and recovery alerts, which are the four the dispatcher reads on every alert rather than snapshotting. Its queue capacity and history length are fixed when it is built, so they are shown as facts rather than offered as controls that would quietly do nothing. A test alert goes through the real sinks, because the only way to find out a webhook URL is wrong is to use it. Metrics come from a MeterListener on the Healthie.NET meter, opt-in through AddHealthieMetrics: it reads the instruments this library already emits without competing with an OpenTelemetry exporter on the same meter. Overlapped triggers get their own card -- a checker whose check outlasts its interval looks healthy and is quietly running at a fraction of the rate it was asked to, and that is the only place it shows. Three E2E tests interacted with the detail panel straight after clicking a row. The panel's controls are already on screen for whatever was selected on load, so waiting on them proved nothing: the tag test was editing the wrong checker and failing elsewhere. They wait on the panel title now, through one helper.
Removing the dead band rules deleted from ".hpm-alerts {" to the cron
marker, and the whole side-menu block sat between the two -- so the rail
lost every rule it had and rendered as wrapped unstyled chips. Put back
where it was, in front of the cron rules, so the 820px override at the
end of the file still wins over it.
Removes an AlertPage.Empty factory nothing called, and the _uptimeFor cache: it short-circuited a re-read of the same checker, so a board left open on one checker showed a 24H figure that never moved again. Dropping it deletes a field and a branch and makes clicking the selected row the way to refresh. Documents the alert history's persistence and paging, the sink panel, the settings the board can change and the ones it deliberately cannot, and the metrics view -- in the changelog and the dashboard README -- plus a v3-to-v4 migration guide covering the one binary break and the ALTER the relational providers run on startup.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cuts 4.0.0 and finishes the dashboard work behind it.
What this adds
IStateProvider, so it survives a redeploy on any durable provider. No second storage contract, and no provider had to learn about alerts.AddHealthieMetrics()reads theHealthie.NETmeter in-process, alongside any OpenTelemetry exporter rather than instead of one.Bugs this fixes
PulseScheduleentirely. It readstate.Intervalfor the rate column, the aggregate checks-per-minute and the interval picker — and that field is documented as ignored onceScheduleis set. A cron checker advertised a rate it was not running at and offered a picker that stored a field nothing reads.SetIntervalAsyncnow clears the schedule, so choosing an interval takes effect.AlertHistorythrew on the first alert at capacity zero, andHistoryLengthhad no validation.Compatibility
Source-compatible — see
docs/migration-v3-to-v4.md. One binary break (HealthieTools' constructor gained a defaulted parameter), and the relational providers add aversioncolumn on startup.Verification