Knowing What Operators Do
At 2 AM during a fire, nobody files a bug report. If a dispatcher fumbles with a control, clicks the wrong thing three times, or hits an error and just… works around it, the only witness is telemetry. That's what this chapter is about: the app streams every click, session, and error to Azure Application Insights, where we can replay what operators actually did — not what we hoped they'd do.
This lesson covers what gets tracked and how the pipeline works. Anatomy of a Click goes deep on the click tracker, and Asking Questions of the Data is the query cookbook.
What gets tracked
Five streams, from broadest to most specific:
Every click. A global listener captures each click with rich context — what was clicked,
what it said, where it sits in the component hierarchy, and every data-* attribute it carried.
This is Dynatrace-style ambient capture: nobody has to remember to instrument a button for it to
show up in the data.
Sessions. Start (browser, platform, screen size, timezone) and end (duration, page views, last action) — the envelope around everything else.
Identity. After sign-in, the user's id, organization, and role are attached to all subsequent telemetry, so "what did agency X's operators struggle with" is an answerable question.
Errors. Console errors, unhandled promise rejections, and React component crashes — with one deliberate filter: ArcGIS abort errors are excluded, because the map fires them constantly during normal pan/zoom and they'd drown everything else.
Domain events. Explicit calls for the things clicks can't express: camera interactions (view, PTZ, snapshot), map interactions (pan, zoom, layer toggles), alert interactions (create, acknowledge).
How a click becomes a queryable row
Three things in that picture matter in practice. Events are batched, so don't expect one-network-request-per-click in the DevTools network tab. There's a 2–5 minute lag before events are queryable — patience before declaring your instrumentation broken. And retention is 90 days by default, so long-term analyses need their conclusions exported, not re-run.
Vitals: how the app feels, not just what was clicked
Alongside events and page views, lib/vitals.ts reports how the app performs
on the operator's machine, as custom metrics the Admin app's Vitals tab
reads on demand:
- Core Web Vitals (
webvitals.LCP,INP,CLS,TTFB,FCP) from theweb-vitalslibrary, one row per page load or interaction, with its rating. - A responsiveness aggregate per visible minute (
ui.longTasks,ui.blockedMs,ui.longTaskMax,ui.heapMB,ui.mountedTiles,ui.pfEngines): long tasks of 50 ms and up, main-thread blocked time, the worst freeze, JS heap, mounted camera tiles and live PeakFinder engines.ui.longTasksandui.blockedMsare rates per visible minute; every row carriesvisibleSec(how much of the window the tab was visible) andreason(tickevery 60 s,hiddenwhen the tab is hidden or closed), so a 50 s visit and the last minute of a long session both count. Windows with under 5 s of visibility are not reported. The olderlong-taskevent only fires above 1 s, which is why a 300 ms stall every 10 s never appeared in telemetry.
Every row carries the build number as the envelope's application version and
view (tile / location), deviceClass and viewport as properties, so any
table can be split by release, screen and device. Reporting starts with the
App Insights instance (startVitalsReporting in lib/applicationInsights.ts).
Where the code lives
The whole system is two files plus a wiring point:
lib/applicationInsights.ts— the SDK setup and everytrack*function:initializeAppInsights,setUserContext/clearUserContext,trackClick,trackSessionStart/End, and the domain trackers (trackCameraInteraction,trackMapInteraction,trackAlertInteraction).hooks/useAppInsightsTracking.ts— the React hooks:useClickTracking(the global listener),useSessionTracking(beforeunload),useUserTracking(auto-links auth state).pages/_app.tsx— where the hooks are mounted, once, for the whole app.
Initialization is guarded by typeof window !== 'undefined' — the SDK never runs during SSR —
and requires one env var, NEXT_PUBLIC_APPLICATIONINSIGHTS_CONNECTION_STRING. No connection
string, no telemetry, no error: it fails quiet.
Wrapping up
Every click, session, identity, error, and domain event flows through two files into Azure, batched and queryable within minutes. The system is ambient — you get click data for free — but the quality of that data depends on how components are written, which is exactly where the next lesson picks up: Anatomy of a Click.