Skip to content

Add the custom-alert web authoring editor (#3285, Component 7) - #3357

Merged
erikdarlingdata merged 1 commit into
devfrom
feature/3285-custom-alert-web-editor
Sep 12, 2026
Merged

erikdarlingdata merged 1 commit into
devfrom
feature/3285-custom-alert-web-editor

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Custom-alert web authoring editor (#3285, Component 7)

The one remaining unbuilt piece of custom alerting — the web rule authoring editor. The backend, MCP tools, templates, and the /api/alerts endpoints all already shipped; this is a faithful front-end port of the custom-views editor onto that surface.

What's here

  • wwwroot/js/alerts-api.js — the /api/alerts client, the twin of views-api.js: list / get / create / update / delete with the same version optimistic-concurrency + response classification, plus the alert-only validate and test POSTs. Re-exports the shared getSession / getCatalog so one cache backs every composer surface.
  • wwwroot/js/pages/alert-rules.js — the rule list, the twin of renderViewList: cards (name, enabled/paused, and per-card metric / condition / scope chips enriched from the definition), a first-run hero, and a "New from template" menu.
  • wwwroot/js/alert-editor.js — the rule editor. Reuses the shared composer body for the metric (a Scalar metric), plus predicate (gt/ge/lt/le + warn/critical thresholds), hysteresis (breach/clear samples), scope (all / specific servers), and an optional evaluation cadence (Advanced). Inline validation via POST /api/alerts/validate, and a live "current value / would-fire per server" preview via POST /api/alerts/test, both debounced. Mirrors the composer's save-blocker for the cheap structural rules — window ceiling (24h), warn/critical ordering, and the count-with-< trap — so the operator gets a reason without a round-trip, with the server re-validating on every write.
  • wwwroot/js/editor.js — buildComposedPanelBody gains a metricOnly mode (a Scalar metric picker: metric + aggregate + unit + filters; no shape/group/chart/identity or preview) and a generic panelExtraAction hook. No alert knowledge added to the shared composer.
  • wwwroot/js/alert-seed.js — a leaf module for the "Create alert from this metric" handoff (a copy, not a live binding), offered on a Scalar panel in both the dashboard and notebook composers via the panelExtraAction hook. A leaf so there's no editor.js ↔ alert-editor.js import cycle (callers pass their own composedPanelToDesc).
  • app.js / index.html / editor.css — the #/alert-rules + #/alert-rule/new[/{template}] + #/alert-rule/{id} routes, the "Alert Rules" nav entry, the poll-clobber guard (alertEditor never re-rendered mid-edit), and the styles.

Template-serving approach (the decision you asked me to make + note)

Templates are read from the existing GET /api/alert-templates (which wraps the list_custom_alert_templates MCP tool), not hardcoded in JS. Rationale: unlike custom-views' templates — which are client-side JS constants (view-templates.js) — the alert starter set is server-authored C# (CustomAlertTemplates.All), drift-guarded against the live catalog on every build. Re-hardcoding it in JS would duplicate that set and let it drift. A dedicated fetch endpoint already ships and is the exact analog of how the views template picker gets its ready-made set, so mirroring "how views serves templates" here means consuming the server's set. Clicking a template opens the editor pre-filled (not an immediate save), because the starters are deliberately conservative and want tuning — mirroring the notebook-template flow.

For a reviewer to eyeball

  • metricOnly on the shared composer body — the load-bearing reuse. It hides shape/group/chart/identity and the compose preview, forces the Scalar metric, and reuses the same measure/aggregate/unit/filter controls. Worth confirming it reads as a restriction of the existing picker, not a fork.
  • panelExtraAction hook + alert-seed.js — the cycle-free way both composers offer "Create alert from this metric". It's rebuilt on each structural change, so the action reveals/hides live as the panel's shape flips to/from Single value.
  • Severity is the two thresholds — there's no separate severity control because the definition has none: warn = Warning tier, warn+critical = Critical tier (CustomAlertRuleDefinition.SeverityFor). Labels + help text carry that.
  • validate + test split — validate gives the inline verdict (cheap, no DB); test runs only when valid, for the per-server preview. Mirrors the composer's saveBlocker-then-preview shape.

Verification

  • dotnet build …Darling.Service.csproj -c Debug → 0 Warning(s) / 0 Error(s).
  • All new/modified JS pass node --check as ES modules; every cross-module import resolves to a real export.
  • The two source-scan pins my files touch verified green: DarlingWebSelfContainmentTests (air-gap) and DarlingWebEditorAttributionTests (incl. the composer probe-failed pin, which now discovers alert-editor.js as a third composer and requires it to distinguish a read-only seat from a failed probe — it does).
  • CRLF preserved (* text=auto eol=crlf); no mixed-ending files.
  • Did not run any live/integration tests.

Deferrals / gaps

  • "Last-fired" on the list cards is not shown — it isn't available from the rule endpoints (the summary carries no fired-at, and correlating config_alert_log to a rule would need a backend change outside this front-end lane, plus a design decision on the correlation/endpoint shape). The spec said "last-fired if available"; flagging rather than filing an issue unilaterally — say the word if you want a tracked backend enhancement.
  • Between/outside operators (Custom alerts: between / outside range predicate operators #3351) and tag scope (Custom alerts: tag scope (scope.mode: "tag") #3350) are intentionally omitted (only gt/ge/lt/le and all/specific-servers), per the plan; both already have issues.
  • No CHANGELOG entry — the task scoped me to the front-end and several sibling lanes are editing CHANGELOG concurrently (line-ending conflict risk); happy to add one if you'd like it in this PR.
  • No sidebar rule list (unlike the Views sidebar list) — deliberately mirrors Alert History (nav-only), keeping the refresh loop untouched. Easy to add if wanted.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WejwpgWF5Xfm3fAmoEbFz4

Ports the custom-views editor to author custom alert rules over the
already-shipped /api/alerts CRUD + /validate + /test surface, mirroring
pages/views.js / views-api.js / editor.js exactly.

- alerts-api.js: the /api/alerts client, the twin of views-api.js (list /
  get / create / update / delete with version optimistic concurrency, plus
  the alert-only validate + test POSTs). Re-exports the shared getSession /
  getCatalog so one cache backs every composer surface.
- pages/alert-rules.js: the rule list (name, enabled/paused, and per-card
  metric / condition / scope chips enriched from the definition) + a first-
  run hero + a "New from template" menu, the twin of renderViewList.
- alert-editor.js: the rule editor. Reuses the shared composer body for the
  metric (locked to a Scalar metric), plus predicate (gt/ge/lt/le + warn /
  critical thresholds), hysteresis, scope (all / specific servers), and an
  optional cadence. Inline validation via POST /api/alerts/validate and a
  live "current value / would-fire per server" preview via POST
  /api/alerts/test, both debounced. Mirrors the composer's save-blocker for
  the cheap structural rules (window ceiling, warn/critical ordering, the
  count-with-'<' trap) so the operator gets a reason without a round-trip.
- editor.js: buildComposedPanelBody gains a metricOnly mode (a scalar metric
  picker: metric + aggregate + unit + filters, no shape/group/chart/identity
  or preview) and a generic panelExtraAction hook. No alert knowledge added.
- alert-seed.js: a leaf module for the "Create alert from this metric"
  handoff (a copy, not a live binding), offered on a scalar panel in both
  the dashboard and notebook composers via the panelExtraAction hook — a
  clean DAG, no editor.js <-> alert-editor.js import cycle.
- app.js / index.html / editor.css: the #/alert-rules + #/alert-rule routes,
  the nav entry, the poll-clobber guard, and the styles.

Templates are read from the existing GET /api/alert-templates (the server-
authored, drift-guarded CustomAlertTemplates set) rather than hardcoded in
JS, since — unlike views' client-side template constants — the alert set is
C# validated against the live catalog on every build.

Build: 0 Warning(s) / 0 Error(s). Air-gap self-containment + the composer
probe-failed pin verified green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejwpgWF5Xfm3fAmoEbFz4
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown

Review summary

Reviewed the new custom-alert-rule web editor (Darling-only: alert-editor.js, alert-seed.js, alerts-api.js, pages/alert-rules.js, plus the editor.js/notebook.js/app.js/index.html/editor.css hookups).

No T-SQL is touched by this PR, so the T-SQL style rules don't apply here. Lite has no web dashboard at all (it's WPF-only), so there's no Lite counterpart for this surface — no parity drift to flag.

Checked and confirmed correct:

  • Client/server mirroring: the save-blocker's window ceiling (MAX_WINDOW_HOURS = 24, MIN_WINDOW_HOURS = 1/60), the count + </<= rejection, the warn/critical ordering (>=/<=, equal values allowed both sides), and MIN_INTERVAL_SECONDS = 30 all match CustomAlertRuleDefinition's authoritative constants and comparisons exactly — no drift between the inline client check and the server's TryParse.
  • XSS/R4 compliance: all user-controlled text (rule name/description, server names) reaches the DOM via el()'s text/textContent path; no innerHTML use anywhere in the new files.
  • Response classification: alerts-api.js / the new /api/alerts/* calls correctly match classifyResponse's three-kind contract (data/empty/error), including the no_in_scope_servers case which deliberately lacks a top-level message so it isn't misclassified as "empty".
  • Import-cycle avoidance: alert-seed.js is correctly kept as a leaf (only imports util.js) so editor.js/notebook.js → alert-seed.js and alert-editor.js → alert-seed.js stays a DAG.
  • CSS classes: all classes referenced from the new JS (scope-list, scope-item, status-cell, sev-Warning/Critical/Healthy, table-wrap, etc.) already exist in app.css; the alert-specific ones added in editor.css are all consumed.
  • The per-card enrichCard fetch in pages/alert-rules.js (one GET per rule to fill in chips) mirrors the identical, pre-existing pattern in pages/views.js — not a new N+1 introduced by this PR.

Nothing else stood out as a correctness, security, or performance concern. This is a solid, well cross-checked port of the composer pattern onto the alert-rule surface.

@erikdarlingdata
erikdarlingdata merged commit 25bae3e into dev Sep 12, 2026
8 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/3285-custom-alert-web-editor branch September 12, 2026 20:32
pull Bot pushed a commit to ehtick/PerformanceMonitor that referenced this pull request Sep 18, 2026
…in the changelog

The erikdarlingdata#3285 [Unreleased] "Added" bullet said "a visual web editor is a
follow-up" -- it shipped in erikdarlingdata#3357, so this flips it to describe the editor
(Scalar compose picker, inline validation, live per-server would-fire
preview, start-from-template, create-from-panel). Also corrects "a
server/tag scope" to "a scope of all servers or a chosen few": tag scope is
deferred (erikdarlingdata#3350), so the bullet should not claim it. Documentation only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejwpgWF5Xfm3fAmoEbFz4
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