Telemetry > Provision form — validation, lookup, safety, and parity¶
Motivation¶
The "Provision a resource" form in Settings → Telemetry was a thin pass-through
to the backend POST /api/settings/app-insights/provision route, with no
client-side validation, no region helper, no existence preview, no confirmation
prompt, and no way to set the workspace's separate resource group or Log
Analytics retention. The 2026-05-27 walk-through identified 27 specific gaps.
User-facing change¶
- All five existing fields now validate inline against the same regexes the backend enforces (subscription GUID, resource group, name, region). Invalid values render in red beneath the field and disable the submit button with a tooltip explanation.
- Region is now an
<input list="…">backed by a<datalist>of the common Azure region slugs (28 entries). Free-text custom regions still work for unusual GAs, but typos like "koreacental" are caught before round-trip. - A new optional Workspace resource group field lets operators put the Log Analytics workspace in a central observability RG rather than the App Insights RG.
- A new Log Analytics retention dropdown exposes the previously-hardcoded 30-day value with 7 / 14 / 30 / 60 / 90 / 120 / 180 / 270 / 365 / 550 / 730 options (Azure-supported set). Default stays at 30.
- The form is wrapped in
<form onSubmit>so the Enter key now submits. - The submit button is disabled until every field validates; the Loader2 icon
now spins (re-uses the existing
spinkeyframe inglass.css). - The whole form is
<fieldset disabled={busy}>while the task runs so the user cannot mutate inputs mid-task. - Clicking submit produces a
window.confirmsummary listing RG, AI name, region, workspace name (with cross-RG callout) and retention before any ARM call goes out. The dialog also notes that PerGB2018 ingestion charges may apply. - A new Reset to defaults button restores the env-aware defaults (now
appi-${envName}/log-${envName}derived from the workload RG, with the hard-coded…-elb-dashboardfallback). - A debounced "does this name already exist?" check fires whenever the
subscription / RG / AI name are all valid — UI shows
Checking… → Will reuse existing / Name is available / 409 multiple
matches. This uses the existing
POST /api/settings/app-insights/lookuproute, no new backend surface needed. - When the underlying Celery task finishes successfully, the form auto- collapses and the parent renders a persistent success line: "Provisioning finished. App Insights component created; Log Analytics workspace reused. Server telemetry applied."
- Region-mismatch warning: when the chosen region differs from the workload region recorded by the Setup Wizard, the hint line turns amber and explains the cross-region Container Insights latency cost.
- Every field now has an explicit
id↔htmlForpairing,required/aria-required,aria-invalid, andautocomplete="off". TaskStatusLinenow renders a thin progress bar driven by thestep / total_stepspayload thatpublish_progresswas already sending. Provision shows three steps (workspace → component → server-apply).
API / IaC diff summary¶
Backend¶
POST /api/settings/app-insights/provisionnow also accepts:workspace_resource_group?: string(already plumbed through to the task, but exercised by tests for the first time).retention_days?: int— validated against the discrete Azure-supported set{7, 14, 30, 60, 90, 120, 180, 270, 365, 550, 730}; anything else is a400.provision_app_insightsCelery task now:- Takes a
retention_days: int | Nonekwarg and forwards it toensure_log_analytics_workspace. - Performs a quick
get_workspace/get_application_insightslookup before the ensure calls so the result envelope can includeworkspace_created: boolandcomponent_created: bool. The SPA uses these to render "created" vs "reused" in the success line. - Result envelope additions:
workspace_created,component_created. The pre-existing keys (workspace,component,connection_string,deployment_apply) are unchanged so older clients still work. api/tests/test_settings_app_insights.py:test_provision_enqueues_celery_task_and_returns_idnow also asserts the optional kwargs default toNone.- New
test_provision_accepts_workspace_rg_and_retention_days. - New
test_provision_rejects_invalid_retention_days.
Frontend¶
AppInsightsProvisionRequestaddsretention_days?: number.SettingsPanel.tsx:ProvisionFormStateaddsworkspace_resource_group: stringandretention_days: number.TaskStateaddsstep?: numberandtotalSteps?: number;usePollTaskcaptures both from theprogresspayload andTaskStatusLinerenders a matching<div role="progressbar">.- New
ProvisionField,validateProvisionFields,validateProvisionForm,deriveEnvNamehelpers (all module-local). - The standalone
Fieldprimitive is unchanged; the form usesProvisionFieldinstead to get explicithtmlFor, error slot, and optional warning-tone hint.
IaC¶
None.
Validation evidence¶
uv run pytest -q api/tests/test_settings_app_insights.py→ 12 passed (added 3 over the previous 10).uv run pytest -q api/tests→ 1527 passed (no regressions).uv run ruff check api→ All checks passed!cd web && npm run build→ built in 6.77 s (no TS errors).
Out of scope (intentionally deferred)¶
- Custom tag editor in the form — charter §12 defines a standard tag set that IaC stamps onto every resource; per-resource user-supplied tags would diverge from that convention.
- Per-field font-family / monospace exception — the cosmetic concern that
INPUT_STYLEis monospace for all fields. Defer; not blocking. - Native "Cancel" inside the form vs the existing row-level "Hide form" — the two controls have the same effect (collapse without submitting), so a duplicate Cancel button would only add visual noise.