feat: add opt-in interaction instrumentation with click-to-request correlation - #98
Draft
parker-edwards wants to merge 25 commits into
Draft
feat: add opt-in interaction instrumentation with click-to-request correlation#98parker-edwards wants to merge 25 commits into
parker-edwards wants to merge 25 commits into
Conversation
…hared utils Pulls determinePropagatorTypes, addTraceContextHttpHeaders, endSpanOnError, and endSpanOnAbort out of fetch.ts into the transport-agnostic instrumentations/http/utils.ts module so xhr.ts (ENG-4822) can reuse them without duplicating logic. Pure refactor, no behavior change. Ref: ENG-4822
Registers the new instrumentation name and enablement gate ahead of the full XMLHttpRequest instrumentation implementation (ENG-4822). Defaults to enabled, matching @dash0/fetch, since axios/XHR-based consumers need this on by default to get traceparent propagation. Ref: ENG-4822
Adds instrumentXhr() covering open()/setRequestHeader()/send() up through span creation, common/url/method attributes, propagator header injection (mirrors fetch.ts's determinePropagatorTypes/addTraceContextHttpHeaders), request header capture, and starting resource-timing correlation. Request completion (success/error/timeout/abort) lands in the next commit. Ref: ENG-4822
…ort) Wires loadend/error/timeout/abort listeners to classify failures, mirrors fetch.ts's endSpanOnAbort/endSpanOnError semantics, captures response headers via getAllResponseHeaders(), and routes span completion through the shared resource-timing correlation (observeResourcePerformance) so XHR spans get the same network timing events as fetch spans. Unit tests await the async resource-timing completion path (maxWait=0 in tests) rather than assuming synchronous span delivery. Ref: ENG-4822
Prevents unbounded listener and closure accumulation on reused XMLHttpRequest instances; cleanup happens on loadend, which fires for every request outcome. Ref: ENG-4822
…d axios Adds test/e2e/spec/09-xhr-instrumentation covering same-origin XHR, cross- origin traceparent injection, X-Ray on a same-origin XHR request, ignored URLs, synchronous XHR, and an axios GET request (axios defaults to its XHR adapter in browsers, the setup that motivated ENG-4822). Serves axios's UMD bundle from the local e2e test server. Ref: ENG-4822
… e2e case Ref: ENG-4822
README and INSTALL previously only mentioned fetch for HTTP instrumentation and trace propagation; several INSTALL.md option descriptions already referenced XMLHttpRequest in anticipation of this feature (ENG-4822) and are now accurate. Ref: ENG-4822
Add InteractionInstrumentationSettings type with enabled/actionNameAttribute fields, wire it through InitOptions and the merge-based init() pipeline with a double gate (enabledInstrumentations name check AND interactionInstrumentation.enabled), and add a stub startInteractionInstrumentation entry point fleshed out in a later task.
…or boundaries The id fast path in buildSelector skipped MAX_SELECTOR_LENGTH; both branches now share one truncation.
…eck, testId, static import)
…ents
Four changes shaped by validating the preview against an Angular 20 +
ASP.NET Core 9 stack:
1. Action names: remove the target.textContent catch-all. Text is only
read from clickable elements (button/link/label/[role=button]), so a
click on an unnamed container yields a blank name with target
metadata instead of the container's entire visible text.
2. Readable event bodies: browser.interaction bodies are now plain
human-readable strings ('Click "Save Part" on /inventory/parts');
structured fields moved to namespaced interaction.* log attributes.
UIs without a dedicated renderer for this event type display a
sentence instead of stringified JSON.
3. Click-to-request correlation: interactions register a short-lived
(2s) active interaction; XHR/fetch spans started in that window are
stamped with user_interaction.id/name and the event carries the same
interaction.id, joining a click to the requests it triggered.
4. New interaction types behind the existing opt-in: scroll (one event
per burst with net direction), key_press (allow-listed control keys
only, printable characters never), change (value length / selected
count only, never the value; password fields report neither). Each
type has its own opt-out flag (captureScrolls, captureKeyPresses,
captureChanges).
375 unit tests passing (about 50 new), lint/prettier/build clean.
Bundle cost of all four changes: 22.5 -> 23.7 kB gzip (ESM).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enabling interaction instrumentation now captures only clicks, as before this PR. The new capture types each require an explicit opt-in (captureScrolls / captureKeyPresses / captureChanges) because they add event volume and capture additional -- if carefully minimized -- data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Navigation keys (arrows, Tab, Escape, paging keys) still emit key_press events but no longer register as the active interaction: they don't cause requests, so letting them claim the attribution slot could stamp unrelated background requests or steal attribution from a real click within the 2s window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… interaction.* attributes The interactions e2e spec still asserted the pre-#4 kvlist body shape. PR #4 moved the structured fields to namespaced interaction.* log attributes and made the body a plain-string human-readable title, but only updated the unit tests. Align the e2e expectations with the ratified shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thiemok
added a commit
that referenced
this pull request
Jul 24, 2026
…on (#99) What Adds XMLHttpRequest instrumentation (@dash0/xhr) — HTTP client spans and trace-context propagation for XHR, matching the existing fetch instrumentation. This replaces #93 (closed), split per feature at the reviewing team's request — this is part 2 of 3 and independent of the startView PR (#96); the interaction-instrumentation PR (#98) is stacked on top of this one. Why axios's default browser adapter uses XMLHttpRequest, so today axios/XHR-based applications get no HTTP spans and no traceparent header — their frontend telemetry never joins backend traces. How New @dash0/xhr instrumentation name, enabled by default alongside @dash0/fetch. First commit extracts the propagator/error helpers from fetch.ts into http/utils.ts, so fetch and XHR share one propagation code path (traceparent, X-Ray, propagators config, CORS URL allow-lists) — no behavior change for fetch. XHR spans cover the full lifecycle: success, error, timeout, and abort all end the span, and per-request event listeners are removed on completion to avoid leaks. Documented in INSTALL.md and the README feature list. axios is added as a devDependency only, for the e2e spec. --------- Co-authored-by: Parker Hyland <parker.hyland@dash0.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
What
Adds opt-in interaction instrumentation (
@dash0/interactions): automaticbrowser.interactionevents for clicks (plus individually opt-in scroll / key-press / change capture), with click-to-request correlation onto HTTP spans. This replaces #93 (closed), split per feature at the reviewing team's request — this is part 3 of 3.Why
Teams migrating from other RUM products (Datadog RUM's
trackUserInteractionsin particular) expect click analytics with human-readable action names, and the ability to tie a user action to the HTTP requests it triggered.How
interactionInstrumentation.enableddefaults tofalse. Scroll (captureScrolls), key-press (captureKeyPresses), and change (captureChanges) capture are each additionally opt-in.windowlistener (no per-element wiring, no listener leakage) emits onebrowser.interactionlog per interaction: a plain-string human-readable body (e.g.Click "Save Settings" on /settings) plus namespacedinteraction.*attributes (type,name,name_source,target.tag,target.id,target.selector,id).data-dash0-action-nameby default, configurable viaactionNameAttribute) → standard attributes (aria-label,aria-labelledby,alt,title,placeholder, button-ishvalue) → visible text → blank. Each event'sname_sourcereflects which phase produced the name.user_interaction.id/user_interaction.name. Key presses attribute only for activation keys (Enter/Space).INSTALL.md.Testing
pnpm run ci(lint, prettier, unit tests, build): green — 365 unit tests across 26 files (~90 new across action-name derivation, click/scroll/keypress/change capture, emit, attribution, and config plumbing).10-interactions(custom attribute naming, text/aria-label derivation, input-value privacy, default-disabled): green locally, as is the full suite (10/10 spec files) viapnpm run test:e2e:local.Notes for reviewers
This feature was built together with @seanpeterkin (the scroll/key-press/change capture and click-to-request correlation are his work; commit authorship is preserved). It's our own initiative rather than a scheduled roadmap item — happy to adjust naming, semantics, or scope to fit your conventions.
🤖 Generated with Claude Code